mirror of
https://github.com/ppy/osu.git
synced 2024-11-11 10:33:30 +08:00
Merge branch 'master' into improve-beatmap-card-buttons
This commit is contained in:
commit
fb0bc597a0
@ -15,6 +15,8 @@ M:Realms.CollectionExtensions.SubscribeForNotifications`1(System.Collections.Gen
|
||||
M:System.Threading.Tasks.Task.Wait();Don't use Task.Wait. Use Task.WaitSafely() to ensure we avoid deadlocks.
|
||||
P:System.Threading.Tasks.Task`1.Result;Don't use Task.Result. Use Task.GetResultSafely() to ensure we avoid deadlocks.
|
||||
M:System.Threading.ManualResetEventSlim.Wait();Specify a timeout to avoid waiting forever.
|
||||
M:System.Char.ToLower(System.Char);char.ToLower() changes behaviour depending on CultureInfo.CurrentCulture. Use char.ToLowerInvariant() instead. If wanting culture-sensitive behaviour, explicitly provide CultureInfo.CurrentCulture.
|
||||
M:System.Char.ToUpper(System.Char);char.ToUpper() changes behaviour depending on CultureInfo.CurrentCulture. Use char.ToUpperInvariant() instead. If wanting culture-sensitive behaviour, explicitly provide CultureInfo.CurrentCulture.
|
||||
M:System.String.ToLower();string.ToLower() changes behaviour depending on CultureInfo.CurrentCulture. Use string.ToLowerInvariant() instead. If wanting culture-sensitive behaviour, explicitly provide CultureInfo.CurrentCulture or use LocalisableString.
|
||||
M:System.String.ToUpper();string.ToUpper() changes behaviour depending on CultureInfo.CurrentCulture. Use string.ToUpperInvariant() instead. If wanting culture-sensitive behaviour, explicitly provide CultureInfo.CurrentCulture or use LocalisableString.
|
||||
M:Humanizer.InflectorExtensions.Pascalize(System.String);Humanizer's .Pascalize() extension method changes behaviour depending on CultureInfo.CurrentCulture. Use StringDehumanizeExtensions.ToPascalCase() instead.
|
||||
|
66
Gemfile.lock
66
Gemfile.lock
@ -3,25 +3,25 @@ GEM
|
||||
specs:
|
||||
CFPropertyList (3.0.5)
|
||||
rexml
|
||||
addressable (2.8.0)
|
||||
public_suffix (>= 2.0.2, < 5.0)
|
||||
addressable (2.8.1)
|
||||
public_suffix (>= 2.0.2, < 6.0)
|
||||
artifactory (3.0.15)
|
||||
atomos (0.1.3)
|
||||
aws-eventstream (1.2.0)
|
||||
aws-partitions (1.601.0)
|
||||
aws-sdk-core (3.131.2)
|
||||
aws-partitions (1.653.0)
|
||||
aws-sdk-core (3.166.0)
|
||||
aws-eventstream (~> 1, >= 1.0.2)
|
||||
aws-partitions (~> 1, >= 1.525.0)
|
||||
aws-sigv4 (~> 1.1)
|
||||
aws-partitions (~> 1, >= 1.651.0)
|
||||
aws-sigv4 (~> 1.5)
|
||||
jmespath (~> 1, >= 1.6.1)
|
||||
aws-sdk-kms (1.57.0)
|
||||
aws-sdk-core (~> 3, >= 3.127.0)
|
||||
aws-sdk-kms (1.59.0)
|
||||
aws-sdk-core (~> 3, >= 3.165.0)
|
||||
aws-sigv4 (~> 1.1)
|
||||
aws-sdk-s3 (1.114.0)
|
||||
aws-sdk-core (~> 3, >= 3.127.0)
|
||||
aws-sdk-s3 (1.117.1)
|
||||
aws-sdk-core (~> 3, >= 3.165.0)
|
||||
aws-sdk-kms (~> 1)
|
||||
aws-sigv4 (~> 1.4)
|
||||
aws-sigv4 (1.5.0)
|
||||
aws-sigv4 (1.5.2)
|
||||
aws-eventstream (~> 1, >= 1.0.2)
|
||||
babosa (1.0.4)
|
||||
claide (1.1.0)
|
||||
@ -34,10 +34,10 @@ GEM
|
||||
rake (>= 12.0.0, < 14.0.0)
|
||||
domain_name (0.5.20190701)
|
||||
unf (>= 0.0.5, < 1.0.0)
|
||||
dotenv (2.7.6)
|
||||
dotenv (2.8.1)
|
||||
emoji_regex (3.2.3)
|
||||
excon (0.92.3)
|
||||
faraday (1.10.0)
|
||||
excon (0.93.1)
|
||||
faraday (1.10.2)
|
||||
faraday-em_http (~> 1.0)
|
||||
faraday-em_synchrony (~> 1.0)
|
||||
faraday-excon (~> 1.1)
|
||||
@ -66,7 +66,7 @@ GEM
|
||||
faraday_middleware (1.2.0)
|
||||
faraday (~> 1.0)
|
||||
fastimage (2.2.6)
|
||||
fastlane (2.206.2)
|
||||
fastlane (2.210.1)
|
||||
CFPropertyList (>= 2.3, < 4.0.0)
|
||||
addressable (>= 2.8, < 3.0.0)
|
||||
artifactory (~> 3.0)
|
||||
@ -110,9 +110,9 @@ GEM
|
||||
souyuz (= 0.11.1)
|
||||
fastlane-plugin-xamarin (0.6.3)
|
||||
gh_inspector (1.1.3)
|
||||
google-apis-androidpublisher_v3 (0.23.0)
|
||||
google-apis-core (>= 0.6, < 2.a)
|
||||
google-apis-core (0.6.0)
|
||||
google-apis-androidpublisher_v3 (0.29.0)
|
||||
google-apis-core (>= 0.9.0, < 2.a)
|
||||
google-apis-core (0.9.1)
|
||||
addressable (~> 2.5, >= 2.5.1)
|
||||
googleauth (>= 0.16.2, < 2.a)
|
||||
httpclient (>= 2.8.1, < 3.a)
|
||||
@ -121,27 +121,27 @@ GEM
|
||||
retriable (>= 2.0, < 4.a)
|
||||
rexml
|
||||
webrick
|
||||
google-apis-iamcredentials_v1 (0.12.0)
|
||||
google-apis-core (>= 0.6, < 2.a)
|
||||
google-apis-playcustomapp_v1 (0.9.0)
|
||||
google-apis-core (>= 0.6, < 2.a)
|
||||
google-apis-storage_v1 (0.16.0)
|
||||
google-apis-core (>= 0.6, < 2.a)
|
||||
google-apis-iamcredentials_v1 (0.15.0)
|
||||
google-apis-core (>= 0.9.0, < 2.a)
|
||||
google-apis-playcustomapp_v1 (0.12.0)
|
||||
google-apis-core (>= 0.9.1, < 2.a)
|
||||
google-apis-storage_v1 (0.19.0)
|
||||
google-apis-core (>= 0.9.0, < 2.a)
|
||||
google-cloud-core (1.6.0)
|
||||
google-cloud-env (~> 1.0)
|
||||
google-cloud-errors (~> 1.0)
|
||||
google-cloud-env (1.6.0)
|
||||
faraday (>= 0.17.3, < 3.0)
|
||||
google-cloud-errors (1.2.0)
|
||||
google-cloud-storage (1.36.2)
|
||||
google-cloud-errors (1.3.0)
|
||||
google-cloud-storage (1.43.0)
|
||||
addressable (~> 2.8)
|
||||
digest-crc (~> 0.4)
|
||||
google-apis-iamcredentials_v1 (~> 0.1)
|
||||
google-apis-storage_v1 (~> 0.1)
|
||||
google-apis-storage_v1 (~> 0.19.0)
|
||||
google-cloud-core (~> 1.6)
|
||||
googleauth (>= 0.16.2, < 2.a)
|
||||
mini_mime (~> 1.0)
|
||||
googleauth (1.2.0)
|
||||
googleauth (1.3.0)
|
||||
faraday (>= 0.17.3, < 3.a)
|
||||
jwt (>= 1.4, < 3.0)
|
||||
memoist (~> 0.16)
|
||||
@ -154,22 +154,22 @@ GEM
|
||||
httpclient (2.8.3)
|
||||
jmespath (1.6.1)
|
||||
json (2.6.2)
|
||||
jwt (2.4.1)
|
||||
jwt (2.5.0)
|
||||
memoist (0.16.2)
|
||||
mini_magick (4.11.0)
|
||||
mini_mime (1.1.2)
|
||||
mini_portile2 (2.7.1)
|
||||
mini_portile2 (2.8.0)
|
||||
multi_json (1.15.0)
|
||||
multipart-post (2.0.0)
|
||||
nanaimo (0.3.0)
|
||||
naturally (2.2.1)
|
||||
nokogiri (1.13.1)
|
||||
mini_portile2 (~> 2.7.0)
|
||||
nokogiri (1.13.9)
|
||||
mini_portile2 (~> 2.8.0)
|
||||
racc (~> 1.4)
|
||||
optparse (0.1.1)
|
||||
os (1.1.4)
|
||||
plist (3.6.0)
|
||||
public_suffix (4.0.7)
|
||||
public_suffix (5.0.0)
|
||||
racc (1.6.0)
|
||||
rake (13.0.6)
|
||||
representable (3.2.0)
|
||||
|
@ -138,10 +138,10 @@ platform :ios do
|
||||
end
|
||||
|
||||
lane :testflight_prune_dry do
|
||||
clean_testflight_testers(days_of_inactivity:45, dry_run: true)
|
||||
clean_testflight_testers(days_of_inactivity:30, dry_run: true)
|
||||
end
|
||||
|
||||
lane :testflight_prune do
|
||||
clean_testflight_testers(days_of_inactivity: 45)
|
||||
clean_testflight_testers(days_of_inactivity: 30)
|
||||
end
|
||||
end
|
||||
|
@ -51,8 +51,8 @@
|
||||
<Reference Include="Java.Interop" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.1008.0" />
|
||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2022.1011.0" />
|
||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.1021.0" />
|
||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2022.1101.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. -->
|
||||
|
@ -18,17 +18,9 @@ using osu.Framework;
|
||||
using osu.Framework.Logging;
|
||||
using osu.Game.Updater;
|
||||
using osu.Desktop.Windows;
|
||||
using osu.Framework.Input.Handlers;
|
||||
using osu.Framework.Input.Handlers.Joystick;
|
||||
using osu.Framework.Input.Handlers.Mouse;
|
||||
using osu.Framework.Input.Handlers.Tablet;
|
||||
using osu.Framework.Input.Handlers.Touch;
|
||||
using osu.Framework.Threading;
|
||||
using osu.Game.IO;
|
||||
using osu.Game.IPC;
|
||||
using osu.Game.Overlays.Settings;
|
||||
using osu.Game.Overlays.Settings.Sections;
|
||||
using osu.Game.Overlays.Settings.Sections.Input;
|
||||
using osu.Game.Utils;
|
||||
using SDL2;
|
||||
|
||||
@ -148,27 +140,6 @@ namespace osu.Desktop
|
||||
desktopWindow.DragDrop += f => fileDrop(new[] { f });
|
||||
}
|
||||
|
||||
public override SettingsSubsection CreateSettingsSubsectionFor(InputHandler handler)
|
||||
{
|
||||
switch (handler)
|
||||
{
|
||||
case ITabletHandler th:
|
||||
return new TabletSettings(th);
|
||||
|
||||
case MouseHandler mh:
|
||||
return new MouseSettings(mh);
|
||||
|
||||
case JoystickHandler jh:
|
||||
return new JoystickSettings(jh);
|
||||
|
||||
case TouchHandler th:
|
||||
return new InputSection.HandlerSection(th);
|
||||
|
||||
default:
|
||||
return base.CreateSettingsSubsectionFor(handler);
|
||||
}
|
||||
}
|
||||
|
||||
protected override BatteryInfo CreateBatteryInfo() => new SDL2BatteryInfo();
|
||||
|
||||
private readonly List<string> importableFiles = new List<string>();
|
||||
|
@ -0,0 +1,23 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using NUnit.Framework;
|
||||
using osu.Game.Rulesets.Catch.Mods;
|
||||
using osu.Game.Tests.Visual;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Tests.Mods
|
||||
{
|
||||
public class TestSceneCatchModFlashlight : ModTestScene
|
||||
{
|
||||
protected override Ruleset CreatePlayerRuleset() => new CatchRuleset();
|
||||
|
||||
[TestCase(1f)]
|
||||
[TestCase(0.5f)]
|
||||
[TestCase(1.25f)]
|
||||
[TestCase(1.5f)]
|
||||
public void TestSizeMultiplier(float sizeMultiplier) => CreateModTest(new ModTestData { Mod = new CatchModFlashlight { SizeMultiplier = { Value = sizeMultiplier } }, PassCondition = () => true });
|
||||
|
||||
[Test]
|
||||
public void TestComboBasedSize([Values] bool comboBasedSize) => CreateModTest(new ModTestData { Mod = new CatchModFlashlight { ComboBasedSize = { Value = comboBasedSize } }, PassCondition = () => true });
|
||||
}
|
||||
}
|
@ -17,6 +17,7 @@ using osu.Game.Rulesets.Catch.Edit;
|
||||
using osu.Game.Rulesets.Catch.Mods;
|
||||
using osu.Game.Rulesets.Catch.Replays;
|
||||
using osu.Game.Rulesets.Catch.Scoring;
|
||||
using osu.Game.Rulesets.Catch.Skinning.Argon;
|
||||
using osu.Game.Rulesets.Catch.Skinning.Legacy;
|
||||
using osu.Game.Rulesets.Catch.UI;
|
||||
using osu.Game.Rulesets.Difficulty;
|
||||
@ -188,6 +189,9 @@ namespace osu.Game.Rulesets.Catch
|
||||
{
|
||||
case LegacySkin:
|
||||
return new CatchLegacySkinTransformer(skin);
|
||||
|
||||
case ArgonSkin:
|
||||
return new CatchArgonSkinTransformer(skin);
|
||||
}
|
||||
|
||||
return null;
|
||||
|
@ -0,0 +1,49 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Rulesets.Catch.UI;
|
||||
using osu.Game.Rulesets.UI;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Edit
|
||||
{
|
||||
public class CatchEditorPlayfieldAdjustmentContainer : PlayfieldAdjustmentContainer
|
||||
{
|
||||
protected override Container<Drawable> Content => content;
|
||||
private readonly Container content;
|
||||
|
||||
public CatchEditorPlayfieldAdjustmentContainer()
|
||||
{
|
||||
Anchor = Anchor.TopCentre;
|
||||
Origin = Anchor.TopCentre;
|
||||
Size = new Vector2(0.8f, 0.9f);
|
||||
|
||||
InternalChild = new ScalingContainer
|
||||
{
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre,
|
||||
Child = content = new Container { RelativeSizeAxes = Axes.Both },
|
||||
};
|
||||
}
|
||||
|
||||
private class ScalingContainer : Container
|
||||
{
|
||||
public ScalingContainer()
|
||||
{
|
||||
RelativeSizeAxes = Axes.Y;
|
||||
Width = CatchPlayfield.WIDTH;
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
|
||||
Scale = new Vector2(Math.Min(Parent.ChildSize.X / CatchPlayfield.WIDTH, Parent.ChildSize.Y / CatchPlayfield.HEIGHT));
|
||||
Height = 1 / Scale.Y;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -3,6 +3,7 @@
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using JetBrains.Annotations;
|
||||
@ -10,10 +11,11 @@ using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.EnumExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Input;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Input.Bindings;
|
||||
using osu.Game.Rulesets.Catch.Objects;
|
||||
using osu.Game.Rulesets.Catch.UI;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
@ -21,7 +23,6 @@ using osu.Game.Rulesets.Edit.Tools;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.UI;
|
||||
using osu.Game.Screens.Edit.Components.TernaryButtons;
|
||||
using osu.Game.Screens.Edit.Compose.Components;
|
||||
using osuTK;
|
||||
|
||||
@ -33,10 +34,14 @@ namespace osu.Game.Rulesets.Catch.Edit
|
||||
|
||||
private CatchDistanceSnapGrid distanceSnapGrid;
|
||||
|
||||
private readonly Bindable<TernaryState> distanceSnapToggle = new Bindable<TernaryState>();
|
||||
|
||||
private InputManager inputManager;
|
||||
|
||||
private readonly BindableDouble timeRangeMultiplier = new BindableDouble(1)
|
||||
{
|
||||
MinValue = 1,
|
||||
MaxValue = 10,
|
||||
};
|
||||
|
||||
public CatchHitObjectComposer(CatchRuleset ruleset)
|
||||
: base(ruleset)
|
||||
{
|
||||
@ -51,7 +56,10 @@ namespace osu.Game.Rulesets.Catch.Edit
|
||||
|
||||
LayerBelowRuleset.Add(new PlayfieldBorder
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Anchor = Anchor.BottomCentre,
|
||||
Origin = Anchor.BottomCentre,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Height = CatchPlayfield.HEIGHT,
|
||||
PlayfieldBorderStyle = { Value = PlayfieldBorderStyle.Corners }
|
||||
});
|
||||
|
||||
@ -70,6 +78,19 @@ namespace osu.Game.Rulesets.Catch.Edit
|
||||
inputManager = GetContainingInputManager();
|
||||
}
|
||||
|
||||
protected override double ReadCurrentDistanceSnap(HitObject before, HitObject after)
|
||||
{
|
||||
// osu!catch's distance snap implementation is limited, in that a custom spacing cannot be specified.
|
||||
// Therefore this functionality is not currently used.
|
||||
//
|
||||
// The implementation below is probably correct but should be checked if/when exposed via controls.
|
||||
|
||||
float expectedDistance = DurationToDistance(before, after.StartTime - before.GetEndTime());
|
||||
float actualDistance = Math.Abs(((CatchHitObject)before).EffectiveX - ((CatchHitObject)after).EffectiveX);
|
||||
|
||||
return actualDistance / expectedDistance;
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
@ -77,8 +98,30 @@ namespace osu.Game.Rulesets.Catch.Edit
|
||||
updateDistanceSnapGrid();
|
||||
}
|
||||
|
||||
public override bool OnPressed(KeyBindingPressEvent<GlobalAction> e)
|
||||
{
|
||||
switch (e.Action)
|
||||
{
|
||||
// Note that right now these are hard to use as the default key bindings conflict with existing editor key bindings.
|
||||
// In the future we will want to expose this via UI and potentially change the key bindings to be editor-specific.
|
||||
// May be worth considering standardising "zoom" behaviour with what the timeline uses (ie. alt-wheel) but that may cause new conflicts.
|
||||
case GlobalAction.IncreaseScrollSpeed:
|
||||
this.TransformBindableTo(timeRangeMultiplier, timeRangeMultiplier.Value - 1, 200, Easing.OutQuint);
|
||||
break;
|
||||
|
||||
case GlobalAction.DecreaseScrollSpeed:
|
||||
this.TransformBindableTo(timeRangeMultiplier, timeRangeMultiplier.Value + 1, 200, Easing.OutQuint);
|
||||
break;
|
||||
}
|
||||
|
||||
return base.OnPressed(e);
|
||||
}
|
||||
|
||||
protected override DrawableRuleset<CatchHitObject> CreateDrawableRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList<Mod> mods = null) =>
|
||||
new DrawableCatchEditorRuleset(ruleset, beatmap, mods);
|
||||
new DrawableCatchEditorRuleset(ruleset, beatmap, mods)
|
||||
{
|
||||
TimeRangeMultiplier = { BindTarget = timeRangeMultiplier, }
|
||||
};
|
||||
|
||||
protected override IReadOnlyList<HitObjectCompositionTool> CompositionTools => new HitObjectCompositionTool[]
|
||||
{
|
||||
@ -87,11 +130,6 @@ namespace osu.Game.Rulesets.Catch.Edit
|
||||
new BananaShowerCompositionTool()
|
||||
};
|
||||
|
||||
protected override IEnumerable<TernaryButton> CreateTernaryButtons() => base.CreateTernaryButtons().Concat(new[]
|
||||
{
|
||||
new TernaryButton(distanceSnapToggle, "Distance Snap", () => new SpriteIcon { Icon = FontAwesome.Solid.Ruler })
|
||||
});
|
||||
|
||||
public override SnapResult FindSnappedPositionAndTime(Vector2 screenSpacePosition, SnapType snapType = SnapType.All)
|
||||
{
|
||||
var result = base.FindSnappedPositionAndTime(screenSpacePosition, snapType);
|
||||
@ -163,7 +201,7 @@ namespace osu.Game.Rulesets.Catch.Edit
|
||||
|
||||
private void updateDistanceSnapGrid()
|
||||
{
|
||||
if (distanceSnapToggle.Value != TernaryState.True)
|
||||
if (DistanceSnapToggle.Value != TernaryState.True)
|
||||
{
|
||||
distanceSnapGrid.Hide();
|
||||
return;
|
||||
|
@ -4,6 +4,7 @@
|
||||
#nullable disable
|
||||
|
||||
using System.Collections.Generic;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Catch.UI;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
@ -13,11 +14,24 @@ namespace osu.Game.Rulesets.Catch.Edit
|
||||
{
|
||||
public class DrawableCatchEditorRuleset : DrawableCatchRuleset
|
||||
{
|
||||
public readonly BindableDouble TimeRangeMultiplier = new BindableDouble(1);
|
||||
|
||||
public DrawableCatchEditorRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList<Mod> mods = null)
|
||||
: base(ruleset, beatmap, mods)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
|
||||
double gamePlayTimeRange = GetTimeRange(Beatmap.Difficulty.ApproachRate);
|
||||
float playfieldStretch = Playfield.DrawHeight / CatchPlayfield.HEIGHT;
|
||||
TimeRange.Value = gamePlayTimeRange * TimeRangeMultiplier.Value * playfieldStretch;
|
||||
}
|
||||
|
||||
protected override Playfield CreatePlayfield() => new CatchEditorPlayfield(Beatmap.Difficulty);
|
||||
|
||||
public override PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer() => new CatchEditorPlayfieldAdjustmentContainer();
|
||||
}
|
||||
}
|
||||
|
@ -19,17 +19,20 @@ namespace osu.Game.Rulesets.Catch.Mods
|
||||
{
|
||||
public override LocalisableString Description => @"Use the mouse to control the catcher.";
|
||||
|
||||
private DrawableRuleset<CatchHitObject> drawableRuleset = null!;
|
||||
private DrawableCatchRuleset drawableRuleset = null!;
|
||||
|
||||
public void ApplyToDrawableRuleset(DrawableRuleset<CatchHitObject> drawableRuleset)
|
||||
{
|
||||
this.drawableRuleset = drawableRuleset;
|
||||
this.drawableRuleset = (DrawableCatchRuleset)drawableRuleset;
|
||||
}
|
||||
|
||||
public void ApplyToPlayer(Player player)
|
||||
{
|
||||
if (!drawableRuleset.HasReplayLoaded.Value)
|
||||
drawableRuleset.Cursor.Add(new MouseInputHelper((CatchPlayfield)drawableRuleset.Playfield));
|
||||
{
|
||||
var catchPlayfield = (CatchPlayfield)drawableRuleset.Playfield;
|
||||
catchPlayfield.CatcherArea.Add(new MouseInputHelper(catchPlayfield.CatcherArea));
|
||||
}
|
||||
}
|
||||
|
||||
private class MouseInputHelper : Drawable, IKeyBindingHandler<CatchAction>, IRequireHighFrequencyMousePosition
|
||||
@ -38,9 +41,10 @@ namespace osu.Game.Rulesets.Catch.Mods
|
||||
|
||||
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true;
|
||||
|
||||
public MouseInputHelper(CatchPlayfield playfield)
|
||||
public MouseInputHelper(CatcherArea catcherArea)
|
||||
{
|
||||
catcherArea = playfield.CatcherArea;
|
||||
this.catcherArea = catcherArea;
|
||||
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
}
|
||||
|
||||
|
@ -28,6 +28,8 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
||||
|
||||
public float DisplayRotation => Rotation;
|
||||
|
||||
public double DisplayStartTime => HitObject.StartTime;
|
||||
|
||||
/// <summary>
|
||||
/// Whether this hit object should stay on the catcher plate when the object is caught by the catcher.
|
||||
/// </summary>
|
||||
|
@ -18,6 +18,8 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
||||
{
|
||||
public new PalpableCatchHitObject HitObject => (PalpableCatchHitObject)base.HitObject;
|
||||
|
||||
public double DisplayStartTime => LifetimeStart;
|
||||
|
||||
Bindable<Color4> IHasCatchObjectState.AccentColour => AccentColour;
|
||||
|
||||
public Bindable<bool> HyperDash { get; } = new Bindable<bool>();
|
||||
|
@ -16,6 +16,8 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
||||
{
|
||||
PalpableCatchHitObject HitObject { get; }
|
||||
|
||||
double DisplayStartTime { get; }
|
||||
|
||||
Bindable<Color4> AccentColour { get; }
|
||||
|
||||
Bindable<bool> HyperDash { get; }
|
||||
|
122
osu.Game.Rulesets.Catch/Skinning/Argon/ArgonBananaPiece.cs
Normal file
122
osu.Game.Rulesets.Catch/Skinning/Argon/ArgonBananaPiece.cs
Normal file
@ -0,0 +1,122 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Colour;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Effects;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Rulesets.Catch.Objects;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Skinning.Argon
|
||||
{
|
||||
internal class ArgonBananaPiece : ArgonFruitPiece
|
||||
{
|
||||
private Container stabilisedPieceContainer = null!;
|
||||
|
||||
private Drawable fadeContent = null!;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
AddInternal(fadeContent = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
stabilisedPieceContainer = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Circle
|
||||
{
|
||||
Colour = Color4.White.Opacity(0.4f),
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Blending = BlendingParameters.Additive,
|
||||
Size = new Vector2(8),
|
||||
Scale = new Vector2(25, 1),
|
||||
},
|
||||
new Box
|
||||
{
|
||||
Colour = ColourInfo.GradientHorizontal(Color4.White.Opacity(0), Color4.White.Opacity(0.8f)),
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Blending = BlendingParameters.Additive,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.CentreRight,
|
||||
Width = 1.6f,
|
||||
Height = 2,
|
||||
},
|
||||
new Circle
|
||||
{
|
||||
Colour = ColourInfo.GradientHorizontal(Color4.White.Opacity(0.8f), Color4.White.Opacity(0)),
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Blending = BlendingParameters.Additive,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.CentreLeft,
|
||||
Width = 1.6f,
|
||||
Height = 2,
|
||||
},
|
||||
}
|
||||
},
|
||||
new Circle
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Size = new Vector2(1.2f),
|
||||
EdgeEffect = new EdgeEffectParameters
|
||||
{
|
||||
Type = EdgeEffectType.Glow,
|
||||
Hollow = false,
|
||||
Colour = Color4.White.Opacity(0.1f),
|
||||
Radius = 50,
|
||||
},
|
||||
Child =
|
||||
{
|
||||
Alpha = 0,
|
||||
AlwaysPresent = true,
|
||||
},
|
||||
BorderColour = Color4.White.Opacity(0.1f),
|
||||
BorderThickness = 3,
|
||||
},
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
|
||||
const float parent_scale_application = 0.4f;
|
||||
|
||||
// relative to time on screen
|
||||
const float lens_flare_start = 0.3f;
|
||||
const float lens_flare_end = 0.8f;
|
||||
|
||||
// Undo some of the parent scale being applied to make the lens flare feel a bit better..
|
||||
float scale = parent_scale_application + (1 - parent_scale_application) * (1 / (ObjectState.DisplaySize.X / (CatchHitObject.OBJECT_RADIUS * 2)));
|
||||
|
||||
stabilisedPieceContainer.Rotation = -ObjectState.DisplayRotation;
|
||||
stabilisedPieceContainer.Scale = new Vector2(scale, 1);
|
||||
|
||||
double duration = ObjectState.HitObject.StartTime - ObjectState.DisplayStartTime;
|
||||
|
||||
fadeContent.Alpha = MathHelper.Clamp(
|
||||
Interpolation.ValueAt(
|
||||
Time.Current, 1f, 0f,
|
||||
ObjectState.DisplayStartTime + duration * lens_flare_start,
|
||||
ObjectState.DisplayStartTime + duration * lens_flare_end,
|
||||
Easing.OutQuint
|
||||
), 0, 1);
|
||||
}
|
||||
}
|
||||
}
|
85
osu.Game.Rulesets.Catch/Skinning/Argon/ArgonCatcher.cs
Normal file
85
osu.Game.Rulesets.Catch/Skinning/Argon/ArgonCatcher.cs
Normal file
@ -0,0 +1,85 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Game.Rulesets.Catch.UI;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Skinning.Argon
|
||||
{
|
||||
public class ArgonCatcher : CompositeDrawable
|
||||
{
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
new Container
|
||||
{
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Height = 10,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Circle
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Colour = Color4.White,
|
||||
Width = Catcher.ALLOWED_CATCH_RANGE,
|
||||
},
|
||||
new Box
|
||||
{
|
||||
Name = "long line left",
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreRight,
|
||||
Colour = Color4.White,
|
||||
Alpha = 0.25f,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Width = 20,
|
||||
Height = 1.8f,
|
||||
},
|
||||
new Circle
|
||||
{
|
||||
Name = "bumper left",
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
Colour = Color4.White,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Width = (1 - Catcher.ALLOWED_CATCH_RANGE) / 2,
|
||||
Height = 4,
|
||||
},
|
||||
new Box
|
||||
{
|
||||
Name = "long line right",
|
||||
Anchor = Anchor.CentreRight,
|
||||
Origin = Anchor.CentreLeft,
|
||||
Colour = Color4.White,
|
||||
Alpha = 0.25f,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Width = 20,
|
||||
Height = 1.8f,
|
||||
},
|
||||
new Circle
|
||||
{
|
||||
Name = "bumper right",
|
||||
Anchor = Anchor.CentreRight,
|
||||
Origin = Anchor.CentreRight,
|
||||
Colour = Color4.White,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Width = (1 - Catcher.ALLOWED_CATCH_RANGE) / 2,
|
||||
Height = 4,
|
||||
},
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
121
osu.Game.Rulesets.Catch/Skinning/Argon/ArgonDropletPiece.cs
Normal file
121
osu.Game.Rulesets.Catch/Skinning/Argon/ArgonDropletPiece.cs
Normal file
@ -0,0 +1,121 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Rulesets.Catch.Skinning.Default;
|
||||
using osu.Game.Rulesets.Catch.UI;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Skinning.Argon
|
||||
{
|
||||
internal class ArgonDropletPiece : CatchHitObjectPiece
|
||||
{
|
||||
protected override Drawable HyperBorderPiece => hyperBorderPiece;
|
||||
|
||||
private Drawable hyperBorderPiece = null!;
|
||||
|
||||
private Container layers = null!;
|
||||
|
||||
private float rotationRandomness;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
|
||||
const float droplet_scale_down = 0.7f;
|
||||
|
||||
int largeBlobSeed = RNG.Next();
|
||||
|
||||
InternalChildren = new[]
|
||||
{
|
||||
new Circle
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Size = new Vector2(20),
|
||||
},
|
||||
layers = new Container
|
||||
{
|
||||
Scale = new Vector2(droplet_scale_down),
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new CircularBlob
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Blending = BlendingParameters.Additive,
|
||||
InnerRadius = 0.5f,
|
||||
Alpha = 0.15f,
|
||||
Seed = largeBlobSeed
|
||||
},
|
||||
new CircularBlob
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Blending = BlendingParameters.Additive,
|
||||
InnerRadius = 0.4f,
|
||||
Alpha = 0.5f,
|
||||
Scale = new Vector2(0.7f),
|
||||
Seed = RNG.Next()
|
||||
},
|
||||
}
|
||||
},
|
||||
hyperBorderPiece = new CircularBlob
|
||||
{
|
||||
Scale = new Vector2(droplet_scale_down),
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Colour = Catcher.DEFAULT_HYPER_DASH_COLOUR,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Blending = BlendingParameters.Additive,
|
||||
InnerRadius = 0.5f,
|
||||
Alpha = 0.15f,
|
||||
Seed = largeBlobSeed
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
AccentColour.BindValueChanged(colour =>
|
||||
{
|
||||
foreach (var sprite in layers)
|
||||
sprite.Colour = colour.NewValue;
|
||||
}, true);
|
||||
|
||||
rotationRandomness = RNG.NextSingle(0.2f, 1);
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
|
||||
// Note that droplets are rotated at a higher level, so this is mostly just to create more
|
||||
// random arrangements of the multiple layers than actually rotate.
|
||||
//
|
||||
// Because underlying rotation is always clockwise, we apply anti-clockwise resistance to avoid
|
||||
// making things spin too fast.
|
||||
for (int i = 0; i < layers.Count; i++)
|
||||
{
|
||||
layers[i].Rotation -=
|
||||
(float)Clock.ElapsedFrameTime
|
||||
* 0.4f * rotationRandomness
|
||||
// Each layer should alternate rotation speed.
|
||||
* (i % 2 == 1 ? 0.5f : 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
121
osu.Game.Rulesets.Catch/Skinning/Argon/ArgonFruitPiece.cs
Normal file
121
osu.Game.Rulesets.Catch/Skinning/Argon/ArgonFruitPiece.cs
Normal file
@ -0,0 +1,121 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Rulesets.Catch.Skinning.Default;
|
||||
using osu.Game.Rulesets.Catch.UI;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Skinning.Argon
|
||||
{
|
||||
internal class ArgonFruitPiece : CatchHitObjectPiece
|
||||
{
|
||||
protected override Drawable HyperBorderPiece => hyperBorderPiece;
|
||||
|
||||
private Drawable hyperBorderPiece = null!;
|
||||
|
||||
private Container layers = null!;
|
||||
|
||||
private float rotationRandomness;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
|
||||
int largeBlobSeed = RNG.Next();
|
||||
|
||||
InternalChildren = new[]
|
||||
{
|
||||
new Circle
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Size = new Vector2(20),
|
||||
},
|
||||
layers = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new CircularBlob
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Blending = BlendingParameters.Additive,
|
||||
Alpha = 0.15f,
|
||||
InnerRadius = 0.5f,
|
||||
Size = new Vector2(1.1f),
|
||||
Seed = largeBlobSeed,
|
||||
},
|
||||
new CircularBlob
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Blending = BlendingParameters.Additive,
|
||||
InnerRadius = 0.2f,
|
||||
Alpha = 0.5f,
|
||||
Seed = RNG.Next(),
|
||||
},
|
||||
new CircularBlob
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Blending = BlendingParameters.Additive,
|
||||
InnerRadius = 0.05f,
|
||||
Seed = RNG.Next(),
|
||||
},
|
||||
}
|
||||
},
|
||||
hyperBorderPiece = new CircularBlob
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Colour = Catcher.DEFAULT_HYPER_DASH_COLOUR,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Blending = BlendingParameters.Additive,
|
||||
InnerRadius = 0.08f,
|
||||
Size = new Vector2(1.15f),
|
||||
Seed = largeBlobSeed
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
AccentColour.BindValueChanged(colour =>
|
||||
{
|
||||
foreach (var sprite in layers)
|
||||
sprite.Colour = colour.NewValue;
|
||||
}, true);
|
||||
|
||||
rotationRandomness = RNG.NextSingle(0.2f, 1) * (RNG.NextBool() ? -1 : 1);
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
|
||||
for (int i = 0; i < layers.Count; i++)
|
||||
{
|
||||
layers[i].Rotation +=
|
||||
// Layers are ordered from largest to smallest. Smaller layers should rotate more.
|
||||
(i * 2)
|
||||
* (float)Clock.ElapsedFrameTime
|
||||
* 0.02f * rotationRandomness
|
||||
// Each layer should alternate rotation direction.
|
||||
* (i % 2 == 1 ? 1 : -1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
112
osu.Game.Rulesets.Catch/Skinning/Argon/ArgonHitExplosion.cs
Normal file
112
osu.Game.Rulesets.Catch/Skinning/Argon/ArgonHitExplosion.cs
Normal file
@ -0,0 +1,112 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Effects;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Rulesets.Catch.Objects;
|
||||
using osu.Game.Rulesets.Catch.UI;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Skinning.Argon
|
||||
{
|
||||
public class ArgonHitExplosion : CompositeDrawable, IHitExplosion
|
||||
{
|
||||
public override bool RemoveWhenNotAlive => true;
|
||||
|
||||
private Container tallExplosion = null!;
|
||||
private Container largeFaint = null!;
|
||||
|
||||
private readonly Bindable<Color4> accentColour = new Bindable<Color4>();
|
||||
|
||||
public ArgonHitExplosion()
|
||||
{
|
||||
Size = new Vector2(20);
|
||||
Anchor = Anchor.BottomCentre;
|
||||
Origin = Anchor.BottomCentre;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
tallExplosion = new Container
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Masking = true,
|
||||
Width = 0.1f,
|
||||
Child = new Box
|
||||
{
|
||||
AlwaysPresent = true,
|
||||
Alpha = 0,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
},
|
||||
},
|
||||
largeFaint = new Container
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Masking = true,
|
||||
Child = new Box
|
||||
{
|
||||
AlwaysPresent = true,
|
||||
Alpha = 0,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
accentColour.BindValueChanged(colour =>
|
||||
{
|
||||
tallExplosion.EdgeEffect = new EdgeEffectParameters
|
||||
{
|
||||
Type = EdgeEffectType.Glow,
|
||||
Colour = colour.NewValue,
|
||||
Hollow = false,
|
||||
Roundness = 15,
|
||||
Radius = 15,
|
||||
};
|
||||
|
||||
largeFaint.EdgeEffect = new EdgeEffectParameters
|
||||
{
|
||||
Type = EdgeEffectType.Glow,
|
||||
Colour = Interpolation.ValueAt(0.2f, colour.NewValue, Color4.White, 0, 1),
|
||||
Hollow = false,
|
||||
Radius = 50,
|
||||
};
|
||||
}, true);
|
||||
}
|
||||
|
||||
public void Animate(HitExplosionEntry entry)
|
||||
{
|
||||
X = entry.Position;
|
||||
Scale = new Vector2(entry.HitObject.Scale);
|
||||
accentColour.Value = entry.ObjectColour;
|
||||
|
||||
using (BeginAbsoluteSequence(entry.LifetimeStart))
|
||||
{
|
||||
this.FadeOutFromOne(400);
|
||||
|
||||
if (!(entry.HitObject is Droplet))
|
||||
{
|
||||
float scale = Math.Clamp(entry.JudgementResult.ComboAtJudgement / 200f, 0.35f, 1.125f);
|
||||
|
||||
tallExplosion
|
||||
.ScaleTo(new Vector2(1.1f, 20 * scale), 200, Easing.OutQuint)
|
||||
.Then()
|
||||
.ScaleTo(new Vector2(1.1f, 1), 600, Easing.In);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
193
osu.Game.Rulesets.Catch/Skinning/Argon/ArgonJudgementPiece.cs
Normal file
193
osu.Game.Rulesets.Catch/Skinning/Argon/ArgonJudgementPiece.cs
Normal file
@ -0,0 +1,193 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Rulesets.Judgements;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Skinning.Argon
|
||||
{
|
||||
public class ArgonJudgementPiece : CompositeDrawable, IAnimatableJudgement
|
||||
{
|
||||
protected readonly HitResult Result;
|
||||
|
||||
protected SpriteText JudgementText { get; private set; } = null!;
|
||||
|
||||
private RingExplosion? ringExplosion;
|
||||
|
||||
[Resolved]
|
||||
private OsuColour colours { get; set; } = null!;
|
||||
|
||||
public ArgonJudgementPiece(HitResult result)
|
||||
{
|
||||
Result = result;
|
||||
Origin = Anchor.Centre;
|
||||
Y = 160;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
AutoSizeAxes = Axes.Both;
|
||||
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
JudgementText = new OsuSpriteText
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Text = Result.GetDescription().ToUpperInvariant(),
|
||||
Colour = colours.ForHitResult(Result),
|
||||
Blending = BlendingParameters.Additive,
|
||||
Spacing = new Vector2(10, 0),
|
||||
Font = OsuFont.Default.With(size: 28, weight: FontWeight.Regular),
|
||||
},
|
||||
};
|
||||
|
||||
if (Result.IsHit())
|
||||
{
|
||||
AddInternal(ringExplosion = new RingExplosion(Result)
|
||||
{
|
||||
Colour = colours.ForHitResult(Result),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Plays the default animation for this judgement piece.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The base implementation only handles fade (for all result types) and misses.
|
||||
/// Individual rulesets are recommended to implement their appropriate hit animations.
|
||||
/// </remarks>
|
||||
public virtual void PlayAnimation()
|
||||
{
|
||||
switch (Result)
|
||||
{
|
||||
default:
|
||||
JudgementText
|
||||
.ScaleTo(Vector2.One)
|
||||
.ScaleTo(new Vector2(1.4f), 1800, Easing.OutQuint);
|
||||
break;
|
||||
|
||||
case HitResult.Miss:
|
||||
this.ScaleTo(1.6f);
|
||||
this.ScaleTo(1, 100, Easing.In);
|
||||
|
||||
this.MoveTo(Vector2.Zero);
|
||||
this.MoveToOffset(new Vector2(0, 100), 800, Easing.InQuint);
|
||||
|
||||
this.RotateTo(0);
|
||||
this.RotateTo(40, 800, Easing.InQuint);
|
||||
break;
|
||||
}
|
||||
|
||||
this.FadeOutFromOne(800);
|
||||
|
||||
ringExplosion?.PlayAnimation();
|
||||
}
|
||||
|
||||
public Drawable? GetAboveHitObjectsProxiedContent() => null;
|
||||
|
||||
private class RingExplosion : CompositeDrawable
|
||||
{
|
||||
private readonly float travel = 52;
|
||||
|
||||
public RingExplosion(HitResult result)
|
||||
{
|
||||
const float thickness = 4;
|
||||
|
||||
const float small_size = 9;
|
||||
const float large_size = 14;
|
||||
|
||||
Anchor = Anchor.Centre;
|
||||
Origin = Anchor.Centre;
|
||||
|
||||
Blending = BlendingParameters.Additive;
|
||||
|
||||
int countSmall = 0;
|
||||
int countLarge = 0;
|
||||
|
||||
switch (result)
|
||||
{
|
||||
case HitResult.Meh:
|
||||
countSmall = 3;
|
||||
travel *= 0.3f;
|
||||
break;
|
||||
|
||||
case HitResult.Ok:
|
||||
case HitResult.Good:
|
||||
countSmall = 4;
|
||||
travel *= 0.6f;
|
||||
break;
|
||||
|
||||
case HitResult.Great:
|
||||
case HitResult.Perfect:
|
||||
countSmall = 4;
|
||||
countLarge = 4;
|
||||
break;
|
||||
}
|
||||
|
||||
for (int i = 0; i < countSmall; i++)
|
||||
AddInternal(new RingPiece(thickness) { Size = new Vector2(small_size) });
|
||||
|
||||
for (int i = 0; i < countLarge; i++)
|
||||
AddInternal(new RingPiece(thickness) { Size = new Vector2(large_size) });
|
||||
}
|
||||
|
||||
public void PlayAnimation()
|
||||
{
|
||||
foreach (var c in InternalChildren)
|
||||
{
|
||||
const float start_position_ratio = 0.3f;
|
||||
|
||||
float direction = RNG.NextSingle(0, 360);
|
||||
float distance = RNG.NextSingle(travel / 2, travel);
|
||||
|
||||
c.MoveTo(new Vector2(
|
||||
MathF.Cos(direction) * distance * start_position_ratio,
|
||||
MathF.Sin(direction) * distance * start_position_ratio
|
||||
));
|
||||
|
||||
c.MoveTo(new Vector2(
|
||||
MathF.Cos(direction) * distance,
|
||||
MathF.Sin(direction) * distance
|
||||
), 600, Easing.OutQuint);
|
||||
}
|
||||
|
||||
this.FadeOutFromOne(1000, Easing.OutQuint);
|
||||
}
|
||||
|
||||
public class RingPiece : CircularContainer
|
||||
{
|
||||
public RingPiece(float thickness = 9)
|
||||
{
|
||||
Anchor = Anchor.Centre;
|
||||
Origin = Anchor.Centre;
|
||||
|
||||
Masking = true;
|
||||
BorderThickness = thickness;
|
||||
BorderColour = Color4.White;
|
||||
|
||||
Child = new Box
|
||||
{
|
||||
AlwaysPresent = true,
|
||||
Alpha = 0,
|
||||
RelativeSizeAxes = Axes.Both
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,46 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Skinning;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Skinning.Argon
|
||||
{
|
||||
public class CatchArgonSkinTransformer : SkinTransformer
|
||||
{
|
||||
public CatchArgonSkinTransformer(ISkin skin)
|
||||
: base(skin)
|
||||
{
|
||||
}
|
||||
|
||||
public override Drawable? GetDrawableComponent(ISkinComponent component)
|
||||
{
|
||||
switch (component)
|
||||
{
|
||||
case CatchSkinComponent catchComponent:
|
||||
// TODO: Once everything is finalised, consider throwing UnsupportedSkinComponentException on missing entries.
|
||||
switch (catchComponent.Component)
|
||||
{
|
||||
case CatchSkinComponents.HitExplosion:
|
||||
return new ArgonHitExplosion();
|
||||
|
||||
case CatchSkinComponents.Catcher:
|
||||
return new ArgonCatcher();
|
||||
|
||||
case CatchSkinComponents.Fruit:
|
||||
return new ArgonFruitPiece();
|
||||
|
||||
case CatchSkinComponents.Banana:
|
||||
return new ArgonBananaPiece();
|
||||
|
||||
case CatchSkinComponents.Droplet:
|
||||
return new ArgonDropletPiece();
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
return base.GetDrawableComponent(component);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,21 +1,19 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using osu.Framework.Graphics;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Skinning.Default
|
||||
{
|
||||
public class BananaPiece : CatchHitObjectPiece
|
||||
{
|
||||
protected override BorderPiece BorderPiece { get; }
|
||||
protected override Drawable BorderPiece { get; }
|
||||
|
||||
public BananaPiece()
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
|
||||
InternalChildren = new Drawable[]
|
||||
InternalChildren = new[]
|
||||
{
|
||||
new BananaPulpFormation
|
||||
{
|
||||
|
@ -7,6 +7,7 @@ using System;
|
||||
using JetBrains.Annotations;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Rulesets.Catch.Objects.Drawables;
|
||||
using osuTK.Graphics;
|
||||
@ -26,13 +27,13 @@ namespace osu.Game.Rulesets.Catch.Skinning.Default
|
||||
/// A part of this piece that will be faded out while falling in the playfield.
|
||||
/// </summary>
|
||||
[CanBeNull]
|
||||
protected virtual BorderPiece BorderPiece => null;
|
||||
protected virtual Drawable BorderPiece => null;
|
||||
|
||||
/// <summary>
|
||||
/// A part of this piece that will be only visible when <see cref="HyperDash"/> is true.
|
||||
/// </summary>
|
||||
[CanBeNull]
|
||||
protected virtual HyperBorderPiece HyperBorderPiece => null;
|
||||
protected virtual Drawable HyperBorderPiece => null;
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
|
@ -11,13 +11,13 @@ namespace osu.Game.Rulesets.Catch.Skinning.Default
|
||||
{
|
||||
public class DropletPiece : CatchHitObjectPiece
|
||||
{
|
||||
protected override HyperBorderPiece HyperBorderPiece { get; }
|
||||
protected override Drawable HyperBorderPiece { get; }
|
||||
|
||||
public DropletPiece()
|
||||
{
|
||||
Size = new Vector2(CatchHitObject.OBJECT_RADIUS / 2);
|
||||
|
||||
InternalChildren = new Drawable[]
|
||||
InternalChildren = new[]
|
||||
{
|
||||
new Pulp
|
||||
{
|
||||
|
@ -18,14 +18,14 @@ namespace osu.Game.Rulesets.Catch.Skinning.Default
|
||||
|
||||
public readonly Bindable<FruitVisualRepresentation> VisualRepresentation = new Bindable<FruitVisualRepresentation>();
|
||||
|
||||
protected override BorderPiece BorderPiece { get; }
|
||||
protected override HyperBorderPiece HyperBorderPiece { get; }
|
||||
protected override Drawable BorderPiece { get; }
|
||||
protected override Drawable HyperBorderPiece { get; }
|
||||
|
||||
public FruitPiece()
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
|
||||
InternalChildren = new Drawable[]
|
||||
InternalChildren = new[]
|
||||
{
|
||||
new FruitPulpFormation
|
||||
{
|
||||
|
@ -23,6 +23,12 @@ namespace osu.Game.Rulesets.Catch.UI
|
||||
/// </summary>
|
||||
public const float WIDTH = 512;
|
||||
|
||||
/// <summary>
|
||||
/// The height of the playfield.
|
||||
/// This doesn't include the catcher area.
|
||||
/// </summary>
|
||||
public const float HEIGHT = 384;
|
||||
|
||||
/// <summary>
|
||||
/// The center position of the playfield.
|
||||
/// </summary>
|
||||
|
@ -31,7 +31,7 @@ namespace osu.Game.Rulesets.Catch.UI
|
||||
: base(ruleset, beatmap, mods)
|
||||
{
|
||||
Direction.Value = ScrollingDirection.Down;
|
||||
TimeRange.Value = IBeatmapDifficultyInfo.DifficultyRange(beatmap.Difficulty.ApproachRate, 1800, 1200, 450);
|
||||
TimeRange.Value = GetTimeRange(beatmap.Difficulty.ApproachRate);
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
@ -42,6 +42,8 @@ namespace osu.Game.Rulesets.Catch.UI
|
||||
KeyBindingInputManager.Add(new CatchTouchInputMapper());
|
||||
}
|
||||
|
||||
protected double GetTimeRange(float approachRate) => IBeatmapDifficultyInfo.DifficultyRange(approachRate, 1800, 1200, 450);
|
||||
|
||||
protected override ReplayInputHandler CreateReplayInputHandler(Replay replay) => new CatchFramedReplayInputHandler(replay);
|
||||
|
||||
protected override ReplayRecorder CreateReplayRecorder(Score score) => new CatchReplayRecorder(score, (CatchPlayfield)Playfield);
|
||||
|
@ -0,0 +1,23 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using NUnit.Framework;
|
||||
using osu.Game.Rulesets.Mania.Mods;
|
||||
using osu.Game.Tests.Visual;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Tests.Mods
|
||||
{
|
||||
public class TestSceneManiaModFlashlight : ModTestScene
|
||||
{
|
||||
protected override Ruleset CreatePlayerRuleset() => new ManiaRuleset();
|
||||
|
||||
[TestCase(1f)]
|
||||
[TestCase(0.5f)]
|
||||
[TestCase(1.5f)]
|
||||
[TestCase(3f)]
|
||||
public void TestSizeMultiplier(float sizeMultiplier) => CreateModTest(new ModTestData { Mod = new ManiaModFlashlight { SizeMultiplier = { Value = sizeMultiplier } }, PassCondition = () => true });
|
||||
|
||||
[Test]
|
||||
public void TestComboBasedSize([Values] bool comboBasedSize) => CreateModTest(new ModTestData { Mod = new ManiaModFlashlight { ComboBasedSize = { Value = comboBasedSize } }, PassCondition = () => true });
|
||||
}
|
||||
}
|
@ -49,7 +49,7 @@ namespace osu.Game.Rulesets.Mania.Tests
|
||||
assertHeadJudgement(HitResult.Miss);
|
||||
assertTickJudgement(HitResult.LargeTickMiss);
|
||||
assertTailJudgement(HitResult.Miss);
|
||||
assertNoteJudgement(HitResult.IgnoreHit);
|
||||
assertNoteJudgement(HitResult.IgnoreMiss);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -76,8 +76,8 @@ namespace osu.Game.Rulesets.Mania.Tests
|
||||
|
||||
performTest(objects, new List<ReplayFrame>());
|
||||
|
||||
addJudgementAssert(objects[0], HitResult.IgnoreHit);
|
||||
addJudgementAssert(objects[1], HitResult.IgnoreHit);
|
||||
addJudgementAssert(objects[0], HitResult.IgnoreMiss);
|
||||
addJudgementAssert(objects[1], HitResult.IgnoreMiss);
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Mania.Mods
|
||||
|
||||
public override string Acronym => "HO";
|
||||
|
||||
public override double ScoreMultiplier => 1;
|
||||
public override double ScoreMultiplier => 0.9;
|
||||
|
||||
public override LocalisableString Description => @"Replaces all hold notes with normal notes.";
|
||||
|
||||
|
@ -69,6 +69,8 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
||||
/// </summary>
|
||||
private double? releaseTime;
|
||||
|
||||
public override double MaximumJudgementOffset => Tail.MaximumJudgementOffset;
|
||||
|
||||
public DrawableHoldNote()
|
||||
: this(null)
|
||||
{
|
||||
@ -260,7 +262,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
||||
tick.MissForcefully();
|
||||
}
|
||||
|
||||
ApplyResult(r => r.Type = r.Judgement.MaxResult);
|
||||
ApplyResult(r => r.Type = Tail.IsHit ? r.Judgement.MaxResult : r.Judgement.MinResult);
|
||||
endHold();
|
||||
}
|
||||
|
||||
|
@ -4,14 +4,18 @@
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Framework.Input;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
using osu.Game.Rulesets.Osu.Beatmaps;
|
||||
using osu.Game.Rulesets.Osu.Edit;
|
||||
@ -33,6 +37,9 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
[Cached(typeof(IBeatSnapProvider))]
|
||||
private readonly EditorBeatmap editorBeatmap;
|
||||
|
||||
[Cached]
|
||||
private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Aquamarine);
|
||||
|
||||
[Cached]
|
||||
private readonly EditorClock editorClock;
|
||||
|
||||
@ -48,6 +55,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
};
|
||||
|
||||
private OsuDistanceSnapGrid grid;
|
||||
private SnappingCursorContainer cursor;
|
||||
|
||||
public TestSceneOsuDistanceSnapGrid()
|
||||
{
|
||||
@ -84,8 +92,8 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = Color4.SlateGray
|
||||
},
|
||||
cursor = new SnappingCursorContainer { GetSnapPosition = v => grid.GetSnappedPosition(grid.ToLocalSpace(v)).position },
|
||||
grid = new OsuDistanceSnapGrid(new HitCircle { Position = grid_position }),
|
||||
new SnappingCursorContainer { GetSnapPosition = v => grid.GetSnappedPosition(grid.ToLocalSpace(v)).position }
|
||||
};
|
||||
});
|
||||
|
||||
@ -150,6 +158,37 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
assertSnappedDistance(expectedDistance);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestReferenceObjectNotOnSnapGrid()
|
||||
{
|
||||
AddStep("create grid", () =>
|
||||
{
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = Color4.SlateGray
|
||||
},
|
||||
cursor = new SnappingCursorContainer { GetSnapPosition = v => grid.GetSnappedPosition(grid.ToLocalSpace(v)).position },
|
||||
grid = new OsuDistanceSnapGrid(new HitCircle
|
||||
{
|
||||
Position = grid_position,
|
||||
// This is important. It sets the reference object to a point in time that isn't on the current snap divisor's grid.
|
||||
// We are testing that the grid's display is offset correctly.
|
||||
StartTime = 40,
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
AddStep("move mouse to point", () => InputManager.MoveMouseTo(grid.ToScreenSpace(grid_position + new Vector2(beat_length, 0) * 2)));
|
||||
|
||||
AddAssert("Ensure cursor is on a grid line", () =>
|
||||
{
|
||||
return grid.ChildrenOfType<CircularProgress>().Any(p => Precision.AlmostEquals(p.ScreenSpaceDrawQuad.TopRight.X, grid.ToScreenSpace(cursor.LastSnappedPosition).X));
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestLimitedDistance()
|
||||
{
|
||||
@ -162,8 +201,8 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = Color4.SlateGray
|
||||
},
|
||||
cursor = new SnappingCursorContainer { GetSnapPosition = v => grid.GetSnappedPosition(grid.ToLocalSpace(v)).position },
|
||||
grid = new OsuDistanceSnapGrid(new HitCircle { Position = grid_position }, new HitCircle { StartTime = 200 }),
|
||||
new SnappingCursorContainer { GetSnapPosition = v => grid.GetSnappedPosition(grid.ToLocalSpace(v)).position }
|
||||
};
|
||||
});
|
||||
|
||||
@ -182,6 +221,8 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
{
|
||||
public Func<Vector2, Vector2> GetSnapPosition;
|
||||
|
||||
public Vector2 LastSnappedPosition { get; private set; }
|
||||
|
||||
private readonly Drawable cursor;
|
||||
|
||||
private InputManager inputManager;
|
||||
@ -210,7 +251,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
cursor.Position = GetSnapPosition.Invoke(inputManager.CurrentState.Mouse.Position);
|
||||
cursor.Position = LastSnappedPosition = GetSnapPosition.Invoke(inputManager.CurrentState.Mouse.Position);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -20,20 +20,49 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
protected override Ruleset CreateEditorRuleset() => new OsuRuleset();
|
||||
|
||||
[Test]
|
||||
public void TestGridExclusivity()
|
||||
public void TestGridToggles()
|
||||
{
|
||||
AddStep("enable distance snap grid", () => InputManager.Key(Key.T));
|
||||
AddStep("select second object", () => EditorBeatmap.SelectedHitObjects.Add(EditorBeatmap.HitObjects.ElementAt(1)));
|
||||
|
||||
AddUntilStep("distance snap grid visible", () => this.ChildrenOfType<OsuDistanceSnapGrid>().Any());
|
||||
rectangularGridActive(false);
|
||||
|
||||
AddStep("enable rectangular grid", () => InputManager.Key(Key.Y));
|
||||
AddUntilStep("distance snap grid hidden", () => !this.ChildrenOfType<OsuDistanceSnapGrid>().Any());
|
||||
|
||||
AddStep("select second object", () => EditorBeatmap.SelectedHitObjects.Add(EditorBeatmap.HitObjects.ElementAt(1)));
|
||||
AddUntilStep("distance snap grid still visible", () => this.ChildrenOfType<OsuDistanceSnapGrid>().Any());
|
||||
rectangularGridActive(true);
|
||||
|
||||
AddStep("enable distance snap grid", () => InputManager.Key(Key.T));
|
||||
AddStep("disable distance snap grid", () => InputManager.Key(Key.T));
|
||||
AddUntilStep("distance snap grid hidden", () => !this.ChildrenOfType<OsuDistanceSnapGrid>().Any());
|
||||
AddStep("select second object", () => EditorBeatmap.SelectedHitObjects.Add(EditorBeatmap.HitObjects.ElementAt(1)));
|
||||
rectangularGridActive(true);
|
||||
|
||||
AddStep("disable rectangular grid", () => InputManager.Key(Key.Y));
|
||||
AddUntilStep("distance snap grid still hidden", () => !this.ChildrenOfType<OsuDistanceSnapGrid>().Any());
|
||||
rectangularGridActive(false);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestDistanceSnapMomentaryToggle()
|
||||
{
|
||||
AddStep("select second object", () => EditorBeatmap.SelectedHitObjects.Add(EditorBeatmap.HitObjects.ElementAt(1)));
|
||||
|
||||
AddUntilStep("distance snap grid hidden", () => !this.ChildrenOfType<OsuDistanceSnapGrid>().Any());
|
||||
AddStep("hold alt", () => InputManager.PressKey(Key.AltLeft));
|
||||
AddUntilStep("distance snap grid visible", () => this.ChildrenOfType<OsuDistanceSnapGrid>().Any());
|
||||
AddStep("release alt", () => InputManager.ReleaseKey(Key.AltLeft));
|
||||
AddUntilStep("distance snap grid hidden", () => !this.ChildrenOfType<OsuDistanceSnapGrid>().Any());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestGridSnapMomentaryToggle()
|
||||
{
|
||||
rectangularGridActive(false);
|
||||
AddStep("hold shift", () => InputManager.PressKey(Key.ShiftLeft));
|
||||
rectangularGridActive(true);
|
||||
AddStep("release shift", () => InputManager.ReleaseKey(Key.ShiftLeft));
|
||||
rectangularGridActive(false);
|
||||
}
|
||||
|
||||
@ -50,8 +79,6 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
AddAssert("placement blueprint at (0, 0)", () => Precision.AlmostEquals(Editor.ChildrenOfType<HitCirclePlacementBlueprint>().Single().HitObject.Position, new Vector2(0, 0)));
|
||||
else
|
||||
AddAssert("placement blueprint at (1, 1)", () => Precision.AlmostEquals(Editor.ChildrenOfType<HitCirclePlacementBlueprint>().Single().HitObject.Position, new Vector2(1, 1)));
|
||||
|
||||
AddStep("choose selection tool", () => InputManager.Key(Key.Number1));
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
@ -0,0 +1,25 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using NUnit.Framework;
|
||||
using osu.Game.Rulesets.Osu.Mods;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Tests.Mods
|
||||
{
|
||||
public class TestSceneOsuModFlashlight : OsuModTestScene
|
||||
{
|
||||
[TestCase(600)]
|
||||
[TestCase(120)]
|
||||
[TestCase(1200)]
|
||||
public void TestFollowDelay(double followDelay) => CreateModTest(new ModTestData { Mod = new OsuModFlashlight { FollowDelay = { Value = followDelay } }, PassCondition = () => true });
|
||||
|
||||
[TestCase(1f)]
|
||||
[TestCase(0.5f)]
|
||||
[TestCase(1.5f)]
|
||||
[TestCase(2f)]
|
||||
public void TestSizeMultiplier(float sizeMultiplier) => CreateModTest(new ModTestData { Mod = new OsuModFlashlight { SizeMultiplier = { Value = sizeMultiplier } }, PassCondition = () => true });
|
||||
|
||||
[Test]
|
||||
public void TestComboBasedSize([Values] bool comboBasedSize) => CreateModTest(new ModTestData { Mod = new OsuModFlashlight { ComboBasedSize = { Value = comboBasedSize } }, PassCondition = () => true });
|
||||
}
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using NUnit.Framework;
|
||||
using osu.Game.Rulesets.Osu.Mods;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Tests.Mods
|
||||
{
|
||||
public class TestSceneOsuModFreezeFrame : OsuModTestScene
|
||||
{
|
||||
[Test]
|
||||
public void TestFreezeFrame()
|
||||
{
|
||||
CreateModTest(new ModTestData
|
||||
{
|
||||
Mod = new OsuModFreezeFrame(),
|
||||
PassCondition = () => true,
|
||||
Autoplay = false,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@ -4,6 +4,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Extensions.ObjectExtensions;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Beatmaps;
|
||||
@ -12,6 +13,7 @@ using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Osu.Mods;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Osu.UI;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Tests.Mods
|
||||
@ -145,6 +147,10 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods
|
||||
|
||||
private bool isBreak() => Player.IsBreakTime.Value;
|
||||
|
||||
private bool cursorAlphaAlmostEquals(float alpha) => Precision.AlmostEquals(Player.DrawableRuleset.Cursor.Alpha, alpha, 0.1f);
|
||||
private OsuPlayfield playfield => (OsuPlayfield)Player.DrawableRuleset.Playfield;
|
||||
|
||||
private bool cursorAlphaAlmostEquals(float alpha) =>
|
||||
Precision.AlmostEquals(playfield.Cursor.AsNonNull().Alpha, alpha, 0.1f) &&
|
||||
Precision.AlmostEquals(playfield.Smoke.Alpha, alpha, 0.1f);
|
||||
}
|
||||
}
|
||||
|
@ -29,7 +29,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
new object[] { LegacyMods.Autoplay, new[] { typeof(OsuModAutoplay) } },
|
||||
new object[] { LegacyMods.SpunOut, new[] { typeof(OsuModSpunOut) } },
|
||||
new object[] { LegacyMods.Autopilot, new[] { typeof(OsuModAutopilot) } },
|
||||
new object[] { LegacyMods.Target, new[] { typeof(OsuModTarget) } },
|
||||
new object[] { LegacyMods.Target, new[] { typeof(OsuModTargetPractice) } },
|
||||
new object[] { LegacyMods.HardRock | LegacyMods.DoubleTime, new[] { typeof(OsuModHardRock), typeof(OsuModDoubleTime) } }
|
||||
};
|
||||
|
||||
|
@ -377,7 +377,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
private void addJudgementAssert(OsuHitObject hitObject, HitResult result)
|
||||
{
|
||||
AddAssert($"({hitObject.GetType().ReadableName()} @ {hitObject.StartTime}) judgement is {result}",
|
||||
() => judgementResults.Single(r => r.HitObject == hitObject).Type == result);
|
||||
() => judgementResults.Single(r => r.HitObject == hitObject).Type, () => Is.EqualTo(result));
|
||||
}
|
||||
|
||||
private void addJudgementAssert(string name, Func<OsuHitObject> hitObject, HitResult result)
|
||||
|
@ -1,21 +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.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Osu.Mods;
|
||||
using osu.Game.Tests.Visual;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Tests
|
||||
{
|
||||
public class TestSceneOsuFlashlight : TestSceneOsuPlayer
|
||||
{
|
||||
protected override TestPlayer CreatePlayer(Ruleset ruleset)
|
||||
{
|
||||
SelectedMods.Value = new Mod[] { new OsuModAutoplay(), new OsuModFlashlight(), };
|
||||
|
||||
return base.CreatePlayer(ruleset);
|
||||
}
|
||||
}
|
||||
}
|
@ -14,6 +14,7 @@ using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Osu.Skinning.Legacy;
|
||||
using osu.Game.Skinning;
|
||||
using osu.Game.Tests.Visual;
|
||||
using osuTK;
|
||||
@ -68,10 +69,8 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
|
||||
AddStep("create slider", () =>
|
||||
{
|
||||
var tintingSkin = skinManager.GetSkin(DefaultLegacySkin.CreateInfo());
|
||||
tintingSkin.Configuration.ConfigDictionary["AllowSliderBallTint"] = "1";
|
||||
|
||||
var provider = Ruleset.Value.CreateInstance().CreateSkinTransformer(tintingSkin, Beatmap.Value.Beatmap);
|
||||
var skin = skinManager.GetSkin(DefaultLegacySkin.CreateInfo());
|
||||
var provider = Ruleset.Value.CreateInstance().CreateSkinTransformer(skin, Beatmap.Value.Beatmap);
|
||||
|
||||
Child = new SkinProvidingContainer(provider)
|
||||
{
|
||||
@ -92,10 +91,10 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
});
|
||||
|
||||
AddStep("set accent white", () => dho.AccentColour.Value = Color4.White);
|
||||
AddAssert("ball is white", () => dho.ChildrenOfType<DrawableSliderBall>().Single().AccentColour == Color4.White);
|
||||
AddAssert("ball is white", () => dho.ChildrenOfType<LegacySliderBall>().Single().BallColour == Color4.White);
|
||||
|
||||
AddStep("set accent red", () => dho.AccentColour.Value = Color4.Red);
|
||||
AddAssert("ball is red", () => dho.ChildrenOfType<DrawableSliderBall>().Single().AccentColour == Color4.Red);
|
||||
AddAssert("ball is red", () => dho.ChildrenOfType<LegacySliderBall>().Single().BallColour == Color4.Red);
|
||||
}
|
||||
|
||||
private Slider prepareObject(Slider slider)
|
||||
|
@ -40,16 +40,23 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
||||
body.BorderColour = colours.Yellow;
|
||||
}
|
||||
|
||||
private int? lastVersion;
|
||||
|
||||
public override void UpdateFrom(Slider hitObject)
|
||||
{
|
||||
base.UpdateFrom(hitObject);
|
||||
|
||||
body.PathRadius = hitObject.Scale * OsuHitObject.OBJECT_RADIUS;
|
||||
|
||||
var vertices = new List<Vector2>();
|
||||
hitObject.Path.GetPathToProgress(vertices, 0, 1);
|
||||
if (lastVersion != hitObject.Path.Version.Value)
|
||||
{
|
||||
lastVersion = hitObject.Path.Version.Value;
|
||||
|
||||
body.SetVertices(vertices);
|
||||
var vertices = new List<Vector2>();
|
||||
hitObject.Path.GetPathToProgress(vertices, 0, 1);
|
||||
|
||||
body.SetVertices(vertices);
|
||||
}
|
||||
|
||||
Size = body.Size;
|
||||
OriginPosition = body.PathOffset;
|
||||
|
@ -59,6 +59,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
||||
|
||||
private readonly BindableList<PathControlPoint> controlPoints = new BindableList<PathControlPoint>();
|
||||
private readonly IBindable<int> pathVersion = new Bindable<int>();
|
||||
private readonly BindableList<HitObject> selectedObjects = new BindableList<HitObject>();
|
||||
|
||||
public SliderSelectionBlueprint(Slider slider)
|
||||
: base(slider)
|
||||
@ -86,6 +87,10 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
||||
pathVersion.BindValueChanged(_ => editorBeatmap?.Update(HitObject));
|
||||
|
||||
BodyPiece.UpdateFrom(HitObject);
|
||||
|
||||
if (editorBeatmap != null)
|
||||
selectedObjects.BindTo(editorBeatmap.SelectedHitObjects);
|
||||
selectedObjects.BindCollectionChanged((_, _) => updateVisualDefinition(), true);
|
||||
}
|
||||
|
||||
public override bool HandleQuickDeletion()
|
||||
@ -100,6 +105,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool hasSingleObjectSelected => selectedObjects.Count == 1;
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
@ -108,14 +115,25 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
||||
BodyPiece.UpdateFrom(HitObject);
|
||||
}
|
||||
|
||||
protected override bool OnHover(HoverEvent e)
|
||||
{
|
||||
updateVisualDefinition();
|
||||
|
||||
// In the case more than a single object is selected, block hover from arriving at sliders behind this one.
|
||||
// Without doing this, the path visualisers of potentially hundreds of sliders will render, which is not only
|
||||
// visually noisy but also functionally useless.
|
||||
return !hasSingleObjectSelected;
|
||||
}
|
||||
|
||||
protected override void OnHoverLost(HoverLostEvent e)
|
||||
{
|
||||
updateVisualDefinition();
|
||||
base.OnHoverLost(e);
|
||||
}
|
||||
|
||||
protected override void OnSelected()
|
||||
{
|
||||
AddInternal(ControlPointVisualiser = new PathControlPointVisualiser(HitObject, true)
|
||||
{
|
||||
RemoveControlPointsRequested = removeControlPoints,
|
||||
SplitControlPointsRequested = splitControlPoints
|
||||
});
|
||||
|
||||
updateVisualDefinition();
|
||||
base.OnSelected();
|
||||
}
|
||||
|
||||
@ -123,13 +141,31 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
||||
{
|
||||
base.OnDeselected();
|
||||
|
||||
// throw away frame buffers on deselection.
|
||||
ControlPointVisualiser?.Expire();
|
||||
ControlPointVisualiser = null;
|
||||
|
||||
updateVisualDefinition();
|
||||
BodyPiece.RecyclePath();
|
||||
}
|
||||
|
||||
private void updateVisualDefinition()
|
||||
{
|
||||
// To reduce overhead of drawing these blueprints, only add extra detail when hovered or when only this slider is selected.
|
||||
if (IsSelected && (hasSingleObjectSelected || IsHovered))
|
||||
{
|
||||
if (ControlPointVisualiser == null)
|
||||
{
|
||||
AddInternal(ControlPointVisualiser = new PathControlPointVisualiser(HitObject, true)
|
||||
{
|
||||
RemoveControlPointsRequested = removeControlPoints,
|
||||
SplitControlPointsRequested = splitControlPoints
|
||||
});
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ControlPointVisualiser?.Expire();
|
||||
ControlPointVisualiser = null;
|
||||
}
|
||||
}
|
||||
|
||||
private Vector2 rightClickPosition;
|
||||
|
||||
protected override bool OnMouseDown(MouseDownEvent e)
|
||||
|
@ -5,19 +5,17 @@
|
||||
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Rulesets.Osu.Skinning.Default;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners.Components
|
||||
{
|
||||
public class SpinnerPiece : BlueprintPiece<Spinner>
|
||||
{
|
||||
private readonly CircularContainer circle;
|
||||
private readonly RingPiece ring;
|
||||
private readonly Circle circle;
|
||||
private readonly Circle ring;
|
||||
|
||||
public SpinnerPiece()
|
||||
{
|
||||
@ -25,18 +23,21 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners.Components
|
||||
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
FillMode = FillMode.Fit;
|
||||
Size = new Vector2(1.3f);
|
||||
Size = new Vector2(1);
|
||||
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
circle = new CircularContainer
|
||||
circle = new Circle
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Masking = true,
|
||||
Alpha = 0.5f,
|
||||
Child = new Box { RelativeSizeAxes = Axes.Both }
|
||||
},
|
||||
ring = new RingPiece()
|
||||
ring = new Circle
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Size = new Vector2(OsuHitObject.OBJECT_RADIUS),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -13,8 +13,11 @@ using osu.Framework.Extensions.EnumExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Input.Bindings;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
using osu.Game.Rulesets.Edit.Tools;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
@ -44,12 +47,10 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
new SpinnerCompositionTool()
|
||||
};
|
||||
|
||||
private readonly Bindable<TernaryState> distanceSnapToggle = new Bindable<TernaryState>();
|
||||
private readonly Bindable<TernaryState> rectangularGridSnapToggle = new Bindable<TernaryState>();
|
||||
|
||||
protected override IEnumerable<TernaryButton> CreateTernaryButtons() => base.CreateTernaryButtons().Concat(new[]
|
||||
{
|
||||
new TernaryButton(distanceSnapToggle, "Distance Snap", () => new SpriteIcon { Icon = FontAwesome.Solid.Ruler }),
|
||||
new TernaryButton(rectangularGridSnapToggle, "Grid Snap", () => new SpriteIcon { Icon = FontAwesome.Solid.Th })
|
||||
});
|
||||
|
||||
@ -60,6 +61,9 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
// Give a bit of breathing room around the playfield content.
|
||||
PlayfieldContentContainer.Padding = new MarginPadding(10);
|
||||
|
||||
LayerBelowRuleset.AddRange(new Drawable[]
|
||||
{
|
||||
distanceSnapGridContainer = new Container
|
||||
@ -77,19 +81,7 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
|
||||
placementObject = EditorBeatmap.PlacementObject.GetBoundCopy();
|
||||
placementObject.ValueChanged += _ => updateDistanceSnapGrid();
|
||||
distanceSnapToggle.ValueChanged += _ =>
|
||||
{
|
||||
updateDistanceSnapGrid();
|
||||
|
||||
if (distanceSnapToggle.Value == TernaryState.True)
|
||||
rectangularGridSnapToggle.Value = TernaryState.False;
|
||||
};
|
||||
|
||||
rectangularGridSnapToggle.ValueChanged += _ =>
|
||||
{
|
||||
if (rectangularGridSnapToggle.Value == TernaryState.True)
|
||||
distanceSnapToggle.Value = TernaryState.False;
|
||||
};
|
||||
DistanceSnapToggle.ValueChanged += _ => updateDistanceSnapGrid();
|
||||
|
||||
// we may be entering the screen with a selection already active
|
||||
updateDistanceSnapGrid();
|
||||
@ -109,6 +101,14 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
|
||||
private RectangularPositionSnapGrid rectangularPositionSnapGrid;
|
||||
|
||||
protected override double ReadCurrentDistanceSnap(HitObject before, HitObject after)
|
||||
{
|
||||
float expectedDistance = DurationToDistance(before, after.StartTime - before.GetEndTime());
|
||||
float actualDistance = Vector2.Distance(((OsuHitObject)before).EndPosition, ((OsuHitObject)after).Position);
|
||||
|
||||
return actualDistance / expectedDistance;
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
@ -129,24 +129,46 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
public override SnapResult FindSnappedPositionAndTime(Vector2 screenSpacePosition, SnapType snapType = SnapType.All)
|
||||
{
|
||||
if (snapType.HasFlagFast(SnapType.NearbyObjects) && snapToVisibleBlueprints(screenSpacePosition, out var snapResult))
|
||||
{
|
||||
// In the case of snapping to nearby objects, a time value is not provided.
|
||||
// This matches the stable editor (which also uses current time), but with the introduction of time-snapping distance snap
|
||||
// this could result in unexpected behaviour when distance snapping is turned on and a user attempts to place an object that is
|
||||
// BOTH on a valid distance snap ring, and also at the same position as a previous object.
|
||||
//
|
||||
// We want to ensure that in this particular case, the time-snapping component of distance snap is still applied.
|
||||
// The easiest way to ensure this is to attempt application of distance snap after a nearby object is found, and copy over
|
||||
// the time value if the proposed positions are roughly the same.
|
||||
if (snapType.HasFlagFast(SnapType.Grids) && DistanceSnapToggle.Value == TernaryState.True && distanceSnapGrid != null)
|
||||
{
|
||||
(Vector2 distanceSnappedPosition, double distanceSnappedTime) = distanceSnapGrid.GetSnappedPosition(distanceSnapGrid.ToLocalSpace(snapResult.ScreenSpacePosition));
|
||||
if (Precision.AlmostEquals(distanceSnapGrid.ToScreenSpace(distanceSnappedPosition), snapResult.ScreenSpacePosition, 1))
|
||||
snapResult.Time = distanceSnappedTime;
|
||||
}
|
||||
|
||||
return snapResult;
|
||||
}
|
||||
|
||||
SnapResult result = base.FindSnappedPositionAndTime(screenSpacePosition, snapType);
|
||||
|
||||
if (snapType.HasFlagFast(SnapType.Grids))
|
||||
{
|
||||
if (distanceSnapToggle.Value == TernaryState.True && distanceSnapGrid != null)
|
||||
if (DistanceSnapToggle.Value == TernaryState.True && distanceSnapGrid != null)
|
||||
{
|
||||
(Vector2 pos, double time) = distanceSnapGrid.GetSnappedPosition(distanceSnapGrid.ToLocalSpace(screenSpacePosition));
|
||||
return new SnapResult(distanceSnapGrid.ToScreenSpace(pos), time, PlayfieldAtScreenSpacePosition(screenSpacePosition));
|
||||
|
||||
result.ScreenSpacePosition = distanceSnapGrid.ToScreenSpace(pos);
|
||||
result.Time = time;
|
||||
}
|
||||
|
||||
if (rectangularGridSnapToggle.Value == TernaryState.True)
|
||||
{
|
||||
Vector2 pos = rectangularPositionSnapGrid.GetSnappedPosition(rectangularPositionSnapGrid.ToLocalSpace(screenSpacePosition));
|
||||
return new SnapResult(rectangularPositionSnapGrid.ToScreenSpace(pos), null, PlayfieldAtScreenSpacePosition(screenSpacePosition));
|
||||
Vector2 pos = rectangularPositionSnapGrid.GetSnappedPosition(rectangularPositionSnapGrid.ToLocalSpace(result.ScreenSpacePosition));
|
||||
|
||||
result.ScreenSpacePosition = rectangularPositionSnapGrid.ToScreenSpace(pos);
|
||||
}
|
||||
}
|
||||
|
||||
return base.FindSnappedPositionAndTime(screenSpacePosition, snapType);
|
||||
return result;
|
||||
}
|
||||
|
||||
private bool snapToVisibleBlueprints(Vector2 screenSpacePosition, out SnapResult snapResult)
|
||||
@ -199,7 +221,7 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
distanceSnapGridCache.Invalidate();
|
||||
distanceSnapGrid = null;
|
||||
|
||||
if (distanceSnapToggle.Value != TernaryState.True)
|
||||
if (DistanceSnapToggle.Value != TernaryState.True)
|
||||
return;
|
||||
|
||||
switch (BlueprintContainer.CurrentTool)
|
||||
@ -226,6 +248,42 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
}
|
||||
}
|
||||
|
||||
protected override bool OnKeyDown(KeyDownEvent e)
|
||||
{
|
||||
if (e.Repeat)
|
||||
return false;
|
||||
|
||||
handleToggleViaKey(e);
|
||||
return base.OnKeyDown(e);
|
||||
}
|
||||
|
||||
protected override void OnKeyUp(KeyUpEvent e)
|
||||
{
|
||||
handleToggleViaKey(e);
|
||||
base.OnKeyUp(e);
|
||||
}
|
||||
|
||||
protected override bool AdjustDistanceSpacing(GlobalAction action, float amount)
|
||||
{
|
||||
// To allow better visualisation, ensure that the spacing grid is visible before adjusting.
|
||||
DistanceSnapToggle.Value = TernaryState.True;
|
||||
|
||||
return base.AdjustDistanceSpacing(action, amount);
|
||||
}
|
||||
|
||||
private bool gridSnapMomentary;
|
||||
|
||||
private void handleToggleViaKey(KeyboardEvent key)
|
||||
{
|
||||
bool shiftPressed = key.ShiftPressed;
|
||||
|
||||
if (shiftPressed != gridSnapMomentary)
|
||||
{
|
||||
gridSnapMomentary = shiftPressed;
|
||||
rectangularGridSnapToggle.Value = rectangularGridSnapToggle.Value == TernaryState.False ? TernaryState.True : TernaryState.False;
|
||||
}
|
||||
}
|
||||
|
||||
private DistanceSnapGrid createDistanceSnapGrid(IEnumerable<HitObject> selectedHitObjects)
|
||||
{
|
||||
if (BlueprintContainer.CurrentTool is SpinnerCompositionTool)
|
||||
|
@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
public override double ScoreMultiplier => 1;
|
||||
public override IconUsage? Icon { get; } = FontAwesome.Regular.Circle;
|
||||
|
||||
public override Type[] IncompatibleMods => new[] { typeof(IHidesApproachCircles) };
|
||||
public override Type[] IncompatibleMods => new[] { typeof(IHidesApproachCircles), typeof(OsuModFreezeFrame) };
|
||||
|
||||
[SettingSource("Initial size", "Change the initial size of the approach circle, relative to hit circles.", 0)]
|
||||
public BindableFloat Scale { get; } = new BindableFloat(4)
|
||||
|
89
osu.Game.Rulesets.Osu/Mods/OsuModFreezeFrame.cs
Normal file
89
osu.Game.Rulesets.Osu/Mods/OsuModFreezeFrame.cs
Normal file
@ -0,0 +1,89 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Mods
|
||||
{
|
||||
public class OsuModFreezeFrame : Mod, IApplicableToDrawableHitObject, IApplicableToBeatmap
|
||||
{
|
||||
public override string Name => "Freeze Frame";
|
||||
|
||||
public override string Acronym => "FR";
|
||||
|
||||
public override double ScoreMultiplier => 1;
|
||||
|
||||
public override LocalisableString Description => "Burn the notes into your memory.";
|
||||
|
||||
//Alters the transforms of the approach circles, breaking the effects of these mods.
|
||||
public override Type[] IncompatibleMods => new[] { typeof(OsuModApproachDifferent) };
|
||||
|
||||
public override ModType Type => ModType.Fun;
|
||||
|
||||
//mod breaks normal approach circle preempt
|
||||
private double originalPreempt;
|
||||
|
||||
public void ApplyToBeatmap(IBeatmap beatmap)
|
||||
{
|
||||
var firstHitObject = beatmap.HitObjects.OfType<OsuHitObject>().FirstOrDefault();
|
||||
if (firstHitObject == null)
|
||||
return;
|
||||
|
||||
double lastNewComboTime = 0;
|
||||
|
||||
originalPreempt = firstHitObject.TimePreempt;
|
||||
|
||||
foreach (var obj in beatmap.HitObjects.OfType<OsuHitObject>())
|
||||
{
|
||||
if (obj.NewCombo) { lastNewComboTime = obj.StartTime; }
|
||||
|
||||
applyFadeInAdjustment(obj);
|
||||
}
|
||||
|
||||
void applyFadeInAdjustment(OsuHitObject osuObject)
|
||||
{
|
||||
osuObject.TimePreempt += osuObject.StartTime - lastNewComboTime;
|
||||
|
||||
foreach (var nested in osuObject.NestedHitObjects.OfType<OsuHitObject>())
|
||||
{
|
||||
switch (nested)
|
||||
{
|
||||
//SliderRepeat wont layer correctly if preempt is changed.
|
||||
case SliderRepeat:
|
||||
break;
|
||||
|
||||
default:
|
||||
applyFadeInAdjustment(nested);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void ApplyToDrawableHitObject(DrawableHitObject drawableObject)
|
||||
{
|
||||
drawableObject.ApplyCustomUpdateState += (drawableHitObject, _) =>
|
||||
{
|
||||
if (drawableHitObject is not DrawableHitCircle drawableHitCircle) return;
|
||||
|
||||
var hitCircle = drawableHitCircle.HitObject;
|
||||
var approachCircle = drawableHitCircle.ApproachCircle;
|
||||
|
||||
// Reapply scale, ensuring the AR isn't changed due to the new preempt.
|
||||
approachCircle.ClearTransforms(targetMember: nameof(approachCircle.Scale));
|
||||
approachCircle.ScaleTo(4 * (float)(hitCircle.TimePreempt / originalPreempt));
|
||||
|
||||
using (drawableHitCircle.ApproachCircle.BeginAbsoluteSequence(hitCircle.StartTime - hitCircle.TimePreempt))
|
||||
approachCircle.ScaleTo(1, hitCircle.TimePreempt).Then().Expire();
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
@ -3,6 +3,7 @@
|
||||
|
||||
using System;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.ObjectExtensions;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Framework.Timing;
|
||||
@ -46,7 +47,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
|
||||
public void Update(Playfield playfield)
|
||||
{
|
||||
var cursorPos = playfield.Cursor.ActiveCursor.DrawPosition;
|
||||
var cursorPos = playfield.Cursor.AsNonNull().ActiveCursor.DrawPosition;
|
||||
|
||||
foreach (var drawable in playfield.HitObjectContainer.AliveObjects)
|
||||
{
|
||||
|
@ -2,6 +2,7 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Localisation;
|
||||
@ -9,6 +10,7 @@ using osu.Framework.Utils;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Rulesets.Osu.UI;
|
||||
using osu.Game.Rulesets.UI;
|
||||
using osu.Game.Utils;
|
||||
|
||||
@ -33,9 +35,15 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
|
||||
public void Update(Playfield playfield)
|
||||
{
|
||||
bool shouldAlwaysShowCursor = IsBreakTime.Value || spinnerPeriods.IsInAny(playfield.Clock.CurrentTime);
|
||||
var osuPlayfield = (OsuPlayfield)playfield;
|
||||
Debug.Assert(osuPlayfield.Cursor != null);
|
||||
|
||||
bool shouldAlwaysShowCursor = IsBreakTime.Value || spinnerPeriods.IsInAny(osuPlayfield.Clock.CurrentTime);
|
||||
float targetAlpha = shouldAlwaysShowCursor ? 1 : ComboBasedAlpha;
|
||||
playfield.Cursor.Alpha = (float)Interpolation.Lerp(playfield.Cursor.Alpha, targetAlpha, Math.Clamp(playfield.Time.Elapsed / TRANSITION_DURATION, 0, 1));
|
||||
float currentAlpha = (float)Interpolation.Lerp(osuPlayfield.Cursor.Alpha, targetAlpha, Math.Clamp(osuPlayfield.Time.Elapsed / TRANSITION_DURATION, 0, 1));
|
||||
|
||||
osuPlayfield.Cursor.Alpha = currentAlpha;
|
||||
osuPlayfield.Smoke.Alpha = currentAlpha;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -26,7 +26,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
{
|
||||
public override LocalisableString Description => "It never gets boring!";
|
||||
|
||||
public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(OsuModTarget)).ToArray();
|
||||
public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(OsuModTargetPractice)).ToArray();
|
||||
|
||||
[SettingSource("Angle sharpness", "How sharp angles should be", SettingControlType = typeof(SettingsSlider<float>))]
|
||||
public BindableFloat AngleSharpness { get; } = new BindableFloat(7)
|
||||
|
@ -3,6 +3,7 @@
|
||||
|
||||
using System;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.ObjectExtensions;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Framework.Timing;
|
||||
using osu.Framework.Utils;
|
||||
@ -45,7 +46,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
|
||||
public void Update(Playfield playfield)
|
||||
{
|
||||
var cursorPos = playfield.Cursor.ActiveCursor.DrawPosition;
|
||||
var cursorPos = playfield.Cursor.AsNonNull().ActiveCursor.DrawPosition;
|
||||
|
||||
foreach (var drawable in playfield.HitObjectContainer.AliveObjects)
|
||||
{
|
||||
|
@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
public override ModType Type => ModType.Automation;
|
||||
public override LocalisableString Description => @"Spinners will be automatically completed.";
|
||||
public override double ScoreMultiplier => 0.9;
|
||||
public override Type[] IncompatibleMods => new[] { typeof(ModAutoplay), typeof(OsuModAutopilot), typeof(OsuModTarget) };
|
||||
public override Type[] IncompatibleMods => new[] { typeof(ModAutoplay), typeof(OsuModAutopilot), typeof(OsuModTargetPractice) };
|
||||
|
||||
public void ApplyToDrawableHitObject(DrawableHitObject hitObject)
|
||||
{
|
||||
|
@ -26,7 +26,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
public override ModType Type => ModType.DifficultyIncrease;
|
||||
public override LocalisableString Description => @"Once you start a slider, follow precisely or get a miss.";
|
||||
public override double ScoreMultiplier => 1.0;
|
||||
public override Type[] IncompatibleMods => new[] { typeof(ModClassic), typeof(OsuModTarget) };
|
||||
public override Type[] IncompatibleMods => new[] { typeof(ModClassic), typeof(OsuModTargetPractice) };
|
||||
|
||||
public void ApplyToDrawableHitObject(DrawableHitObject drawable)
|
||||
{
|
||||
|
@ -12,7 +12,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[]
|
||||
{
|
||||
typeof(OsuModAutopilot),
|
||||
typeof(OsuModTarget),
|
||||
typeof(OsuModTargetPractice),
|
||||
}).ToArray();
|
||||
}
|
||||
}
|
||||
|
@ -32,16 +32,15 @@ using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Mods
|
||||
{
|
||||
public class OsuModTarget : ModWithVisibilityAdjustment, IApplicableToDrawableRuleset<OsuHitObject>,
|
||||
IApplicableToHealthProcessor, IApplicableToDifficulty, IApplicableFailOverride,
|
||||
IHasSeed, IHidesApproachCircles
|
||||
public class OsuModTargetPractice : ModWithVisibilityAdjustment, IApplicableToDrawableRuleset<OsuHitObject>,
|
||||
IApplicableToHealthProcessor, IApplicableToDifficulty, IApplicableFailOverride, IHasSeed, IHidesApproachCircles
|
||||
{
|
||||
public override string Name => "Target";
|
||||
public override string Name => "Target Practice";
|
||||
public override string Acronym => "TP";
|
||||
public override ModType Type => ModType.Conversion;
|
||||
public override IconUsage? Icon => OsuIcon.ModTarget;
|
||||
public override LocalisableString Description => @"Practice keeping up with the beat of the song.";
|
||||
public override double ScoreMultiplier => 1;
|
||||
public override double ScoreMultiplier => 0.1;
|
||||
|
||||
public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[]
|
||||
{
|
@ -102,8 +102,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
|
||||
Size = HitArea.DrawSize;
|
||||
|
||||
PositionBindable.BindValueChanged(_ => Position = HitObject.StackedPosition);
|
||||
StackHeightBindable.BindValueChanged(_ => Position = HitObject.StackedPosition);
|
||||
PositionBindable.BindValueChanged(_ => UpdatePosition());
|
||||
StackHeightBindable.BindValueChanged(_ => UpdatePosition());
|
||||
ScaleBindable.BindValueChanged(scale => scaleContainer.Scale = new Vector2(scale.NewValue));
|
||||
}
|
||||
|
||||
@ -134,6 +134,11 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void UpdatePosition()
|
||||
{
|
||||
Position = HitObject.StackedPosition;
|
||||
}
|
||||
|
||||
public override void Shake() => shakeContainer.Shake();
|
||||
|
||||
protected override void CheckForResult(bool userTriggered, double timeOffset)
|
||||
|
@ -14,12 +14,10 @@ using osu.Game.Audio;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Osu.Skinning;
|
||||
using osu.Game.Rulesets.Osu.Skinning.Default;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Skinning;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
{
|
||||
@ -106,7 +104,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
{
|
||||
foreach (var drawableHitObject in NestedHitObjects)
|
||||
drawableHitObject.AccentColour.Value = colour.NewValue;
|
||||
updateBallTint();
|
||||
}, true);
|
||||
|
||||
Tracking.BindValueChanged(updateSlidingSample);
|
||||
@ -257,22 +254,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
SliderBody?.RecyclePath();
|
||||
}
|
||||
|
||||
protected override void ApplySkin(ISkinSource skin, bool allowFallback)
|
||||
{
|
||||
base.ApplySkin(skin, allowFallback);
|
||||
|
||||
updateBallTint();
|
||||
}
|
||||
|
||||
private void updateBallTint()
|
||||
{
|
||||
if (CurrentSkin == null)
|
||||
return;
|
||||
|
||||
bool allowBallTint = CurrentSkin.GetConfig<OsuSkinConfiguration, bool>(OsuSkinConfiguration.AllowSliderBallTint)?.Value ?? false;
|
||||
Ball.AccentColour = allowBallTint ? AccentColour.Value : Color4.White;
|
||||
}
|
||||
|
||||
protected override void CheckForResult(bool userTriggered, double timeOffset)
|
||||
{
|
||||
if (userTriggered || Time.Current < HitObject.EndTime)
|
||||
@ -331,7 +312,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
{
|
||||
base.UpdateHitStateTransforms(state);
|
||||
|
||||
const float fade_out_time = 450;
|
||||
const float fade_out_time = 240;
|
||||
|
||||
switch (state)
|
||||
{
|
||||
@ -341,7 +322,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
break;
|
||||
}
|
||||
|
||||
this.FadeOut(fade_out_time, Easing.OutQuint).Expire();
|
||||
this.FadeOut(fade_out_time).Expire();
|
||||
}
|
||||
|
||||
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => SliderBody?.ReceivePositionalInputAt(screenSpacePos) ?? base.ReceivePositionalInputAt(screenSpacePos);
|
||||
|
@ -11,28 +11,20 @@ using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Input;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
using osu.Game.Rulesets.Osu.Skinning.Default;
|
||||
using osu.Game.Skinning;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
{
|
||||
public class DrawableSliderBall : CircularContainer, ISliderProgress, IRequireHighFrequencyMousePosition, IHasAccentColour
|
||||
public class DrawableSliderBall : CircularContainer, ISliderProgress, IRequireHighFrequencyMousePosition
|
||||
{
|
||||
public const float FOLLOW_AREA = 2.4f;
|
||||
|
||||
public Func<OsuAction?> GetInitialHitAction;
|
||||
|
||||
public Color4 AccentColour
|
||||
{
|
||||
get => ball.Colour;
|
||||
set => ball.Colour = value;
|
||||
}
|
||||
|
||||
private Drawable followCircleReceptor;
|
||||
private DrawableSlider drawableSlider;
|
||||
private Drawable ball;
|
||||
@ -87,7 +79,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
public override void ApplyTransformsAt(double time, bool propagateChildren = false)
|
||||
{
|
||||
// For the same reasons as above w.r.t rewinding, we shouldn't propagate to children here either.
|
||||
// ReSharper disable once RedundantArgumentDefaultValue - removing the "redundant" default value triggers BaseMethodCallWithDefaultParameter
|
||||
|
||||
// ReSharper disable once RedundantArgumentDefaultValue
|
||||
base.ApplyTransformsAt(time, false);
|
||||
}
|
||||
|
||||
|
@ -6,7 +6,6 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using JetBrains.Annotations;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
@ -43,13 +42,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
{
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
PositionBindable.BindValueChanged(_ => updatePosition());
|
||||
pathVersion.BindValueChanged(_ => updatePosition());
|
||||
}
|
||||
|
||||
protected override void OnFree()
|
||||
{
|
||||
base.OnFree();
|
||||
@ -57,6 +49,11 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
pathVersion.UnbindFrom(DrawableSlider.PathVersion);
|
||||
}
|
||||
|
||||
protected override void UpdatePosition()
|
||||
{
|
||||
// Slider head is always drawn at (0,0).
|
||||
}
|
||||
|
||||
protected override void OnApply()
|
||||
{
|
||||
base.OnApply();
|
||||
@ -100,11 +97,5 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
base.Shake();
|
||||
DrawableSlider.Shake();
|
||||
}
|
||||
|
||||
private void updatePosition()
|
||||
{
|
||||
if (Slider != null)
|
||||
Position = HitObject.Position - Slider.Position;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -57,7 +57,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
/// </summary>
|
||||
public readonly IBindable<double> SpinsPerMinute = new BindableDouble();
|
||||
|
||||
private const double fade_out_duration = 160;
|
||||
private const double fade_out_duration = 240;
|
||||
|
||||
public DrawableSpinner()
|
||||
: this(null)
|
||||
|
@ -109,7 +109,7 @@ namespace osu.Game.Rulesets.Osu
|
||||
yield return new OsuModSpunOut();
|
||||
|
||||
if (mods.HasFlagFast(LegacyMods.Target))
|
||||
yield return new OsuModTarget();
|
||||
yield return new OsuModTargetPractice();
|
||||
|
||||
if (mods.HasFlagFast(LegacyMods.TouchDevice))
|
||||
yield return new OsuModTouchDevice();
|
||||
@ -131,7 +131,7 @@ namespace osu.Game.Rulesets.Osu
|
||||
value |= LegacyMods.SpunOut;
|
||||
break;
|
||||
|
||||
case OsuModTarget:
|
||||
case OsuModTargetPractice:
|
||||
value |= LegacyMods.Target;
|
||||
break;
|
||||
|
||||
@ -170,7 +170,7 @@ namespace osu.Game.Rulesets.Osu
|
||||
case ModType.Conversion:
|
||||
return new Mod[]
|
||||
{
|
||||
new OsuModTarget(),
|
||||
new OsuModTargetPractice(),
|
||||
new OsuModDifficultyAdjust(),
|
||||
new OsuModClassic(),
|
||||
new OsuModRandom(),
|
||||
@ -201,7 +201,8 @@ namespace osu.Game.Rulesets.Osu
|
||||
new OsuModMuted(),
|
||||
new OsuModNoScope(),
|
||||
new MultiMod(new OsuModMagnetised(), new OsuModRepel()),
|
||||
new ModAdaptiveSpeed()
|
||||
new ModAdaptiveSpeed(),
|
||||
new OsuModFreezeFrame()
|
||||
};
|
||||
|
||||
case ModType.System:
|
||||
|
@ -75,6 +75,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon
|
||||
{
|
||||
default:
|
||||
JudgementText
|
||||
.FadeInFromZero(300, Easing.OutQuint)
|
||||
.ScaleTo(Vector2.One)
|
||||
.ScaleTo(new Vector2(1.2f), 1800, Easing.OutQuint);
|
||||
break;
|
||||
@ -96,7 +97,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon
|
||||
ringExplosion?.PlayAnimation();
|
||||
}
|
||||
|
||||
public Drawable? GetAboveHitObjectsProxiedContent() => null;
|
||||
public Drawable? GetAboveHitObjectsProxiedContent() => JudgementText.CreateProxy();
|
||||
|
||||
private class RingExplosion : CompositeDrawable
|
||||
{
|
||||
|
@ -108,20 +108,29 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
accentColour.BindValueChanged(colour =>
|
||||
{
|
||||
outerFill.Colour = innerFill.Colour = colour.NewValue.Darken(4);
|
||||
outerGradient.Colour = ColourInfo.GradientVertical(colour.NewValue, colour.NewValue.Darken(0.1f));
|
||||
innerGradient.Colour = ColourInfo.GradientVertical(colour.NewValue.Darken(0.5f), colour.NewValue.Darken(0.6f));
|
||||
flash.Colour = colour.NewValue;
|
||||
}, true);
|
||||
|
||||
indexInCurrentCombo.BindValueChanged(index => number.Text = (index.NewValue + 1).ToString(), true);
|
||||
|
||||
accentColour.BindValueChanged(colour =>
|
||||
{
|
||||
// A colour transform is applied.
|
||||
// Without removing transforms first, when it is rewound it may apply an old colour.
|
||||
outerGradient.ClearTransforms(targetMember: nameof(Colour));
|
||||
outerGradient.Colour = ColourInfo.GradientVertical(colour.NewValue, colour.NewValue.Darken(0.1f));
|
||||
|
||||
outerFill.Colour = innerFill.Colour = colour.NewValue.Darken(4);
|
||||
innerGradient.Colour = ColourInfo.GradientVertical(colour.NewValue.Darken(0.5f), colour.NewValue.Darken(0.6f));
|
||||
flash.Colour = colour.NewValue;
|
||||
|
||||
// Accent colour may be changed many times during a paused gameplay state.
|
||||
// Schedule the change to avoid transforms piling up.
|
||||
Scheduler.AddOnce(updateStateTransforms);
|
||||
}, true);
|
||||
|
||||
drawableObject.ApplyCustomUpdateState += updateStateTransforms;
|
||||
updateStateTransforms(drawableObject, drawableObject.State.Value);
|
||||
}
|
||||
|
||||
private void updateStateTransforms() => updateStateTransforms(drawableObject, drawableObject.State.Value);
|
||||
|
||||
private void updateStateTransforms(DrawableHitObject drawableHitObject, ArmedState state)
|
||||
{
|
||||
using (BeginAbsoluteSequence(drawableObject.HitStateUpdateTime))
|
||||
@ -166,18 +175,14 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon
|
||||
// This is to give it a bomb-like effect, with the border "triggering" its animation when getting close.
|
||||
using (BeginDelayedSequence(flash_in_duration / 12))
|
||||
{
|
||||
outerGradient.ResizeTo(outerGradient.Size * shrink_size, resize_duration, Easing.OutElasticHalf);
|
||||
outerGradient.ResizeTo(OUTER_GRADIENT_SIZE * shrink_size, resize_duration, Easing.OutElasticHalf);
|
||||
outerGradient
|
||||
.FadeColour(Color4.White, 80)
|
||||
.Then()
|
||||
.FadeOut(flash_in_duration);
|
||||
}
|
||||
|
||||
// The flash layer starts white to give the wanted brightness, but is almost immediately
|
||||
// recoloured to the accent colour. This would more correctly be done with two layers (one for the initial flash)
|
||||
// but works well enough with the colour fade.
|
||||
flash.FadeTo(1, flash_in_duration, Easing.OutQuint);
|
||||
flash.FlashColour(accentColour.Value, fade_out_time, Easing.OutQuint);
|
||||
|
||||
this.FadeOut(fade_out_time, Easing.OutQuad);
|
||||
break;
|
||||
|
@ -68,7 +68,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
||||
|
||||
InternalChildren = new[]
|
||||
{
|
||||
CircleSprite = new KiaiFlashingDrawable(() => new Sprite { Texture = skin.GetTexture(circleName) })
|
||||
CircleSprite = new LegacyKiaiFlashingDrawable(() => new Sprite { Texture = skin.GetTexture(circleName) })
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
@ -77,7 +77,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Child = OverlaySprite = new KiaiFlashingDrawable(() => skin.GetAnimation(@$"{circleName}overlay", true, true, frameLength: 1000 / 2d))
|
||||
Child = OverlaySprite = new LegacyKiaiFlashingDrawable(() => skin.GetAnimation(@$"{circleName}overlay", true, true, frameLength: 1000 / 2d))
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
@ -134,10 +134,10 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
||||
switch (state)
|
||||
{
|
||||
case ArmedState.Hit:
|
||||
CircleSprite.FadeOut(legacy_fade_duration, Easing.Out);
|
||||
CircleSprite.FadeOut(legacy_fade_duration);
|
||||
CircleSprite.ScaleTo(1.4f, legacy_fade_duration, Easing.Out);
|
||||
|
||||
OverlaySprite.FadeOut(legacy_fade_duration, Easing.Out);
|
||||
OverlaySprite.FadeOut(legacy_fade_duration);
|
||||
OverlaySprite.ScaleTo(1.4f, legacy_fade_duration, Easing.Out);
|
||||
|
||||
if (hasNumber)
|
||||
@ -146,11 +146,11 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
||||
|
||||
if (legacyVersion >= 2.0m)
|
||||
// legacy skins of version 2.0 and newer only apply very short fade out to the number piece.
|
||||
hitCircleText.FadeOut(legacy_fade_duration / 4, Easing.Out);
|
||||
hitCircleText.FadeOut(legacy_fade_duration / 4);
|
||||
else
|
||||
{
|
||||
// old skins scale and fade it normally along other pieces.
|
||||
hitCircleText.FadeOut(legacy_fade_duration, Easing.Out);
|
||||
hitCircleText.FadeOut(legacy_fade_duration);
|
||||
hitCircleText.ScaleTo(1.4f, legacy_fade_duration, Easing.Out);
|
||||
}
|
||||
}
|
||||
|
@ -107,8 +107,8 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
||||
using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimePreempt))
|
||||
this.FadeOut();
|
||||
|
||||
using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimeFadeIn / 2))
|
||||
this.FadeInFromZero(spinner.TimeFadeIn / 2);
|
||||
using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimeFadeIn))
|
||||
this.FadeInFromZero(spinner.TimeFadeIn);
|
||||
|
||||
using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimePreempt))
|
||||
{
|
||||
|
@ -2,6 +2,7 @@
|
||||
// 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.Sprites;
|
||||
@ -21,6 +22,8 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
||||
[Resolved(canBeNull: true)]
|
||||
private DrawableHitObject? parentObject { get; set; }
|
||||
|
||||
public Color4 BallColour => animationContent.Colour;
|
||||
|
||||
private Sprite layerNd = null!;
|
||||
private Sprite layerSpec = null!;
|
||||
|
||||
@ -61,6 +64,8 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
||||
};
|
||||
}
|
||||
|
||||
private readonly IBindable<Color4> accentColour = new Bindable<Color4>();
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
@ -69,6 +74,12 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
||||
{
|
||||
parentObject.ApplyCustomUpdateState += updateStateTransforms;
|
||||
updateStateTransforms(parentObject, parentObject.State.Value);
|
||||
|
||||
if (skin.GetConfig<SkinConfiguration.LegacySetting, bool>(SkinConfiguration.LegacySetting.AllowSliderBallTint)?.Value == true)
|
||||
{
|
||||
accentColour.BindTo(parentObject.AccentColour);
|
||||
accentColour.BindValueChanged(a => animationContent.Colour = a.NewValue, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -65,6 +65,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
||||
{
|
||||
spin = new Sprite
|
||||
{
|
||||
Alpha = 0,
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.Centre,
|
||||
Texture = source.GetTexture("spinner-spin"),
|
||||
@ -82,7 +83,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
||||
},
|
||||
bonusCounter = new LegacySpriteText(LegacyFont.Score)
|
||||
{
|
||||
Alpha = 0f,
|
||||
Alpha = 0,
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.Centre,
|
||||
Scale = new Vector2(SPRITE_SCALE),
|
||||
@ -179,6 +180,9 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
||||
spmCounter.MoveToOffset(new Vector2(0, -spm_hide_offset), d.HitObject.TimeFadeIn, Easing.Out);
|
||||
}
|
||||
|
||||
using (BeginAbsoluteSequence(d.HitObject.StartTime - d.HitObject.TimeFadeIn / 2))
|
||||
spin.FadeInFromZero(d.HitObject.TimeFadeIn / 2);
|
||||
|
||||
using (BeginAbsoluteSequence(d.HitObject.StartTime))
|
||||
ApproachCircle?.ScaleTo(SPRITE_SCALE * 0.1f, d.HitObject.Duration);
|
||||
|
||||
|
@ -9,7 +9,6 @@ namespace osu.Game.Rulesets.Osu.Skinning
|
||||
{
|
||||
SliderBorderSize,
|
||||
SliderPathRadius,
|
||||
AllowSliderBallTint,
|
||||
CursorCentre,
|
||||
CursorExpand,
|
||||
CursorRotate,
|
||||
|
@ -4,6 +4,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
@ -14,6 +15,7 @@ using osu.Framework.Graphics.Rendering.Vertices;
|
||||
using osu.Framework.Graphics.Shaders;
|
||||
using osu.Framework.Graphics.Textures;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Utils;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
@ -21,8 +23,6 @@ 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;
|
||||
|
||||
@ -84,12 +84,6 @@ namespace osu.Game.Rulesets.Osu.Skinning
|
||||
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;
|
||||
@ -106,33 +100,27 @@ namespace osu.Game.Rulesets.Osu.Skinning
|
||||
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++)
|
||||
if (SmokePoints.Count == 0 || SmokePoints[^1].Time <= time)
|
||||
{
|
||||
SmokePoints.Add(new SmokePoint
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
Position = pointPos,
|
||||
Time = time,
|
||||
Direction = nextPointDirection(),
|
||||
});
|
||||
SmokePoints.Add(new SmokePoint
|
||||
{
|
||||
Position = pointPos,
|
||||
Time = time,
|
||||
Angle = RNG.NextSingle(0, 2 * MathF.PI),
|
||||
});
|
||||
|
||||
pointPos += increment;
|
||||
pointPos += increment;
|
||||
}
|
||||
}
|
||||
|
||||
Invalidate(Invalidation.DrawNode);
|
||||
}
|
||||
|
||||
lastPosition = position;
|
||||
|
||||
if (SmokePoints.Count >= max_point_count)
|
||||
FinishDrawing(time);
|
||||
}
|
||||
|
||||
public void FinishDrawing(double time)
|
||||
@ -156,7 +144,7 @@ namespace osu.Game.Rulesets.Osu.Skinning
|
||||
{
|
||||
public Vector2 Position;
|
||||
public double Time;
|
||||
public Vector2 Direction;
|
||||
public float Angle;
|
||||
|
||||
public struct UpperBoundComparer : IComparer<SmokePoint>
|
||||
{
|
||||
@ -170,6 +158,17 @@ namespace osu.Game.Rulesets.Osu.Skinning
|
||||
return x.Time > target.Time ? 1 : -1;
|
||||
}
|
||||
}
|
||||
|
||||
public struct LowerBoundComparer : IComparer<SmokePoint>
|
||||
{
|
||||
public int Compare(SmokePoint x, SmokePoint target)
|
||||
{
|
||||
// Similar logic as UpperBoundComparer, except returned index will always be
|
||||
// the first element larger or equal
|
||||
|
||||
return x.Time < target.Time ? -1 : 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected class SmokeDrawNode : TexturedShaderDrawNode
|
||||
@ -185,17 +184,17 @@ namespace osu.Game.Rulesets.Osu.Skinning
|
||||
private float radius;
|
||||
private Vector2 drawSize;
|
||||
private Texture? texture;
|
||||
private int rotationSeed;
|
||||
private int firstVisiblePointIndex;
|
||||
|
||||
// anim calculation vars (color, scale, direction)
|
||||
private double initialFadeOutDurationTrunc;
|
||||
private double firstVisiblePointTime;
|
||||
private double firstVisiblePointTimeAfterSmokeEnded;
|
||||
|
||||
private double initialFadeOutTime;
|
||||
private double reFadeInTime;
|
||||
private double finalFadeOutTime;
|
||||
|
||||
private Random rotationRNG = new Random();
|
||||
|
||||
public SmokeDrawNode(ITexturedShaderDrawable source)
|
||||
: base(source)
|
||||
{
|
||||
@ -205,9 +204,6 @@ namespace osu.Game.Rulesets.Osu.Skinning
|
||||
{
|
||||
base.ApplyState();
|
||||
|
||||
points.Clear();
|
||||
points.AddRange(Source.SmokePoints);
|
||||
|
||||
radius = Source.radius;
|
||||
drawSize = Source.DrawSize;
|
||||
texture = Source.Texture;
|
||||
@ -216,14 +212,21 @@ namespace osu.Game.Rulesets.Osu.Skinning
|
||||
SmokeEndTime = Source.smokeEndTime;
|
||||
CurrentTime = Source.Clock.CurrentTime;
|
||||
|
||||
rotationRNG = new Random(Source.rotationSeed);
|
||||
rotationSeed = Source.rotationSeed;
|
||||
|
||||
initialFadeOutDurationTrunc = Math.Min(initial_fade_out_duration, SmokeEndTime - SmokeStartTime);
|
||||
firstVisiblePointTime = SmokeEndTime - initialFadeOutDurationTrunc;
|
||||
firstVisiblePointTimeAfterSmokeEnded = SmokeEndTime - initialFadeOutDurationTrunc;
|
||||
|
||||
initialFadeOutTime = CurrentTime;
|
||||
reFadeInTime = CurrentTime - initialFadeOutDurationTrunc - firstVisiblePointTime * (1 - 1 / re_fade_in_speed);
|
||||
finalFadeOutTime = CurrentTime - initialFadeOutDurationTrunc - firstVisiblePointTime * (1 - 1 / final_fade_out_speed);
|
||||
initialFadeOutTime = Math.Min(CurrentTime, SmokeEndTime);
|
||||
reFadeInTime = CurrentTime - initialFadeOutDurationTrunc - firstVisiblePointTimeAfterSmokeEnded * (1 - 1 / re_fade_in_speed);
|
||||
finalFadeOutTime = CurrentTime - initialFadeOutDurationTrunc - firstVisiblePointTimeAfterSmokeEnded * (1 - 1 / final_fade_out_speed);
|
||||
|
||||
double firstVisiblePointTime = Math.Min(SmokeEndTime, CurrentTime) - initialFadeOutDurationTrunc;
|
||||
firstVisiblePointIndex = ~Source.SmokePoints.BinarySearch(new SmokePoint { Time = firstVisiblePointTime }, new SmokePoint.LowerBoundComparer());
|
||||
int futurePointIndex = ~Source.SmokePoints.BinarySearch(new SmokePoint { Time = CurrentTime }, new SmokePoint.UpperBoundComparer());
|
||||
|
||||
points.Clear();
|
||||
points.AddRange(Source.SmokePoints.Skip(firstVisiblePointIndex).Take(futurePointIndex - firstVisiblePointIndex));
|
||||
}
|
||||
|
||||
public sealed override void Draw(IRenderer renderer)
|
||||
@ -233,7 +236,14 @@ namespace osu.Game.Rulesets.Osu.Skinning
|
||||
if (points.Count == 0)
|
||||
return;
|
||||
|
||||
quadBatch ??= renderer.CreateQuadBatch<TexturedVertex2D>(max_point_count / 10, 10);
|
||||
quadBatch ??= renderer.CreateQuadBatch<TexturedVertex2D>(200, 4);
|
||||
|
||||
if (points.Count > quadBatch.Size && quadBatch.Size != IRenderer.MAX_QUADS)
|
||||
{
|
||||
int batchSize = Math.Min(quadBatch.Size * 2, IRenderer.MAX_QUADS);
|
||||
quadBatch = renderer.CreateQuadBatch<TexturedVertex2D>(batchSize, 4);
|
||||
}
|
||||
|
||||
texture ??= renderer.WhitePixel;
|
||||
RectangleF textureRect = texture.GetTextureRect();
|
||||
|
||||
@ -245,8 +255,8 @@ namespace osu.Game.Rulesets.Osu.Skinning
|
||||
shader.Bind();
|
||||
texture.Bind();
|
||||
|
||||
foreach (var point in points)
|
||||
drawPointQuad(point, textureRect);
|
||||
for (int i = 0; i < points.Count; i++)
|
||||
drawPointQuad(points[i], textureRect, i + firstVisiblePointIndex);
|
||||
|
||||
shader.Unbind();
|
||||
renderer.PopLocalMatrix();
|
||||
@ -260,30 +270,34 @@ namespace osu.Game.Rulesets.Osu.Skinning
|
||||
{
|
||||
var color = Color4.White;
|
||||
|
||||
double timeDoingInitialFadeOut = Math.Min(initialFadeOutTime, SmokeEndTime) - point.Time;
|
||||
double timeDoingFinalFadeOut = finalFadeOutTime - point.Time / final_fade_out_speed;
|
||||
|
||||
if (timeDoingInitialFadeOut > 0)
|
||||
if (timeDoingFinalFadeOut > 0 && point.Time >= firstVisiblePointTimeAfterSmokeEnded)
|
||||
{
|
||||
float fraction = Math.Clamp((float)(timeDoingInitialFadeOut / initial_fade_out_duration), 0, 1);
|
||||
color.A = (1 - fraction) * initial_alpha;
|
||||
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;
|
||||
}
|
||||
|
||||
if (color.A > 0)
|
||||
else
|
||||
{
|
||||
double timeDoingReFadeIn = reFadeInTime - point.Time / re_fade_in_speed;
|
||||
double timeDoingFinalFadeOut = finalFadeOutTime - point.Time / final_fade_out_speed;
|
||||
double timeDoingInitialFadeOut = initialFadeOutTime - point.Time;
|
||||
|
||||
if (timeDoingFinalFadeOut > 0)
|
||||
if (timeDoingInitialFadeOut > 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;
|
||||
float fraction = Math.Clamp((float)(timeDoingInitialFadeOut / initial_fade_out_duration), 0, 1);
|
||||
color.A = (1 - fraction) * initial_alpha;
|
||||
}
|
||||
else if (timeDoingReFadeIn > 0)
|
||||
|
||||
if (point.Time > firstVisiblePointTimeAfterSmokeEnded)
|
||||
{
|
||||
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;
|
||||
double timeDoingReFadeIn = reFadeInTime - point.Time / re_fade_in_speed;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -298,33 +312,33 @@ namespace osu.Game.Rulesets.Osu.Skinning
|
||||
return fraction * (final_scale - initial_scale) + initial_scale;
|
||||
}
|
||||
|
||||
protected virtual Vector2 PointDirection(SmokePoint point)
|
||||
protected virtual Vector2 PointDirection(SmokePoint point, int index)
|
||||
{
|
||||
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;
|
||||
float angle = fraction * getRotation(index) + point.Angle;
|
||||
|
||||
return new Vector2(MathF.Sin(angle), -MathF.Cos(angle));
|
||||
}
|
||||
|
||||
private float nextRotation() => max_rotation * ((float)rotationRNG.NextDouble() * 2 - 1);
|
||||
private float getRotation(int index) => max_rotation * (StatelessRNG.NextSingle(rotationSeed, index) * 2 - 1);
|
||||
|
||||
private void drawPointQuad(SmokePoint point, RectangleF textureRect)
|
||||
private void drawPointQuad(SmokePoint point, RectangleF textureRect, int index)
|
||||
{
|
||||
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)
|
||||
if (colour.A == 0)
|
||||
return;
|
||||
|
||||
float scale = PointScale(point);
|
||||
if (scale == 0)
|
||||
return;
|
||||
|
||||
var dir = PointDirection(point, index);
|
||||
var ortho = dir.PerpendicularLeft;
|
||||
|
||||
var localTopLeft = point.Position + (radius * scale * (-ortho - dir));
|
||||
var localTopRight = point.Position + (radius * scale * (-ortho + dir));
|
||||
var localBotLeft = point.Position + (radius * scale * (ortho - dir));
|
||||
|
@ -36,6 +36,7 @@ namespace osu.Game.Rulesets.Osu.UI
|
||||
private readonly ProxyContainer spinnerProxies;
|
||||
private readonly JudgementContainer<DrawableOsuJudgement> judgementLayer;
|
||||
|
||||
public SmokeContainer Smoke { get; }
|
||||
public FollowPointRenderer FollowPoints { get; }
|
||||
|
||||
public static readonly Vector2 BASE_SIZE = new Vector2(512, 384);
|
||||
@ -54,7 +55,7 @@ namespace osu.Game.Rulesets.Osu.UI
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
playfieldBorder = new PlayfieldBorder { RelativeSizeAxes = Axes.Both },
|
||||
new SmokeContainer { RelativeSizeAxes = Axes.Both },
|
||||
Smoke = new SmokeContainer { RelativeSizeAxes = Axes.Both },
|
||||
spinnerProxies = new ProxyContainer { RelativeSizeAxes = Axes.Both },
|
||||
FollowPoints = new FollowPointRenderer { RelativeSizeAxes = Axes.Both },
|
||||
judgementLayer = new JudgementContainer<DrawableOsuJudgement> { RelativeSizeAxes = Axes.Both },
|
||||
|
@ -0,0 +1,56 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Rulesets.Taiko.Mods;
|
||||
using osu.Game.Rulesets.Taiko.UI;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Taiko.Tests.Mods
|
||||
{
|
||||
public class TestSceneTaikoModFlashlight : TaikoModTestScene
|
||||
{
|
||||
[TestCase(1f)]
|
||||
[TestCase(0.5f)]
|
||||
[TestCase(1.25f)]
|
||||
[TestCase(1.5f)]
|
||||
public void TestSizeMultiplier(float sizeMultiplier) => CreateModTest(new ModTestData { Mod = new TaikoModFlashlight { SizeMultiplier = { Value = sizeMultiplier } }, PassCondition = () => true });
|
||||
|
||||
[Test]
|
||||
public void TestComboBasedSize([Values] bool comboBasedSize) => CreateModTest(new ModTestData { Mod = new TaikoModFlashlight { ComboBasedSize = { Value = comboBasedSize } }, PassCondition = () => true });
|
||||
|
||||
[Test]
|
||||
public void TestFlashlightAlwaysHasNonZeroSize()
|
||||
{
|
||||
bool failed = false;
|
||||
|
||||
CreateModTest(new ModTestData
|
||||
{
|
||||
Mod = new TestTaikoModFlashlight { ComboBasedSize = { Value = true } },
|
||||
Autoplay = false,
|
||||
PassCondition = () =>
|
||||
{
|
||||
failed |= this.ChildrenOfType<TestTaikoModFlashlight.TestTaikoFlashlight>().SingleOrDefault()?.FlashlightSize.Y == 0;
|
||||
return !failed;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private class TestTaikoModFlashlight : TaikoModFlashlight
|
||||
{
|
||||
protected override Flashlight CreateFlashlight() => new TestTaikoFlashlight(this, Playfield);
|
||||
|
||||
public class TestTaikoFlashlight : TaikoFlashlight
|
||||
{
|
||||
public TestTaikoFlashlight(TaikoModFlashlight modFlashlight, TaikoPlayfield taikoPlayfield)
|
||||
: base(modFlashlight, taikoPlayfield)
|
||||
{
|
||||
}
|
||||
|
||||
public new Vector2 FlashlightSize => base.FlashlightSize;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -25,8 +25,8 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning
|
||||
TimeRange = { Value = 5000 },
|
||||
};
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
[Test]
|
||||
public void DrumrollTest()
|
||||
{
|
||||
AddStep("Drum roll", () => SetContents(_ =>
|
||||
{
|
||||
|
@ -0,0 +1,30 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using NUnit.Framework;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Beatmaps;
|
||||
|
||||
namespace osu.Game.Rulesets.Taiko.Tests.Skinning
|
||||
{
|
||||
[TestFixture]
|
||||
public class TestSceneDrawableDrumRollKiai : TestSceneDrawableDrumRoll
|
||||
{
|
||||
[SetUp]
|
||||
public void SetUp() => Schedule(() =>
|
||||
{
|
||||
var controlPointInfo = new ControlPointInfo();
|
||||
|
||||
controlPointInfo.Add(0, new TimingControlPoint { BeatLength = 500 });
|
||||
controlPointInfo.Add(0, new EffectControlPoint { KiaiMode = true });
|
||||
|
||||
Beatmap.Value = CreateWorkingBeatmap(new Beatmap
|
||||
{
|
||||
ControlPointInfo = controlPointInfo
|
||||
});
|
||||
|
||||
// track needs to be playing for BeatSyncedContainer to work.
|
||||
Beatmap.Value.Track.Start();
|
||||
});
|
||||
}
|
||||
}
|
@ -4,7 +4,6 @@
|
||||
#nullable disable
|
||||
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
@ -16,8 +15,8 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning
|
||||
[TestFixture]
|
||||
public class TestSceneDrawableHit : TaikoSkinnableTestScene
|
||||
{
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
[Test]
|
||||
public void TestHits()
|
||||
{
|
||||
AddStep("Centre hit", () => SetContents(_ => new DrawableHit(createHitAtCurrentTime())
|
||||
{
|
||||
@ -31,23 +30,24 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning
|
||||
Origin = Anchor.Centre,
|
||||
}));
|
||||
|
||||
AddStep("Rim hit", () => SetContents(_ => new DrawableHit(createHitAtCurrentTime())
|
||||
AddStep("Rim hit", () => SetContents(_ => new DrawableHit(createHitAtCurrentTime(rim: true))
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
}));
|
||||
|
||||
AddStep("Rim hit (strong)", () => SetContents(_ => new DrawableHit(createHitAtCurrentTime(true))
|
||||
AddStep("Rim hit (strong)", () => SetContents(_ => new DrawableHit(createHitAtCurrentTime(true, true))
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
}));
|
||||
}
|
||||
|
||||
private Hit createHitAtCurrentTime(bool strong = false)
|
||||
private Hit createHitAtCurrentTime(bool strong = false, bool rim = false)
|
||||
{
|
||||
var hit = new Hit
|
||||
{
|
||||
Type = rim ? HitType.Rim : HitType.Centre,
|
||||
IsStrong = strong,
|
||||
StartTime = Time.Current + 3000,
|
||||
};
|
||||
|
@ -0,0 +1,30 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using NUnit.Framework;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
|
||||
namespace osu.Game.Rulesets.Taiko.Tests.Skinning
|
||||
{
|
||||
[TestFixture]
|
||||
public class TestSceneDrawableHitKiai : TestSceneDrawableHit
|
||||
{
|
||||
[SetUp]
|
||||
public void SetUp() => Schedule(() =>
|
||||
{
|
||||
var controlPointInfo = new ControlPointInfo();
|
||||
|
||||
controlPointInfo.Add(0, new TimingControlPoint { BeatLength = 500 });
|
||||
controlPointInfo.Add(0, new EffectControlPoint { KiaiMode = true });
|
||||
|
||||
Beatmap.Value = CreateWorkingBeatmap(new Beatmap
|
||||
{
|
||||
ControlPointInfo = controlPointInfo
|
||||
});
|
||||
|
||||
// track needs to be playing for BeatSyncedContainer to work.
|
||||
Beatmap.Value.Track.Start();
|
||||
});
|
||||
}
|
||||
}
|
@ -13,6 +13,7 @@ using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Rulesets.Taiko.Objects;
|
||||
using osu.Game.Rulesets.Taiko.UI;
|
||||
using osu.Game.Screens.Ranking;
|
||||
|
||||
namespace osu.Game.Rulesets.Taiko.Tests.Skinning
|
||||
{
|
||||
@ -49,11 +50,19 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning
|
||||
// the hit needs to be added to hierarchy in order for nested objects to be created correctly.
|
||||
// setting zero alpha is supposed to prevent the test from looking broken.
|
||||
hit.With(h => h.Alpha = 0),
|
||||
new HitExplosion(hit.Type)
|
||||
|
||||
new AspectContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
}.With(explosion => explosion.Apply(hit))
|
||||
Child =
|
||||
new HitExplosion(hit.Type)
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
}.With(explosion => explosion.Apply(hit))
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -57,6 +57,28 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
|
||||
|
||||
Beatmap<TaikoHitObject> converted = base.ConvertBeatmap(original, cancellationToken);
|
||||
|
||||
if (original.BeatmapInfo.Ruleset.OnlineID == 0)
|
||||
{
|
||||
// Post processing step to transform standard slider velocity changes into scroll speed changes
|
||||
double lastScrollSpeed = 1;
|
||||
|
||||
foreach (HitObject hitObject in original.HitObjects)
|
||||
{
|
||||
double nextScrollSpeed = hitObject.DifficultyControlPoint.SliderVelocity;
|
||||
EffectControlPoint currentEffectPoint = converted.ControlPointInfo.EffectPointAt(hitObject.StartTime);
|
||||
|
||||
if (!Precision.AlmostEquals(lastScrollSpeed, nextScrollSpeed, acceptableDifference: currentEffectPoint.ScrollSpeedBindable.Precision))
|
||||
{
|
||||
converted.ControlPointInfo.Add(hitObject.StartTime, new EffectControlPoint
|
||||
{
|
||||
KiaiMode = currentEffectPoint.KiaiMode,
|
||||
OmitFirstBarLine = currentEffectPoint.OmitFirstBarLine,
|
||||
ScrollSpeed = lastScrollSpeed = nextScrollSpeed,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (original.BeatmapInfo.Ruleset.OnlineID == 3)
|
||||
{
|
||||
// Post processing step to transform mania hit objects with the same start time into strong hits
|
||||
|
@ -27,6 +27,12 @@ namespace osu.Game.Rulesets.Taiko.Edit.Blueprints
|
||||
};
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
BeginPlacement();
|
||||
}
|
||||
|
||||
protected override bool OnMouseDown(MouseDownEvent e)
|
||||
{
|
||||
switch (e.Button)
|
||||
|
@ -52,6 +52,12 @@ namespace osu.Game.Rulesets.Taiko.Edit.Blueprints
|
||||
private double originalStartTime;
|
||||
private Vector2 originalPosition;
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
BeginPlacement();
|
||||
}
|
||||
|
||||
protected override bool OnMouseDown(MouseDownEvent e)
|
||||
{
|
||||
if (e.Button != MouseButton.Left)
|
||||
|
@ -1,8 +1,6 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System.Collections.Generic;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
using osu.Game.Rulesets.Edit.Tools;
|
||||
|
@ -27,17 +27,17 @@ namespace osu.Game.Rulesets.Taiko.Mods
|
||||
|
||||
public override float DefaultFlashlightSize => 200;
|
||||
|
||||
protected override Flashlight CreateFlashlight() => new TaikoFlashlight(this, playfield);
|
||||
protected override Flashlight CreateFlashlight() => new TaikoFlashlight(this, Playfield);
|
||||
|
||||
private TaikoPlayfield playfield = null!;
|
||||
protected TaikoPlayfield Playfield { get; private set; } = null!;
|
||||
|
||||
public override void ApplyToDrawableRuleset(DrawableRuleset<TaikoHitObject> drawableRuleset)
|
||||
{
|
||||
playfield = (TaikoPlayfield)drawableRuleset.Playfield;
|
||||
Playfield = (TaikoPlayfield)drawableRuleset.Playfield;
|
||||
base.ApplyToDrawableRuleset(drawableRuleset);
|
||||
}
|
||||
|
||||
private class TaikoFlashlight : Flashlight
|
||||
public class TaikoFlashlight : Flashlight
|
||||
{
|
||||
private readonly LayoutValue flashlightProperties = new LayoutValue(Invalidation.RequiredParentSizeToFit | Invalidation.DrawInfo);
|
||||
private readonly TaikoPlayfield taikoPlayfield;
|
||||
@ -47,21 +47,28 @@ namespace osu.Game.Rulesets.Taiko.Mods
|
||||
{
|
||||
this.taikoPlayfield = taikoPlayfield;
|
||||
|
||||
FlashlightSize = adjustSize(GetSize());
|
||||
FlashlightSize = adjustSizeForPlayfieldAspectRatio(GetSize());
|
||||
FlashlightSmoothness = 1.4f;
|
||||
|
||||
AddLayout(flashlightProperties);
|
||||
}
|
||||
|
||||
private Vector2 adjustSize(float size)
|
||||
/// <summary>
|
||||
/// Returns the aspect ratio-adjusted size of the flashlight.
|
||||
/// This ensures that the size of the flashlight remains independent of taiko-specific aspect ratio adjustments.
|
||||
/// </summary>
|
||||
/// <param name="size">
|
||||
/// The size of the flashlight.
|
||||
/// The value provided here should always come from <see cref="ModFlashlight{T}.Flashlight.GetSize"/>.
|
||||
/// </param>
|
||||
private Vector2 adjustSizeForPlayfieldAspectRatio(float size)
|
||||
{
|
||||
// Preserve flashlight size through the playfield's aspect adjustment.
|
||||
return new Vector2(0, size * taikoPlayfield.DrawHeight / TaikoPlayfield.DEFAULT_HEIGHT);
|
||||
}
|
||||
|
||||
protected override void UpdateFlashlightSize(float size)
|
||||
{
|
||||
this.TransformTo(nameof(FlashlightSize), adjustSize(size), FLASHLIGHT_FADE_DURATION);
|
||||
this.TransformTo(nameof(FlashlightSize), adjustSizeForPlayfieldAspectRatio(size), FLASHLIGHT_FADE_DURATION);
|
||||
}
|
||||
|
||||
protected override string FragmentShader => "CircularFlashlight";
|
||||
@ -75,7 +82,7 @@ namespace osu.Game.Rulesets.Taiko.Mods
|
||||
FlashlightPosition = ToLocalSpace(taikoPlayfield.HitTarget.ScreenSpaceDrawQuad.Centre);
|
||||
|
||||
ClearTransforms(targetMember: nameof(FlashlightSize));
|
||||
FlashlightSize = adjustSize(Combo.Value);
|
||||
FlashlightSize = adjustSizeForPlayfieldAspectRatio(GetSize());
|
||||
|
||||
flashlightProperties.Validate();
|
||||
}
|
||||
|
@ -41,12 +41,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Default
|
||||
|
||||
Children = new[]
|
||||
{
|
||||
new CircularContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Masking = true,
|
||||
Children = new[] { new Box { RelativeSizeAxes = Axes.Both } }
|
||||
}
|
||||
new Circle { RelativeSizeAxes = Axes.Both }
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -3,6 +3,7 @@
|
||||
|
||||
#nullable disable
|
||||
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio.Track;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
@ -13,6 +14,7 @@ using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Backgrounds;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Taiko.Objects;
|
||||
using osuTK.Graphics;
|
||||
|
||||
@ -32,6 +34,8 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Default
|
||||
|
||||
private const double pre_beat_transition_time = 80;
|
||||
|
||||
private const float flash_opacity = 0.3f;
|
||||
|
||||
private Color4 accentColour;
|
||||
|
||||
/// <summary>
|
||||
@ -152,11 +156,22 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Default
|
||||
};
|
||||
}
|
||||
|
||||
[Resolved]
|
||||
private DrawableHitObject drawableHitObject { get; set; }
|
||||
|
||||
protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, ChannelAmplitudes amplitudes)
|
||||
{
|
||||
if (!effectPoint.KiaiMode)
|
||||
return;
|
||||
|
||||
if (drawableHitObject.State.Value == ArmedState.Idle)
|
||||
{
|
||||
FlashBox
|
||||
.FadeTo(flash_opacity)
|
||||
.Then()
|
||||
.FadeOut(timingPoint.BeatLength * 0.75, Easing.OutSine);
|
||||
}
|
||||
|
||||
if (beatIndex % timingPoint.TimeSignature.Numerator != 0)
|
||||
return;
|
||||
|
||||
|
@ -1,9 +1,6 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using JetBrains.Annotations;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
@ -12,19 +9,19 @@ using osu.Game.Graphics;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Rulesets.Taiko.Objects;
|
||||
using osu.Game.Rulesets.Taiko.UI;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Rulesets.Taiko.UI
|
||||
namespace osu.Game.Rulesets.Taiko.Skinning.Default
|
||||
{
|
||||
internal class DefaultHitExplosion : CircularContainer, IAnimatableHitExplosion
|
||||
{
|
||||
private readonly HitResult result;
|
||||
|
||||
[CanBeNull]
|
||||
private Box body;
|
||||
private Box? body;
|
||||
|
||||
[Resolved]
|
||||
private OsuColour colours { get; set; }
|
||||
private OsuColour colours { get; set; } = null!;
|
||||
|
||||
public DefaultHitExplosion(HitResult result)
|
||||
{
|
||||
@ -58,7 +55,7 @@ namespace osu.Game.Rulesets.Taiko.UI
|
||||
updateColour();
|
||||
}
|
||||
|
||||
private void updateColour([CanBeNull] DrawableHitObject judgedObject = null)
|
||||
private void updateColour(DrawableHitObject? judgedObject = null)
|
||||
{
|
||||
if (body == null)
|
||||
return;
|
181
osu.Game.Rulesets.Taiko/Skinning/Default/DefaultInputDrum.cs
Normal file
181
osu.Game.Rulesets.Taiko/Skinning/Default/DefaultInputDrum.cs
Normal file
@ -0,0 +1,181 @@
|
||||
// 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;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Graphics.Textures;
|
||||
using osu.Framework.Input.Bindings;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Screens.Ranking;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Taiko.Skinning.Default
|
||||
{
|
||||
public class DefaultInputDrum : AspectContainer
|
||||
{
|
||||
public DefaultInputDrum()
|
||||
{
|
||||
RelativeSizeAxes = Axes.Y;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
const float middle_split = 0.025f;
|
||||
|
||||
InternalChild = new Container
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Scale = new Vector2(0.9f),
|
||||
Children = new[]
|
||||
{
|
||||
new TaikoHalfDrum(false)
|
||||
{
|
||||
Name = "Left Half",
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.CentreRight,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
RelativePositionAxes = Axes.X,
|
||||
X = -middle_split / 2,
|
||||
RimAction = TaikoAction.LeftRim,
|
||||
CentreAction = TaikoAction.LeftCentre
|
||||
},
|
||||
new TaikoHalfDrum(true)
|
||||
{
|
||||
Name = "Right Half",
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.CentreLeft,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
RelativePositionAxes = Axes.X,
|
||||
X = middle_split / 2,
|
||||
RimAction = TaikoAction.RightRim,
|
||||
CentreAction = TaikoAction.RightCentre
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A half-drum. Contains one centre and one rim hit.
|
||||
/// </summary>
|
||||
private class TaikoHalfDrum : Container, IKeyBindingHandler<TaikoAction>
|
||||
{
|
||||
/// <summary>
|
||||
/// The key to be used for the rim of the half-drum.
|
||||
/// </summary>
|
||||
public TaikoAction RimAction;
|
||||
|
||||
/// <summary>
|
||||
/// The key to be used for the centre of the half-drum.
|
||||
/// </summary>
|
||||
public TaikoAction CentreAction;
|
||||
|
||||
private readonly Sprite rim;
|
||||
private readonly Sprite rimHit;
|
||||
private readonly Sprite centre;
|
||||
private readonly Sprite centreHit;
|
||||
|
||||
public TaikoHalfDrum(bool flipped)
|
||||
{
|
||||
Masking = true;
|
||||
|
||||
Children = new Drawable[]
|
||||
{
|
||||
rim = new Sprite
|
||||
{
|
||||
Anchor = flipped ? Anchor.CentreLeft : Anchor.CentreRight,
|
||||
Origin = Anchor.Centre,
|
||||
RelativeSizeAxes = Axes.Both
|
||||
},
|
||||
rimHit = new Sprite
|
||||
{
|
||||
Anchor = flipped ? Anchor.CentreLeft : Anchor.CentreRight,
|
||||
Origin = Anchor.Centre,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Alpha = 0,
|
||||
Blending = BlendingParameters.Additive,
|
||||
},
|
||||
centre = new Sprite
|
||||
{
|
||||
Anchor = flipped ? Anchor.CentreLeft : Anchor.CentreRight,
|
||||
Origin = Anchor.Centre,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Size = new Vector2(0.7f)
|
||||
},
|
||||
centreHit = new Sprite
|
||||
{
|
||||
Anchor = flipped ? Anchor.CentreLeft : Anchor.CentreRight,
|
||||
Origin = Anchor.Centre,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Size = new Vector2(0.7f),
|
||||
Alpha = 0,
|
||||
Blending = BlendingParameters.Additive
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(TextureStore textures, OsuColour colours)
|
||||
{
|
||||
rim.Texture = textures.Get(@"Gameplay/taiko/taiko-drum-outer");
|
||||
rimHit.Texture = textures.Get(@"Gameplay/taiko/taiko-drum-outer-hit");
|
||||
centre.Texture = textures.Get(@"Gameplay/taiko/taiko-drum-inner");
|
||||
centreHit.Texture = textures.Get(@"Gameplay/taiko/taiko-drum-inner-hit");
|
||||
|
||||
rimHit.Colour = colours.Blue;
|
||||
centreHit.Colour = colours.Pink;
|
||||
}
|
||||
|
||||
public bool OnPressed(KeyBindingPressEvent<TaikoAction> e)
|
||||
{
|
||||
Drawable target = null;
|
||||
Drawable back = null;
|
||||
|
||||
if (e.Action == CentreAction)
|
||||
{
|
||||
target = centreHit;
|
||||
back = centre;
|
||||
}
|
||||
else if (e.Action == RimAction)
|
||||
{
|
||||
target = rimHit;
|
||||
back = rim;
|
||||
}
|
||||
|
||||
if (target != null)
|
||||
{
|
||||
const float scale_amount = 0.05f;
|
||||
const float alpha_amount = 0.5f;
|
||||
|
||||
const float down_time = 40;
|
||||
const float up_time = 1000;
|
||||
|
||||
back.ScaleTo(target.Scale.X - scale_amount, down_time, Easing.OutQuint)
|
||||
.Then()
|
||||
.ScaleTo(1, up_time, Easing.OutQuint);
|
||||
|
||||
target.Animate(
|
||||
t => t.ScaleTo(target.Scale.X - scale_amount, down_time, Easing.OutQuint),
|
||||
t => t.FadeTo(Math.Min(target.Alpha + alpha_amount, 1), down_time, Easing.OutQuint)
|
||||
).Then(
|
||||
t => t.ScaleTo(1, up_time, Easing.OutQuint),
|
||||
t => t.FadeOut(up_time, Easing.OutQuint)
|
||||
);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void OnReleased(KeyBindingReleaseEvent<TaikoAction> e)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,9 +1,6 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using osuTK;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
@ -11,8 +8,9 @@ using osu.Framework.Graphics.Effects;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Rulesets.Taiko.Objects;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Taiko.UI
|
||||
namespace osu.Game.Rulesets.Taiko.Skinning.Default
|
||||
{
|
||||
public class DefaultKiaiHitExplosion : CircularContainer
|
||||
{
|
@ -1,8 +1,6 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Graphics;
|
||||
|
@ -1,8 +1,6 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
|
@ -1,8 +1,6 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
|
@ -1,8 +1,6 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
|
@ -1,8 +1,6 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
|
@ -1,8 +1,6 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Animations;
|
||||
@ -19,7 +17,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy
|
||||
{
|
||||
public class LegacyCirclePiece : CompositeDrawable, IHasAccentColour
|
||||
{
|
||||
private Drawable backgroundLayer;
|
||||
private Drawable backgroundLayer = null!;
|
||||
|
||||
// required for editor blueprints (not sure why these circle pieces are zero size).
|
||||
public override Quad ScreenSpaceDrawQuad => backgroundLayer.ScreenSpaceDrawQuad;
|
||||
@ -32,7 +30,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(ISkinSource skin, DrawableHitObject drawableHitObject)
|
||||
{
|
||||
Drawable getDrawableFor(string lookup)
|
||||
Drawable? getDrawableFor(string lookup)
|
||||
{
|
||||
const string normal_hit = "taikohit";
|
||||
const string big_hit = "taikobig";
|
||||
@ -45,7 +43,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy
|
||||
}
|
||||
|
||||
// backgroundLayer is guaranteed to exist due to the pre-check in TaikoLegacySkinTransformer.
|
||||
AddInternal(backgroundLayer = getDrawableFor("circle"));
|
||||
AddInternal(backgroundLayer = new LegacyKiaiFlashingDrawable(() => getDrawableFor("circle")));
|
||||
|
||||
var foregroundLayer = getDrawableFor("circleoverlay");
|
||||
if (foregroundLayer != null)
|
||||
|
@ -1,8 +1,6 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
@ -28,11 +26,11 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy
|
||||
}
|
||||
}
|
||||
|
||||
private LegacyCirclePiece headCircle;
|
||||
private LegacyCirclePiece headCircle = null!;
|
||||
|
||||
private Sprite body;
|
||||
private Sprite body = null!;
|
||||
|
||||
private Sprite tailCircle;
|
||||
private Sprite tailCircle = null!;
|
||||
|
||||
public LegacyDrumRoll()
|
||||
{
|
||||
|
@ -1,8 +1,6 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Game.Skinning;
|
||||
using osuTK.Graphics;
|
||||
|
@ -1,9 +1,6 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using JetBrains.Annotations;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Animations;
|
||||
@ -17,8 +14,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy
|
||||
{
|
||||
private readonly Drawable sprite;
|
||||
|
||||
[CanBeNull]
|
||||
private readonly Drawable strongSprite;
|
||||
private readonly Drawable? strongSprite;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new legacy hit explosion.
|
||||
@ -29,7 +25,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy
|
||||
/// </remarks>
|
||||
/// <param name="sprite">The normal legacy explosion sprite.</param>
|
||||
/// <param name="strongSprite">The strong legacy explosion sprite.</param>
|
||||
public LegacyHitExplosion(Drawable sprite, [CanBeNull] Drawable strongSprite = null)
|
||||
public LegacyHitExplosion(Drawable sprite, Drawable? strongSprite = null)
|
||||
{
|
||||
this.sprite = sprite;
|
||||
this.strongSprite = strongSprite;
|
||||
|
@ -1,8 +1,6 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
@ -20,9 +18,9 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy
|
||||
/// </summary>
|
||||
internal class LegacyInputDrum : Container
|
||||
{
|
||||
private Container content;
|
||||
private LegacyHalfDrum left;
|
||||
private LegacyHalfDrum right;
|
||||
private Container content = null!;
|
||||
private LegacyHalfDrum left = null!;
|
||||
private LegacyHalfDrum right = null!;
|
||||
|
||||
public LegacyInputDrum()
|
||||
{
|
||||
@ -142,7 +140,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy
|
||||
|
||||
public bool OnPressed(KeyBindingPressEvent<TaikoAction> e)
|
||||
{
|
||||
Drawable target = null;
|
||||
Drawable? target = null;
|
||||
|
||||
if (e.Action == CentreAction)
|
||||
{
|
||||
|
@ -1,8 +1,6 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
@ -27,7 +25,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader(true)]
|
||||
private void load(GameplayState gameplayState)
|
||||
private void load(GameplayState? gameplayState)
|
||||
{
|
||||
if (gameplayState != null)
|
||||
((IBindable<JudgementResult>)LastResult).BindTo(gameplayState.LastJudgementResult);
|
||||
@ -91,8 +89,8 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy
|
||||
|
||||
private class ScrollerSprite : CompositeDrawable
|
||||
{
|
||||
private Sprite passingSprite;
|
||||
private Sprite failingSprite;
|
||||
private Sprite passingSprite = null!;
|
||||
private Sprite failingSprite = null!;
|
||||
|
||||
private bool passing = true;
|
||||
|
||||
|
@ -1,8 +1,6 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
@ -15,7 +13,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy
|
||||
{
|
||||
public class TaikoLegacyHitTarget : CompositeDrawable
|
||||
{
|
||||
private Container content;
|
||||
private Container content = null!;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(ISkinSource skin)
|
||||
|
@ -1,8 +1,6 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio.Track;
|
||||
using osu.Framework.Graphics;
|
||||
@ -16,7 +14,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy
|
||||
{
|
||||
public class TaikoLegacyPlayfieldBackgroundRight : BeatSyncedContainer
|
||||
{
|
||||
private Sprite kiai;
|
||||
private Sprite kiai = null!;
|
||||
|
||||
private bool kiaiDisplayed;
|
||||
|
||||
|
@ -1,8 +1,6 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using osu.Game.Skinning;
|
||||
|
||||
namespace osu.Game.Rulesets.Taiko
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user