mirror of
https://github.com/ppy/osu.git
synced 2025-01-12 21:52:55 +08:00
Merge branch 'master' into fix-multiple-playback-on-skin-change
This commit is contained in:
commit
fcd9483ffa
@ -5,6 +5,6 @@
|
|||||||
"version": "3.1.100"
|
"version": "3.1.100"
|
||||||
},
|
},
|
||||||
"msbuild-sdks": {
|
"msbuild-sdks": {
|
||||||
"Microsoft.Build.Traversal": "2.2.3"
|
"Microsoft.Build.Traversal": "3.0.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -51,7 +51,7 @@
|
|||||||
<Reference Include="Java.Interop" />
|
<Reference Include="Java.Interop" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2020.1030.0" />
|
<PackageReference Include="ppy.osu.Game.Resources" Version="2020.1202.0" />
|
||||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2020.1118.0" />
|
<PackageReference Include="ppy.osu.Framework.Android" Version="2020.1203.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
@ -59,7 +59,7 @@ namespace osu.Desktop
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
using (RegistryKey key = Registry.ClassesRoot.OpenSubKey("osu"))
|
using (RegistryKey key = Registry.ClassesRoot.OpenSubKey("osu"))
|
||||||
stableInstallPath = key?.OpenSubKey(@"shell\open\command")?.GetValue(string.Empty).ToString()?.Split('"')[1].Replace("osu!.exe", "");
|
stableInstallPath = key?.OpenSubKey(@"shell\open\command")?.GetValue(string.Empty)?.ToString()?.Split('"')[1].Replace("osu!.exe", "");
|
||||||
|
|
||||||
if (checkExists(stableInstallPath))
|
if (checkExists(stableInstallPath))
|
||||||
return stableInstallPath;
|
return stableInstallPath;
|
||||||
@ -138,7 +138,7 @@ namespace osu.Desktop
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
// SDL2 DesktopWindow
|
// SDL2 DesktopWindow
|
||||||
case DesktopWindow desktopWindow:
|
case SDL2DesktopWindow desktopWindow:
|
||||||
desktopWindow.CursorState |= CursorState.Hidden;
|
desktopWindow.CursorState |= CursorState.Hidden;
|
||||||
desktopWindow.SetIconFromStream(iconStream);
|
desktopWindow.SetIconFromStream(iconStream);
|
||||||
desktopWindow.Title = Name;
|
desktopWindow.Title = Name;
|
||||||
|
@ -22,9 +22,9 @@ namespace osu.Desktop
|
|||||||
{
|
{
|
||||||
// Back up the cwd before DesktopGameHost changes it
|
// Back up the cwd before DesktopGameHost changes it
|
||||||
var cwd = Environment.CurrentDirectory;
|
var cwd = Environment.CurrentDirectory;
|
||||||
bool useSdl = args.Contains("--sdl");
|
bool useOsuTK = args.Contains("--tk");
|
||||||
|
|
||||||
using (DesktopGameHost host = Host.GetSuitableHost(@"osu", true, useSdl: useSdl))
|
using (DesktopGameHost host = Host.GetSuitableHost(@"osu", true, useOsuTK: useOsuTK))
|
||||||
{
|
{
|
||||||
host.ExceptionThrown += handleException;
|
host.ExceptionThrown += handleException;
|
||||||
|
|
||||||
|
@ -24,12 +24,12 @@
|
|||||||
<ProjectReference Include="..\osu.Game.Rulesets.Taiko\osu.Game.Rulesets.Taiko.csproj" />
|
<ProjectReference Include="..\osu.Game.Rulesets.Taiko\osu.Game.Rulesets.Taiko.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup Label="Package References">
|
<ItemGroup Label="Package References">
|
||||||
<PackageReference Include="System.IO.Packaging" Version="4.7.0" />
|
<PackageReference Include="System.IO.Packaging" Version="5.0.0" />
|
||||||
<PackageReference Include="ppy.squirrel.windows" Version="1.9.0.4" />
|
<PackageReference Include="ppy.squirrel.windows" Version="1.9.0.4" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.6" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.6" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="2.2.6" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="2.2.6" />
|
||||||
<PackageReference Include="Microsoft.Win32.Registry" Version="4.7.0" />
|
<PackageReference Include="Microsoft.Win32.Registry" Version="5.0.0" />
|
||||||
<PackageReference Include="DiscordRichPresence" Version="1.0.150" />
|
<PackageReference Include="DiscordRichPresence" Version="1.0.166" />
|
||||||
<!-- .NET 3.1 SDK seems to cause issues with a runtime specification. This will likely be resolved in .NET 5. -->
|
<!-- .NET 3.1 SDK seems to cause issues with a runtime specification. This will likely be resolved in .NET 5. -->
|
||||||
<PackageReference Include="System.IO.FileSystem.Primitives" Version="4.3.0" />
|
<PackageReference Include="System.IO.FileSystem.Primitives" Version="4.3.0" />
|
||||||
<PackageReference Include="System.Runtime.Handles" Version="4.3.0" />
|
<PackageReference Include="System.Runtime.Handles" Version="4.3.0" />
|
||||||
|
@ -38,17 +38,17 @@ namespace osu.Game.Rulesets.Catch.Tests.Mods
|
|||||||
new Fruit
|
new Fruit
|
||||||
{
|
{
|
||||||
X = 0,
|
X = 0,
|
||||||
StartTime = 250
|
StartTime = 1000
|
||||||
},
|
},
|
||||||
new Fruit
|
new Fruit
|
||||||
{
|
{
|
||||||
X = CatchPlayfield.WIDTH,
|
X = CatchPlayfield.WIDTH,
|
||||||
StartTime = 500
|
StartTime = 2000
|
||||||
},
|
},
|
||||||
new JuiceStream
|
new JuiceStream
|
||||||
{
|
{
|
||||||
X = CatchPlayfield.CENTER_X,
|
X = CatchPlayfield.CENTER_X,
|
||||||
StartTime = 750,
|
StartTime = 3000,
|
||||||
Path = new SliderPath(PathType.Linear, new[] { Vector2.Zero, Vector2.UnitY * 200 })
|
Path = new SliderPath(PathType.Linear, new[] { Vector2.Zero, Vector2.UnitY * 200 })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -43,7 +43,7 @@ namespace osu.Game.Rulesets.Catch.Tests
|
|||||||
NewCombo = i % 8 == 0,
|
NewCombo = i % 8 == 0,
|
||||||
Samples = new List<HitSampleInfo>(new[]
|
Samples = new List<HitSampleInfo>(new[]
|
||||||
{
|
{
|
||||||
new HitSampleInfo { Bank = "normal", Name = "hitnormal", Volume = 100 }
|
new HitSampleInfo(HitSampleInfo.HIT_NORMAL, "normal", volume: 100)
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,14 @@
|
|||||||
|
// 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.Tests.Visual;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Catch.Tests
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class TestSceneCatchPlayerLegacySkin : LegacySkinPlayerTestScene
|
||||||
|
{
|
||||||
|
protected override Ruleset CreatePlayerRuleset() => new CatchRuleset();
|
||||||
|
}
|
||||||
|
}
|
@ -1,26 +1,194 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Game.Rulesets.Catch.UI;
|
using osu.Game.Rulesets.Catch.UI;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Testing;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
|
using osu.Game.Configuration;
|
||||||
|
using osu.Game.Rulesets.Catch.Objects;
|
||||||
|
using osu.Game.Rulesets.Catch.Objects.Drawables;
|
||||||
|
using osu.Game.Tests.Visual;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Tests
|
namespace osu.Game.Rulesets.Catch.Tests
|
||||||
{
|
{
|
||||||
[TestFixture]
|
[TestFixture]
|
||||||
public class TestSceneCatcher : CatchSkinnableTestScene
|
public class TestSceneCatcher : OsuTestScene
|
||||||
{
|
{
|
||||||
[BackgroundDependencyLoader]
|
[Resolved]
|
||||||
private void load()
|
private OsuConfigManager config { get; set; }
|
||||||
|
|
||||||
|
private Container droppedObjectContainer;
|
||||||
|
|
||||||
|
private TestCatcher catcher;
|
||||||
|
|
||||||
|
[SetUp]
|
||||||
|
public void SetUp() => Schedule(() =>
|
||||||
{
|
{
|
||||||
SetContents(() => new Catcher(new Container())
|
var difficulty = new BeatmapDifficulty
|
||||||
|
{
|
||||||
|
CircleSize = 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
var trailContainer = new Container();
|
||||||
|
droppedObjectContainer = new Container();
|
||||||
|
catcher = new TestCatcher(trailContainer, droppedObjectContainer, difficulty);
|
||||||
|
|
||||||
|
Child = new Container
|
||||||
{
|
{
|
||||||
RelativePositionAxes = Axes.None,
|
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
trailContainer,
|
||||||
|
droppedObjectContainer,
|
||||||
|
catcher
|
||||||
|
}
|
||||||
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestCatcherCatchWidth()
|
||||||
|
{
|
||||||
|
var halfWidth = Catcher.CalculateCatchWidth(new BeatmapDifficulty { CircleSize = 0 }) / 2;
|
||||||
|
AddStep("catch fruit", () =>
|
||||||
|
{
|
||||||
|
attemptCatch(new Fruit { X = -halfWidth + 1 });
|
||||||
|
attemptCatch(new Fruit { X = halfWidth - 1 });
|
||||||
|
});
|
||||||
|
checkPlate(2);
|
||||||
|
AddStep("miss fruit", () =>
|
||||||
|
{
|
||||||
|
attemptCatch(new Fruit { X = -halfWidth - 1 });
|
||||||
|
attemptCatch(new Fruit { X = halfWidth + 1 });
|
||||||
|
});
|
||||||
|
checkPlate(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestFruitChangesCatcherState()
|
||||||
|
{
|
||||||
|
AddStep("miss fruit", () => attemptCatch(new Fruit { X = 100 }));
|
||||||
|
checkState(CatcherAnimationState.Fail);
|
||||||
|
AddStep("catch fruit", () => attemptCatch(new Fruit()));
|
||||||
|
checkState(CatcherAnimationState.Idle);
|
||||||
|
AddStep("catch kiai fruit", () => attemptCatch(new TestKiaiFruit()));
|
||||||
|
checkState(CatcherAnimationState.Kiai);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestNormalFruitResetsHyperDashState()
|
||||||
|
{
|
||||||
|
AddStep("catch hyper fruit", () => attemptCatch(new Fruit
|
||||||
|
{
|
||||||
|
HyperDashTarget = new Fruit { X = 100 }
|
||||||
|
}));
|
||||||
|
checkHyperDash(true);
|
||||||
|
AddStep("catch normal fruit", () => attemptCatch(new Fruit()));
|
||||||
|
checkHyperDash(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestTinyDropletMissPreservesCatcherState()
|
||||||
|
{
|
||||||
|
AddStep("catch hyper kiai fruit", () => attemptCatch(new TestKiaiFruit
|
||||||
|
{
|
||||||
|
HyperDashTarget = new Fruit { X = 100 }
|
||||||
|
}));
|
||||||
|
AddStep("catch tiny droplet", () => attemptCatch(new TinyDroplet()));
|
||||||
|
AddStep("miss tiny droplet", () => attemptCatch(new TinyDroplet { X = 100 }));
|
||||||
|
// catcher state and hyper dash state is preserved
|
||||||
|
checkState(CatcherAnimationState.Kiai);
|
||||||
|
checkHyperDash(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestBananaMissPreservesCatcherState()
|
||||||
|
{
|
||||||
|
AddStep("catch hyper kiai fruit", () => attemptCatch(new TestKiaiFruit
|
||||||
|
{
|
||||||
|
HyperDashTarget = new Fruit { X = 100 }
|
||||||
|
}));
|
||||||
|
AddStep("miss banana", () => attemptCatch(new Banana { X = 100 }));
|
||||||
|
// catcher state is preserved but hyper dash state is reset
|
||||||
|
checkState(CatcherAnimationState.Kiai);
|
||||||
|
checkHyperDash(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestCatcherStacking()
|
||||||
|
{
|
||||||
|
AddStep("catch fruit", () => attemptCatch(new Fruit()));
|
||||||
|
checkPlate(1);
|
||||||
|
AddStep("catch more fruits", () => attemptCatch(new Fruit(), 9));
|
||||||
|
checkPlate(10);
|
||||||
|
AddAssert("caught objects are stacked", () =>
|
||||||
|
catcher.CaughtObjects.All(obj => obj.Y <= 0) &&
|
||||||
|
catcher.CaughtObjects.Any(obj => obj.Y == 0) &&
|
||||||
|
catcher.CaughtObjects.Any(obj => obj.Y < -20));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestCatcherExplosionAndDropping()
|
||||||
|
{
|
||||||
|
AddStep("catch fruit", () => attemptCatch(new Fruit()));
|
||||||
|
AddStep("catch tiny droplet", () => attemptCatch(new TinyDroplet()));
|
||||||
|
AddAssert("tiny droplet is exploded", () => catcher.CaughtObjects.Count() == 1 && droppedObjectContainer.Count == 1);
|
||||||
|
AddUntilStep("wait explosion", () => !droppedObjectContainer.Any());
|
||||||
|
AddStep("catch more fruits", () => attemptCatch(new Fruit(), 9));
|
||||||
|
AddStep("explode", () => catcher.Explode());
|
||||||
|
AddAssert("fruits are exploded", () => !catcher.CaughtObjects.Any() && droppedObjectContainer.Count == 10);
|
||||||
|
AddUntilStep("wait explosion", () => !droppedObjectContainer.Any());
|
||||||
|
AddStep("catch fruits", () => attemptCatch(new Fruit(), 10));
|
||||||
|
AddStep("drop", () => catcher.Drop());
|
||||||
|
AddAssert("fruits are dropped", () => !catcher.CaughtObjects.Any() && droppedObjectContainer.Count == 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestCase(true)]
|
||||||
|
[TestCase(false)]
|
||||||
|
public void TestHitLighting(bool enabled)
|
||||||
|
{
|
||||||
|
AddStep($"{(enabled ? "enable" : "disable")} hit lighting", () => config.Set(OsuSetting.HitLighting, enabled));
|
||||||
|
AddStep("catch fruit", () => attemptCatch(new Fruit()));
|
||||||
|
AddAssert("check hit lighting", () => catcher.ChildrenOfType<HitExplosion>().Any() == enabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkPlate(int count) => AddAssert($"{count} objects on the plate", () => catcher.CaughtObjects.Count() == count);
|
||||||
|
|
||||||
|
private void checkState(CatcherAnimationState state) => AddAssert($"catcher state is {state}", () => catcher.CurrentState == state);
|
||||||
|
|
||||||
|
private void checkHyperDash(bool state) => AddAssert($"catcher is {(state ? "" : "not ")}hyper dashing", () => catcher.HyperDashing == state);
|
||||||
|
|
||||||
|
private void attemptCatch(CatchHitObject hitObject, int count = 1)
|
||||||
|
{
|
||||||
|
hitObject.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
|
||||||
|
|
||||||
|
for (var i = 0; i < count; i++)
|
||||||
|
catcher.AttemptCatch(hitObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class TestCatcher : Catcher
|
||||||
|
{
|
||||||
|
public IEnumerable<DrawablePalpableCatchHitObject> CaughtObjects => this.ChildrenOfType<DrawablePalpableCatchHitObject>();
|
||||||
|
|
||||||
|
public TestCatcher(Container trailsTarget, Container droppedObjectTarget, BeatmapDifficulty difficulty)
|
||||||
|
: base(trailsTarget, droppedObjectTarget, difficulty)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class TestKiaiFruit : Fruit
|
||||||
|
{
|
||||||
|
protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty)
|
||||||
|
{
|
||||||
|
controlPointInfo.Add(0, new EffectControlPoint { KiaiMode = true });
|
||||||
|
base.ApplyDefaultsToSelf(controlPointInfo, difficulty);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,18 +6,17 @@ using NUnit.Framework;
|
|||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Beatmaps.ControlPoints;
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
using osu.Game.Configuration;
|
using osu.Game.Configuration;
|
||||||
using osu.Game.Rulesets.Catch.Beatmaps;
|
|
||||||
using osu.Game.Rulesets.Catch.Judgements;
|
using osu.Game.Rulesets.Catch.Judgements;
|
||||||
using osu.Game.Rulesets.Catch.Objects;
|
using osu.Game.Rulesets.Catch.Objects;
|
||||||
using osu.Game.Rulesets.Catch.Objects.Drawables;
|
using osu.Game.Rulesets.Catch.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.Catch.UI;
|
using osu.Game.Rulesets.Catch.UI;
|
||||||
using osu.Game.Rulesets.Judgements;
|
using osu.Game.Rulesets.Judgements;
|
||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
using osu.Game.Rulesets.UI;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Tests
|
namespace osu.Game.Rulesets.Catch.Tests
|
||||||
{
|
{
|
||||||
@ -29,82 +28,68 @@ namespace osu.Game.Rulesets.Catch.Tests
|
|||||||
[Resolved]
|
[Resolved]
|
||||||
private OsuConfigManager config { get; set; }
|
private OsuConfigManager config { get; set; }
|
||||||
|
|
||||||
private Catcher catcher => this.ChildrenOfType<CatcherArea>().First().MovableCatcher;
|
private Catcher catcher => this.ChildrenOfType<Catcher>().First();
|
||||||
|
|
||||||
|
private float circleSize;
|
||||||
|
|
||||||
public TestSceneCatcherArea()
|
public TestSceneCatcherArea()
|
||||||
{
|
{
|
||||||
AddSliderStep<float>("CircleSize", 0, 8, 5, createCatcher);
|
AddSliderStep<float>("circle size", 0, 8, 5, createCatcher);
|
||||||
AddToggleStep("Hyperdash", t =>
|
AddToggleStep("hyper dash", t => this.ChildrenOfType<TestCatcherArea>().ForEach(area => area.ToggleHyperDash(t)));
|
||||||
CreatedDrawables.OfType<CatchInputManager>().Select(i => i.Child)
|
|
||||||
.OfType<TestCatcherArea>().ForEach(c => c.ToggleHyperDash(t)));
|
|
||||||
|
|
||||||
AddRepeatStep("catch fruit", () => catchFruit(new TestFruit(false)
|
AddStep("catch fruit", () => attemptCatch(new Fruit()));
|
||||||
{
|
AddStep("catch fruit last in combo", () => attemptCatch(new Fruit { LastInCombo = true }));
|
||||||
X = catcher.X
|
AddStep("catch kiai fruit", () => attemptCatch(new TestSceneCatcher.TestKiaiFruit()));
|
||||||
}), 20);
|
AddStep("miss last in combo", () => attemptCatch(new Fruit { X = 100, LastInCombo = true }));
|
||||||
AddRepeatStep("catch fruit last in combo", () => catchFruit(new TestFruit(false)
|
|
||||||
{
|
|
||||||
X = catcher.X,
|
|
||||||
LastInCombo = true,
|
|
||||||
}), 20);
|
|
||||||
AddRepeatStep("catch kiai fruit", () => catchFruit(new TestFruit(true)
|
|
||||||
{
|
|
||||||
X = catcher.X
|
|
||||||
}), 20);
|
|
||||||
AddRepeatStep("miss fruit", () => catchFruit(new Fruit
|
|
||||||
{
|
|
||||||
X = catcher.X + 100,
|
|
||||||
LastInCombo = true,
|
|
||||||
}, true), 20);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestCase(true)]
|
private void attemptCatch(Fruit fruit)
|
||||||
[TestCase(false)]
|
|
||||||
public void TestHitLighting(bool enable)
|
|
||||||
{
|
{
|
||||||
AddStep("create catcher", () => createCatcher(5));
|
fruit.X += catcher.X;
|
||||||
|
fruit.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty
|
||||||
|
{
|
||||||
|
CircleSize = circleSize
|
||||||
|
});
|
||||||
|
|
||||||
AddStep("toggle hit lighting", () => config.Set(OsuSetting.HitLighting, enable));
|
foreach (var area in this.ChildrenOfType<CatcherArea>())
|
||||||
AddStep("catch fruit", () => catchFruit(new TestFruit(false)
|
|
||||||
{
|
|
||||||
X = catcher.X
|
|
||||||
}));
|
|
||||||
AddStep("catch fruit last in combo", () => catchFruit(new TestFruit(false)
|
|
||||||
{
|
|
||||||
X = catcher.X,
|
|
||||||
LastInCombo = true
|
|
||||||
}));
|
|
||||||
AddAssert("check hit explosion", () => catcher.ChildrenOfType<HitExplosion>().Any() == enable);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void catchFruit(Fruit fruit, bool miss = false)
|
|
||||||
{
|
|
||||||
this.ChildrenOfType<CatcherArea>().ForEach(area =>
|
|
||||||
{
|
{
|
||||||
DrawableFruit drawable = new DrawableFruit(fruit);
|
DrawableFruit drawable = new DrawableFruit(fruit);
|
||||||
area.Add(drawable);
|
area.Add(drawable);
|
||||||
|
|
||||||
Schedule(() =>
|
Schedule(() =>
|
||||||
{
|
{
|
||||||
area.AttemptCatch(fruit);
|
bool caught = area.AttemptCatch(fruit);
|
||||||
area.OnNewResult(drawable, new JudgementResult(fruit, new CatchJudgement()) { Type = miss ? HitResult.Miss : HitResult.Great });
|
area.OnNewResult(drawable, new JudgementResult(fruit, new CatchJudgement())
|
||||||
|
{
|
||||||
|
Type = caught ? HitResult.Great : HitResult.Miss
|
||||||
|
});
|
||||||
|
|
||||||
drawable.Expire();
|
drawable.Expire();
|
||||||
});
|
});
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void createCatcher(float size)
|
private void createCatcher(float size)
|
||||||
{
|
{
|
||||||
SetContents(() => new CatchInputManager(catchRuleset)
|
circleSize = size;
|
||||||
|
|
||||||
|
SetContents(() =>
|
||||||
|
{
|
||||||
|
var droppedObjectContainer = new Container();
|
||||||
|
|
||||||
|
return new CatchInputManager(catchRuleset)
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Child = new TestCatcherArea(new BeatmapDifficulty { CircleSize = size })
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
droppedObjectContainer,
|
||||||
|
new TestCatcherArea(droppedObjectContainer, new BeatmapDifficulty { CircleSize = size })
|
||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.TopCentre,
|
Origin = Anchor.TopCentre,
|
||||||
CreateDrawableRepresentation = ((DrawableRuleset<CatchHitObject>)catchRuleset.CreateInstance().CreateDrawableRulesetWith(new CatchBeatmap())).CreateDrawableRepresentation
|
}
|
||||||
},
|
}
|
||||||
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -114,26 +99,13 @@ namespace osu.Game.Rulesets.Catch.Tests
|
|||||||
catchRuleset = rulesets.GetRuleset(2);
|
catchRuleset = rulesets.GetRuleset(2);
|
||||||
}
|
}
|
||||||
|
|
||||||
public class TestFruit : Fruit
|
|
||||||
{
|
|
||||||
public TestFruit(bool kiai)
|
|
||||||
{
|
|
||||||
var kiaiCpi = new ControlPointInfo();
|
|
||||||
kiaiCpi.Add(0, new EffectControlPoint { KiaiMode = kiai });
|
|
||||||
|
|
||||||
ApplyDefaultsToSelf(kiaiCpi, new BeatmapDifficulty());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class TestCatcherArea : CatcherArea
|
private class TestCatcherArea : CatcherArea
|
||||||
{
|
{
|
||||||
public TestCatcherArea(BeatmapDifficulty beatmapDifficulty)
|
public TestCatcherArea(Container droppedObjectContainer, BeatmapDifficulty beatmapDifficulty)
|
||||||
: base(beatmapDifficulty)
|
: base(droppedObjectContainer, beatmapDifficulty)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public new Catcher MovableCatcher => base.MovableCatcher;
|
|
||||||
|
|
||||||
public void ToggleHyperDash(bool status) => MovableCatcher.SetHyperDashState(status ? 2 : 1);
|
public void ToggleHyperDash(bool status) => MovableCatcher.SetHyperDashState(status ? 2 : 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -136,7 +136,7 @@ namespace osu.Game.Rulesets.Catch.Tests
|
|||||||
if (juice.NestedHitObjects.Last() is CatchHitObject tail)
|
if (juice.NestedHitObjects.Last() is CatchHitObject tail)
|
||||||
tail.LastInCombo = true; // usually the (Catch)BeatmapProcessor would do this for us when necessary
|
tail.LastInCombo = true; // usually the (Catch)BeatmapProcessor would do this for us when necessary
|
||||||
|
|
||||||
addToPlayfield(new DrawableJuiceStream(juice, drawableRuleset.CreateDrawableRepresentation));
|
addToPlayfield(new DrawableJuiceStream(juice));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void spawnBananas(bool hit = false)
|
private void spawnBananas(bool hit = false)
|
||||||
|
@ -1,12 +1,14 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System;
|
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Timing;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
using osu.Game.Rulesets.Catch.Objects;
|
using osu.Game.Rulesets.Catch.Objects;
|
||||||
using osu.Game.Rulesets.Catch.Objects.Drawables;
|
using osu.Game.Rulesets.Catch.Objects.Drawables;
|
||||||
using osuTK;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Tests
|
namespace osu.Game.Rulesets.Catch.Tests
|
||||||
{
|
{
|
||||||
@ -17,53 +19,69 @@ namespace osu.Game.Rulesets.Catch.Tests
|
|||||||
{
|
{
|
||||||
base.LoadComplete();
|
base.LoadComplete();
|
||||||
|
|
||||||
foreach (FruitVisualRepresentation rep in Enum.GetValues(typeof(FruitVisualRepresentation)))
|
AddStep("show pear", () => SetContents(() => createDrawableFruit(0)));
|
||||||
AddStep($"show {rep}", () => SetContents(() => createDrawableFruit(rep)));
|
AddStep("show grape", () => SetContents(() => createDrawableFruit(1)));
|
||||||
|
AddStep("show pineapple / apple", () => SetContents(() => createDrawableFruit(2)));
|
||||||
|
AddStep("show raspberry / orange", () => SetContents(() => createDrawableFruit(3)));
|
||||||
|
|
||||||
|
AddStep("show banana", () => SetContents(createDrawableBanana));
|
||||||
|
|
||||||
AddStep("show droplet", () => SetContents(() => createDrawableDroplet()));
|
AddStep("show droplet", () => SetContents(() => createDrawableDroplet()));
|
||||||
AddStep("show tiny droplet", () => SetContents(createDrawableTinyDroplet));
|
AddStep("show tiny droplet", () => SetContents(createDrawableTinyDroplet));
|
||||||
|
|
||||||
foreach (FruitVisualRepresentation rep in Enum.GetValues(typeof(FruitVisualRepresentation)))
|
AddStep("show hyperdash pear", () => SetContents(() => createDrawableFruit(0, true)));
|
||||||
AddStep($"show hyperdash {rep}", () => SetContents(() => createDrawableFruit(rep, true)));
|
AddStep("show hyperdash grape", () => SetContents(() => createDrawableFruit(1, true)));
|
||||||
|
AddStep("show hyperdash pineapple / apple", () => SetContents(() => createDrawableFruit(2, true)));
|
||||||
|
AddStep("show hyperdash raspberry / orange", () => SetContents(() => createDrawableFruit(3, true)));
|
||||||
|
|
||||||
AddStep("show hyperdash droplet", () => SetContents(() => createDrawableDroplet(true)));
|
AddStep("show hyperdash droplet", () => SetContents(() => createDrawableDroplet(true)));
|
||||||
}
|
}
|
||||||
|
|
||||||
private Drawable createDrawableFruit(FruitVisualRepresentation rep, bool hyperdash = false) =>
|
private Drawable createDrawableFruit(int indexInBeatmap, bool hyperdash = false) =>
|
||||||
setProperties(new DrawableFruit(new TestCatchFruit(rep)), hyperdash);
|
new TestDrawableCatchHitObjectSpecimen(new DrawableFruit(new Fruit
|
||||||
|
|
||||||
private Drawable createDrawableDroplet(bool hyperdash = false) => setProperties(new DrawableDroplet(new Droplet()), hyperdash);
|
|
||||||
|
|
||||||
private Drawable createDrawableTinyDroplet() => setProperties(new DrawableTinyDroplet(new TinyDroplet()));
|
|
||||||
|
|
||||||
private DrawableCatchHitObject setProperties(DrawableCatchHitObject d, bool hyperdash = false)
|
|
||||||
{
|
{
|
||||||
var hitObject = d.HitObject;
|
IndexInBeatmap = indexInBeatmap,
|
||||||
hitObject.StartTime = 1000000000000;
|
HyperDashBindable = { Value = hyperdash }
|
||||||
hitObject.Scale = 1.5f;
|
}));
|
||||||
|
|
||||||
if (hyperdash)
|
private Drawable createDrawableBanana() =>
|
||||||
hitObject.HyperDashTarget = new Banana();
|
new TestDrawableCatchHitObjectSpecimen(new DrawableBanana(new Banana()));
|
||||||
|
|
||||||
|
private Drawable createDrawableDroplet(bool hyperdash = false) =>
|
||||||
|
new TestDrawableCatchHitObjectSpecimen(new DrawableDroplet(new Droplet
|
||||||
|
{
|
||||||
|
HyperDashBindable = { Value = hyperdash }
|
||||||
|
}));
|
||||||
|
|
||||||
|
private Drawable createDrawableTinyDroplet() => new TestDrawableCatchHitObjectSpecimen(new DrawableTinyDroplet(new TinyDroplet()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public class TestDrawableCatchHitObjectSpecimen : CompositeDrawable
|
||||||
|
{
|
||||||
|
public readonly ManualClock ManualClock;
|
||||||
|
|
||||||
|
public TestDrawableCatchHitObjectSpecimen(DrawableCatchHitObject d)
|
||||||
|
{
|
||||||
|
AutoSizeAxes = Axes.Both;
|
||||||
|
Anchor = Anchor.Centre;
|
||||||
|
Origin = Anchor.Centre;
|
||||||
|
|
||||||
|
ManualClock = new ManualClock();
|
||||||
|
Clock = new FramedClock(ManualClock);
|
||||||
|
|
||||||
|
var hitObject = d.HitObject;
|
||||||
|
hitObject.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
|
||||||
|
hitObject.Scale = 1.5f;
|
||||||
|
hitObject.StartTime = 500;
|
||||||
|
|
||||||
d.Anchor = Anchor.Centre;
|
d.Anchor = Anchor.Centre;
|
||||||
d.RelativePositionAxes = Axes.None;
|
|
||||||
d.Position = Vector2.Zero;
|
|
||||||
d.HitObjectApplied += _ =>
|
d.HitObjectApplied += _ =>
|
||||||
{
|
{
|
||||||
d.LifetimeStart = double.NegativeInfinity;
|
d.LifetimeStart = double.NegativeInfinity;
|
||||||
d.LifetimeEnd = double.PositiveInfinity;
|
d.LifetimeEnd = double.PositiveInfinity;
|
||||||
};
|
};
|
||||||
return d;
|
|
||||||
}
|
|
||||||
|
|
||||||
public class TestCatchFruit : Fruit
|
InternalChild = d;
|
||||||
{
|
|
||||||
public TestCatchFruit(FruitVisualRepresentation rep)
|
|
||||||
{
|
|
||||||
VisualRepresentation = rep;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override FruitVisualRepresentation VisualRepresentation { get; }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
96
osu.Game.Rulesets.Catch.Tests/TestSceneFruitRandomness.cs
Normal file
96
osu.Game.Rulesets.Catch.Tests/TestSceneFruitRandomness.cs
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
// 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.Objects;
|
||||||
|
using osu.Game.Rulesets.Catch.Objects.Drawables;
|
||||||
|
using osu.Game.Tests.Visual;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Catch.Tests
|
||||||
|
{
|
||||||
|
public class TestSceneFruitRandomness : OsuTestScene
|
||||||
|
{
|
||||||
|
private readonly TestDrawableFruit drawableFruit;
|
||||||
|
private readonly TestDrawableBanana drawableBanana;
|
||||||
|
|
||||||
|
public TestSceneFruitRandomness()
|
||||||
|
{
|
||||||
|
drawableFruit = new TestDrawableFruit(new Fruit());
|
||||||
|
drawableBanana = new TestDrawableBanana(new Banana());
|
||||||
|
|
||||||
|
Add(new TestDrawableCatchHitObjectSpecimen(drawableFruit) { X = -200 });
|
||||||
|
Add(new TestDrawableCatchHitObjectSpecimen(drawableBanana));
|
||||||
|
|
||||||
|
AddSliderStep("start time", 500, 600, 0, x =>
|
||||||
|
{
|
||||||
|
drawableFruit.HitObject.StartTime = drawableBanana.HitObject.StartTime = x;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestFruitRandomness()
|
||||||
|
{
|
||||||
|
// Use values such that the banana colour changes (2/3 of the integers are okay)
|
||||||
|
const int initial_start_time = 500;
|
||||||
|
const int another_start_time = 501;
|
||||||
|
|
||||||
|
float fruitRotation = 0;
|
||||||
|
float bananaRotation = 0;
|
||||||
|
float bananaScale = 0;
|
||||||
|
Color4 bananaColour = new Color4();
|
||||||
|
|
||||||
|
AddStep("Initialize start time", () =>
|
||||||
|
{
|
||||||
|
drawableFruit.HitObject.StartTime = drawableBanana.HitObject.StartTime = initial_start_time;
|
||||||
|
|
||||||
|
fruitRotation = drawableFruit.InnerRotation;
|
||||||
|
bananaRotation = drawableBanana.InnerRotation;
|
||||||
|
bananaScale = drawableBanana.InnerScale;
|
||||||
|
bananaColour = drawableBanana.AccentColour.Value;
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("change start time", () =>
|
||||||
|
{
|
||||||
|
drawableFruit.HitObject.StartTime = drawableBanana.HitObject.StartTime = another_start_time;
|
||||||
|
});
|
||||||
|
|
||||||
|
AddAssert("fruit rotation is changed", () => drawableFruit.InnerRotation != fruitRotation);
|
||||||
|
AddAssert("banana rotation is changed", () => drawableBanana.InnerRotation != bananaRotation);
|
||||||
|
AddAssert("banana scale is changed", () => drawableBanana.InnerScale != bananaScale);
|
||||||
|
AddAssert("banana colour is changed", () => drawableBanana.AccentColour.Value != bananaColour);
|
||||||
|
|
||||||
|
AddStep("reset start time", () =>
|
||||||
|
{
|
||||||
|
drawableFruit.HitObject.StartTime = drawableBanana.HitObject.StartTime = initial_start_time;
|
||||||
|
});
|
||||||
|
|
||||||
|
AddAssert("rotation and scale restored", () =>
|
||||||
|
drawableFruit.InnerRotation == fruitRotation &&
|
||||||
|
drawableBanana.InnerRotation == bananaRotation &&
|
||||||
|
drawableBanana.InnerScale == bananaScale &&
|
||||||
|
drawableBanana.AccentColour.Value == bananaColour);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TestDrawableFruit : DrawableFruit
|
||||||
|
{
|
||||||
|
public float InnerRotation => ScaleContainer.Rotation;
|
||||||
|
|
||||||
|
public TestDrawableFruit(Fruit h)
|
||||||
|
: base(h)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TestDrawableBanana : DrawableBanana
|
||||||
|
{
|
||||||
|
public float InnerRotation => ScaleContainer.Rotation;
|
||||||
|
public float InnerScale => ScaleContainer.Scale.X;
|
||||||
|
|
||||||
|
public TestDrawableBanana(Banana h)
|
||||||
|
: base(h)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
32
osu.Game.Rulesets.Catch.Tests/TestSceneFruitVisualChange.cs
Normal file
32
osu.Game.Rulesets.Catch.Tests/TestSceneFruitVisualChange.cs
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Game.Rulesets.Catch.Objects;
|
||||||
|
using osu.Game.Rulesets.Catch.Objects.Drawables;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Catch.Tests
|
||||||
|
{
|
||||||
|
public class TestSceneFruitVisualChange : TestSceneFruitObjects
|
||||||
|
{
|
||||||
|
private readonly Bindable<int> indexInBeatmap = new Bindable<int>();
|
||||||
|
private readonly Bindable<bool> hyperDash = new Bindable<bool>();
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
AddStep("fruit changes visual and hyper", () => SetContents(() => new TestDrawableCatchHitObjectSpecimen(new DrawableFruit(new Fruit
|
||||||
|
{
|
||||||
|
IndexInBeatmapBindable = { BindTarget = indexInBeatmap },
|
||||||
|
HyperDashBindable = { BindTarget = hyperDash },
|
||||||
|
}))));
|
||||||
|
|
||||||
|
AddStep("droplet changes hyper", () => SetContents(() => new TestDrawableCatchHitObjectSpecimen(new DrawableDroplet(new Droplet
|
||||||
|
{
|
||||||
|
HyperDashBindable = { BindTarget = hyperDash },
|
||||||
|
}))));
|
||||||
|
|
||||||
|
Scheduler.AddDelayed(() => indexInBeatmap.Value++, 250, true);
|
||||||
|
Scheduler.AddDelayed(() => hyperDash.Value = !hyperDash.Value, 1000, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -117,7 +117,7 @@ namespace osu.Game.Rulesets.Catch.Tests
|
|||||||
|
|
||||||
AddStep("create hyper-dashing catcher", () =>
|
AddStep("create hyper-dashing catcher", () =>
|
||||||
{
|
{
|
||||||
Child = setupSkinHierarchy(catcherArea = new CatcherArea
|
Child = setupSkinHierarchy(catcherArea = new CatcherArea(new Container())
|
||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
<Import Project="..\osu.TestProject.props" />
|
<Import Project="..\osu.TestProject.props" />
|
||||||
<ItemGroup Label="Package References">
|
<ItemGroup Label="Package References">
|
||||||
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
|
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.1" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.0" />
|
||||||
<PackageReference Include="NUnit" Version="3.12.0" />
|
<PackageReference Include="NUnit" Version="3.12.0" />
|
||||||
<PackageReference Include="NUnit3TestAdapter" Version="3.17.0" />
|
<PackageReference Include="NUnit3TestAdapter" Version="3.17.0" />
|
||||||
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
|
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
|
||||||
|
@ -5,11 +5,11 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Rulesets.Catch.MathUtils;
|
||||||
using osu.Game.Rulesets.Catch.Objects;
|
using osu.Game.Rulesets.Catch.Objects;
|
||||||
using osu.Game.Rulesets.Catch.UI;
|
using osu.Game.Rulesets.Catch.UI;
|
||||||
using osu.Game.Rulesets.Objects.Types;
|
|
||||||
using osu.Game.Rulesets.Catch.MathUtils;
|
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Beatmaps
|
namespace osu.Game.Rulesets.Catch.Beatmaps
|
||||||
{
|
{
|
||||||
@ -192,24 +192,24 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
|
|||||||
|
|
||||||
private static void initialiseHyperDash(IBeatmap beatmap)
|
private static void initialiseHyperDash(IBeatmap beatmap)
|
||||||
{
|
{
|
||||||
List<CatchHitObject> objectWithDroplets = new List<CatchHitObject>();
|
List<PalpableCatchHitObject> palpableObjects = new List<PalpableCatchHitObject>();
|
||||||
|
|
||||||
foreach (var currentObject in beatmap.HitObjects)
|
foreach (var currentObject in beatmap.HitObjects)
|
||||||
{
|
{
|
||||||
if (currentObject is Fruit fruitObject)
|
if (currentObject is Fruit fruitObject)
|
||||||
objectWithDroplets.Add(fruitObject);
|
palpableObjects.Add(fruitObject);
|
||||||
|
|
||||||
if (currentObject is JuiceStream)
|
if (currentObject is JuiceStream)
|
||||||
{
|
{
|
||||||
foreach (var currentJuiceElement in currentObject.NestedHitObjects)
|
foreach (var juice in currentObject.NestedHitObjects)
|
||||||
{
|
{
|
||||||
if (!(currentJuiceElement is TinyDroplet))
|
if (juice is PalpableCatchHitObject palpableObject && !(juice is TinyDroplet))
|
||||||
objectWithDroplets.Add((CatchHitObject)currentJuiceElement);
|
palpableObjects.Add(palpableObject);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
objectWithDroplets.Sort((h1, h2) => h1.StartTime.CompareTo(h2.StartTime));
|
palpableObjects.Sort((h1, h2) => h1.StartTime.CompareTo(h2.StartTime));
|
||||||
|
|
||||||
double halfCatcherWidth = Catcher.CalculateCatchWidth(beatmap.BeatmapInfo.BaseDifficulty) / 2;
|
double halfCatcherWidth = Catcher.CalculateCatchWidth(beatmap.BeatmapInfo.BaseDifficulty) / 2;
|
||||||
|
|
||||||
@ -221,10 +221,10 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
|
|||||||
int lastDirection = 0;
|
int lastDirection = 0;
|
||||||
double lastExcess = halfCatcherWidth;
|
double lastExcess = halfCatcherWidth;
|
||||||
|
|
||||||
for (int i = 0; i < objectWithDroplets.Count - 1; i++)
|
for (int i = 0; i < palpableObjects.Count - 1; i++)
|
||||||
{
|
{
|
||||||
CatchHitObject currentObject = objectWithDroplets[i];
|
var currentObject = palpableObjects[i];
|
||||||
CatchHitObject nextObject = objectWithDroplets[i + 1];
|
var nextObject = palpableObjects[i + 1];
|
||||||
|
|
||||||
// Reset variables in-case values have changed (e.g. after applying HR)
|
// Reset variables in-case values have changed (e.g. after applying HR)
|
||||||
currentObject.HyperDashTarget = null;
|
currentObject.HyperDashTarget = null;
|
||||||
|
@ -12,9 +12,9 @@ namespace osu.Game.Rulesets.Catch.Difficulty.Preprocessing
|
|||||||
{
|
{
|
||||||
private const float normalized_hitobject_radius = 41.0f;
|
private const float normalized_hitobject_radius = 41.0f;
|
||||||
|
|
||||||
public new CatchHitObject BaseObject => (CatchHitObject)base.BaseObject;
|
public new PalpableCatchHitObject BaseObject => (PalpableCatchHitObject)base.BaseObject;
|
||||||
|
|
||||||
public new CatchHitObject LastObject => (CatchHitObject)base.LastObject;
|
public new PalpableCatchHitObject LastObject => (PalpableCatchHitObject)base.LastObject;
|
||||||
|
|
||||||
public readonly float NormalizedPosition;
|
public readonly float NormalizedPosition;
|
||||||
public readonly float LastNormalizedPosition;
|
public readonly float LastNormalizedPosition;
|
||||||
|
@ -5,7 +5,7 @@ using osu.Game.Rulesets.Mods;
|
|||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Mods
|
namespace osu.Game.Rulesets.Catch.Mods
|
||||||
{
|
{
|
||||||
public class CatchModEasy : ModEasy
|
public class CatchModEasy : ModEasyWithExtraLives
|
||||||
{
|
{
|
||||||
public override string Description => @"Larger fruits, more forgiving HP drain, less accuracy required, and three lives!";
|
public override string Description => @"Larger fruits, more forgiving HP drain, less accuracy required, and three lives!";
|
||||||
}
|
}
|
||||||
|
@ -1,23 +1,26 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using osu.Game.Audio;
|
using osu.Game.Audio;
|
||||||
using osu.Game.Rulesets.Catch.Judgements;
|
using osu.Game.Rulesets.Catch.Judgements;
|
||||||
using osu.Game.Rulesets.Judgements;
|
using osu.Game.Rulesets.Judgements;
|
||||||
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
|
using osu.Game.Utils;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Objects
|
namespace osu.Game.Rulesets.Catch.Objects
|
||||||
{
|
{
|
||||||
public class Banana : Fruit
|
public class Banana : Fruit, IHasComboInformation
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Index of banana in current shower.
|
/// Index of banana in current shower.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int BananaIndex;
|
public int BananaIndex;
|
||||||
|
|
||||||
public override FruitVisualRepresentation VisualRepresentation => FruitVisualRepresentation.Banana;
|
|
||||||
|
|
||||||
public override Judgement CreateJudgement() => new CatchBananaJudgement();
|
public override Judgement CreateJudgement() => new CatchBananaJudgement();
|
||||||
|
|
||||||
private static readonly List<HitSampleInfo> samples = new List<HitSampleInfo> { new BananaHitSampleInfo() };
|
private static readonly List<HitSampleInfo> samples = new List<HitSampleInfo> { new BananaHitSampleInfo() };
|
||||||
@ -27,15 +30,43 @@ namespace osu.Game.Rulesets.Catch.Objects
|
|||||||
Samples = samples;
|
Samples = samples;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// override any external colour changes with banananana
|
||||||
|
Color4 IHasComboInformation.GetComboColour(IReadOnlyList<Color4> comboColours) => getBananaColour();
|
||||||
|
|
||||||
|
private Color4 getBananaColour()
|
||||||
|
{
|
||||||
|
switch (StatelessRNG.NextInt(3, RandomSeed))
|
||||||
|
{
|
||||||
|
default:
|
||||||
|
return new Color4(255, 240, 0, 255);
|
||||||
|
|
||||||
|
case 1:
|
||||||
|
return new Color4(255, 192, 0, 255);
|
||||||
|
|
||||||
|
case 2:
|
||||||
|
return new Color4(214, 221, 28, 255);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private class BananaHitSampleInfo : HitSampleInfo, IEquatable<BananaHitSampleInfo>
|
private class BananaHitSampleInfo : HitSampleInfo, IEquatable<BananaHitSampleInfo>
|
||||||
{
|
{
|
||||||
private static readonly string[] lookup_names = { "metronomelow", "catch-banana" };
|
private static readonly string[] lookup_names = { "Gameplay/metronomelow", "Gameplay/catch-banana" };
|
||||||
|
|
||||||
public override IEnumerable<string> LookupNames => lookup_names;
|
public override IEnumerable<string> LookupNames => lookup_names;
|
||||||
|
|
||||||
public bool Equals(BananaHitSampleInfo other) => true;
|
public BananaHitSampleInfo(int volume = 0)
|
||||||
|
: base(string.Empty, volume: volume)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
public override bool Equals(object obj) => obj is BananaHitSampleInfo other && Equals(other);
|
public sealed override HitSampleInfo With(Optional<string> newName = default, Optional<string?> newBank = default, Optional<string?> newSuffix = default, Optional<int> newVolume = default)
|
||||||
|
=> new BananaHitSampleInfo(newVolume.GetOr(Volume));
|
||||||
|
|
||||||
|
public bool Equals(BananaHitSampleInfo? other)
|
||||||
|
=> other != null;
|
||||||
|
|
||||||
|
public override bool Equals(object? obj)
|
||||||
|
=> obj is BananaHitSampleInfo other && Equals(other);
|
||||||
|
|
||||||
public override int GetHashCode() => lookup_names.GetHashCode();
|
public override int GetHashCode() => lookup_names.GetHashCode();
|
||||||
}
|
}
|
||||||
|
@ -9,8 +9,6 @@ namespace osu.Game.Rulesets.Catch.Objects
|
|||||||
{
|
{
|
||||||
public class BananaShower : CatchHitObject, IHasDuration
|
public class BananaShower : CatchHitObject, IHasDuration
|
||||||
{
|
{
|
||||||
public override FruitVisualRepresentation VisualRepresentation => FruitVisualRepresentation.Banana;
|
|
||||||
|
|
||||||
public override bool LastInCombo => true;
|
public override bool LastInCombo => true;
|
||||||
|
|
||||||
public override Judgement CreateJudgement() => new IgnoreJudgement();
|
public override Judgement CreateJudgement() => new IgnoreJudgement();
|
||||||
|
@ -16,32 +16,47 @@ namespace osu.Game.Rulesets.Catch.Objects
|
|||||||
{
|
{
|
||||||
public const float OBJECT_RADIUS = 64;
|
public const float OBJECT_RADIUS = 64;
|
||||||
|
|
||||||
private float x;
|
// This value is after XOffset applied.
|
||||||
|
public readonly Bindable<float> XBindable = new Bindable<float>();
|
||||||
|
|
||||||
|
// This value is before XOffset applied.
|
||||||
|
private float originalX;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The horizontal position of the fruit between 0 and <see cref="CatchPlayfield.WIDTH"/>.
|
/// The horizontal position of the fruit between 0 and <see cref="CatchPlayfield.WIDTH"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public float X
|
public float X
|
||||||
{
|
{
|
||||||
get => x + XOffset;
|
// TODO: I don't like this asymmetry.
|
||||||
set => x = value;
|
get => XBindable.Value;
|
||||||
|
// originalX is set by `XBindable.BindValueChanged`
|
||||||
|
set => XBindable.Value = value + xOffset;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
private float xOffset;
|
||||||
/// Whether this object can be placed on the catcher's plate.
|
|
||||||
/// </summary>
|
|
||||||
public virtual bool CanBePlated => false;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A random offset applied to <see cref="X"/>, set by the <see cref="CatchBeatmapProcessor"/>.
|
/// A random offset applied to <see cref="X"/>, set by the <see cref="CatchBeatmapProcessor"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal float XOffset { get; set; }
|
internal float XOffset
|
||||||
|
{
|
||||||
|
get => xOffset;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
xOffset = value;
|
||||||
|
XBindable.Value = originalX + xOffset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public double TimePreempt = 1000;
|
public double TimePreempt = 1000;
|
||||||
|
|
||||||
public int IndexInBeatmap { get; set; }
|
public readonly Bindable<int> IndexInBeatmapBindable = new Bindable<int>();
|
||||||
|
|
||||||
public virtual FruitVisualRepresentation VisualRepresentation => (FruitVisualRepresentation)(IndexInBeatmap % 4);
|
public int IndexInBeatmap
|
||||||
|
{
|
||||||
|
get => IndexInBeatmapBindable.Value;
|
||||||
|
set => IndexInBeatmapBindable.Value = value;
|
||||||
|
}
|
||||||
|
|
||||||
public virtual bool NewCombo { get; set; }
|
public virtual bool NewCombo { get; set; }
|
||||||
|
|
||||||
@ -63,13 +78,6 @@ namespace osu.Game.Rulesets.Catch.Objects
|
|||||||
set => ComboIndexBindable.Value = value;
|
set => ComboIndexBindable.Value = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Difference between the distance to the next object
|
|
||||||
/// and the distance that would have triggered a hyper dash.
|
|
||||||
/// A value close to 0 indicates a difficult jump (for difficulty calculation).
|
|
||||||
/// </summary>
|
|
||||||
public float DistanceToHyperDash { get; set; }
|
|
||||||
|
|
||||||
public Bindable<bool> LastInComboBindable { get; } = new Bindable<bool>();
|
public Bindable<bool> LastInComboBindable { get; } = new Bindable<bool>();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -81,17 +89,19 @@ namespace osu.Game.Rulesets.Catch.Objects
|
|||||||
set => LastInComboBindable.Value = value;
|
set => LastInComboBindable.Value = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public float Scale { get; set; } = 1;
|
public readonly Bindable<float> ScaleBindable = new Bindable<float>(1);
|
||||||
|
|
||||||
|
public float Scale
|
||||||
|
{
|
||||||
|
get => ScaleBindable.Value;
|
||||||
|
set => ScaleBindable.Value = value;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether this fruit can initiate a hyperdash.
|
/// The seed value used for visual randomness such as fruit rotation.
|
||||||
|
/// The value is <see cref="HitObject.StartTime"/> truncated to an integer.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool HyperDash => HyperDashTarget != null;
|
public int RandomSeed => (int)StartTime;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The target fruit if we are to initiate a hyperdash.
|
|
||||||
/// </summary>
|
|
||||||
public CatchHitObject HyperDashTarget;
|
|
||||||
|
|
||||||
protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty)
|
protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty)
|
||||||
{
|
{
|
||||||
@ -103,22 +113,10 @@ namespace osu.Game.Rulesets.Catch.Objects
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected override HitWindows CreateHitWindows() => HitWindows.Empty;
|
protected override HitWindows CreateHitWindows() => HitWindows.Empty;
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
protected CatchHitObject()
|
||||||
/// Represents a single object that can be caught by the catcher.
|
|
||||||
/// </summary>
|
|
||||||
public abstract class PalpableCatchHitObject : CatchHitObject
|
|
||||||
{
|
{
|
||||||
public override bool CanBePlated => true;
|
XBindable.BindValueChanged(x => originalX = x.NewValue - xOffset);
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum FruitVisualRepresentation
|
|
||||||
{
|
|
||||||
Pear,
|
|
||||||
Grape,
|
|
||||||
Pineapple,
|
|
||||||
Raspberry,
|
|
||||||
Banana // banananananannaanana
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,26 +1,31 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System.Collections.Generic;
|
using JetBrains.Annotations;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Utils;
|
|
||||||
using osuTK.Graphics;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
||||||
{
|
{
|
||||||
public class DrawableBanana : DrawableFruit
|
public class DrawableBanana : DrawableFruit
|
||||||
{
|
{
|
||||||
public DrawableBanana(Banana h)
|
protected override FruitVisualRepresentation GetVisualRepresentation(int indexInBeatmap) => FruitVisualRepresentation.Banana;
|
||||||
|
|
||||||
|
public DrawableBanana()
|
||||||
|
: this(null)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public DrawableBanana([CanBeNull] Banana h)
|
||||||
: base(h)
|
: base(h)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
private Color4? colour;
|
protected override void LoadComplete()
|
||||||
|
|
||||||
protected override Color4 GetComboColour(IReadOnlyList<Color4> comboColours)
|
|
||||||
{
|
{
|
||||||
// override any external colour changes with banananana
|
base.LoadComplete();
|
||||||
return colour ??= getBananaColour();
|
|
||||||
|
// start time affects the random seed which is used to determine the banana colour
|
||||||
|
StartTimeBindable.BindValueChanged(_ => UpdateComboColour());
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void UpdateInitialTransforms()
|
protected override void UpdateInitialTransforms()
|
||||||
@ -30,14 +35,14 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
|||||||
const float end_scale = 0.6f;
|
const float end_scale = 0.6f;
|
||||||
const float random_scale_range = 1.6f;
|
const float random_scale_range = 1.6f;
|
||||||
|
|
||||||
ScaleContainer.ScaleTo(HitObject.Scale * (end_scale + random_scale_range * RNG.NextSingle()))
|
ScaleContainer.ScaleTo(HitObject.Scale * (end_scale + random_scale_range * RandomSingle(3)))
|
||||||
.Then().ScaleTo(HitObject.Scale * end_scale, HitObject.TimePreempt);
|
.Then().ScaleTo(HitObject.Scale * end_scale, HitObject.TimePreempt);
|
||||||
|
|
||||||
ScaleContainer.RotateTo(getRandomAngle())
|
ScaleContainer.RotateTo(getRandomAngle(1))
|
||||||
.Then()
|
.Then()
|
||||||
.RotateTo(getRandomAngle(), HitObject.TimePreempt);
|
.RotateTo(getRandomAngle(2), HitObject.TimePreempt);
|
||||||
|
|
||||||
float getRandomAngle() => 180 * (RNG.NextSingle() * 2 - 1);
|
float getRandomAngle(int series) => 180 * (RandomSingle(series) * 2 - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void PlaySamples()
|
public override void PlaySamples()
|
||||||
@ -46,20 +51,5 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
|||||||
if (Samples != null)
|
if (Samples != null)
|
||||||
Samples.Frequency.Value = 0.77f + ((Banana)HitObject).BananaIndex * 0.006f;
|
Samples.Frequency.Value = 0.77f + ((Banana)HitObject).BananaIndex * 0.006f;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Color4 getBananaColour()
|
|
||||||
{
|
|
||||||
switch (RNG.Next(0, 3))
|
|
||||||
{
|
|
||||||
default:
|
|
||||||
return new Color4(255, 240, 0, 255);
|
|
||||||
|
|
||||||
case 1:
|
|
||||||
return new Color4(255, 192, 0, 255);
|
|
||||||
|
|
||||||
case 2:
|
|
||||||
return new Color4(214, 221, 28, 255);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,26 +1,27 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System;
|
using JetBrains.Annotations;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Game.Rulesets.Objects;
|
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
||||||
{
|
{
|
||||||
public class DrawableBananaShower : DrawableCatchHitObject<BananaShower>
|
public class DrawableBananaShower : DrawableCatchHitObject
|
||||||
{
|
{
|
||||||
private readonly Func<CatchHitObject, DrawableHitObject<CatchHitObject>> createDrawableRepresentation;
|
|
||||||
private readonly Container bananaContainer;
|
private readonly Container bananaContainer;
|
||||||
|
|
||||||
public DrawableBananaShower(BananaShower s, Func<CatchHitObject, DrawableHitObject<CatchHitObject>> createDrawableRepresentation = null)
|
public DrawableBananaShower()
|
||||||
|
: this(null)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public DrawableBananaShower([CanBeNull] BananaShower s)
|
||||||
: base(s)
|
: base(s)
|
||||||
{
|
{
|
||||||
this.createDrawableRepresentation = createDrawableRepresentation;
|
|
||||||
RelativeSizeAxes = Axes.X;
|
RelativeSizeAxes = Axes.X;
|
||||||
Origin = Anchor.BottomLeft;
|
Origin = Anchor.BottomLeft;
|
||||||
X = 0;
|
|
||||||
|
|
||||||
AddInternal(bananaContainer = new Container { RelativeSizeAxes = Axes.Both });
|
AddInternal(bananaContainer = new Container { RelativeSizeAxes = Axes.Both });
|
||||||
}
|
}
|
||||||
@ -34,18 +35,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
|||||||
protected override void ClearNestedHitObjects()
|
protected override void ClearNestedHitObjects()
|
||||||
{
|
{
|
||||||
base.ClearNestedHitObjects();
|
base.ClearNestedHitObjects();
|
||||||
bananaContainer.Clear();
|
bananaContainer.Clear(false);
|
||||||
}
|
|
||||||
|
|
||||||
protected override DrawableHitObject CreateNestedHitObject(HitObject hitObject)
|
|
||||||
{
|
|
||||||
switch (hitObject)
|
|
||||||
{
|
|
||||||
case Banana banana:
|
|
||||||
return createDrawableRepresentation?.Invoke(banana)?.With(o => ((DrawableCatchHitObject)o).CheckPosition = p => CheckPosition?.Invoke(p) ?? false);
|
|
||||||
}
|
|
||||||
|
|
||||||
return base.CreateNestedHitObject(hitObject);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,77 +2,48 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using JetBrains.Annotations;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
|
||||||
using osu.Game.Rulesets.Catch.UI;
|
using osu.Game.Rulesets.Catch.UI;
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
using osuTK;
|
using osu.Game.Utils;
|
||||||
using osuTK.Graphics;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
||||||
{
|
{
|
||||||
public abstract class PalpableDrawableCatchHitObject<TObject> : DrawableCatchHitObject<TObject>
|
|
||||||
where TObject : PalpableCatchHitObject
|
|
||||||
{
|
|
||||||
protected Container ScaleContainer { get; private set; }
|
|
||||||
|
|
||||||
protected PalpableDrawableCatchHitObject(TObject hitObject)
|
|
||||||
: base(hitObject)
|
|
||||||
{
|
|
||||||
Origin = Anchor.Centre;
|
|
||||||
Size = new Vector2(CatchHitObject.OBJECT_RADIUS * 2);
|
|
||||||
Masking = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
|
||||||
private void load()
|
|
||||||
{
|
|
||||||
AddRangeInternal(new Drawable[]
|
|
||||||
{
|
|
||||||
ScaleContainer = new Container
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Origin = Anchor.Centre,
|
|
||||||
Anchor = Anchor.Centre,
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
ScaleContainer.Scale = new Vector2(HitObject.Scale);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override Color4 GetComboColour(IReadOnlyList<Color4> comboColours) =>
|
|
||||||
comboColours[(HitObject.IndexInBeatmap + 1) % comboColours.Count];
|
|
||||||
}
|
|
||||||
|
|
||||||
public abstract class DrawableCatchHitObject<TObject> : DrawableCatchHitObject
|
|
||||||
where TObject : CatchHitObject
|
|
||||||
{
|
|
||||||
public new TObject HitObject;
|
|
||||||
|
|
||||||
protected DrawableCatchHitObject(TObject hitObject)
|
|
||||||
: base(hitObject)
|
|
||||||
{
|
|
||||||
HitObject = hitObject;
|
|
||||||
Anchor = Anchor.BottomLeft;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public abstract class DrawableCatchHitObject : DrawableHitObject<CatchHitObject>
|
public abstract class DrawableCatchHitObject : DrawableHitObject<CatchHitObject>
|
||||||
{
|
{
|
||||||
|
public readonly Bindable<float> XBindable = new Bindable<float>();
|
||||||
|
|
||||||
protected override double InitialLifetimeOffset => HitObject.TimePreempt;
|
protected override double InitialLifetimeOffset => HitObject.TimePreempt;
|
||||||
|
|
||||||
public virtual bool StaysOnPlate => HitObject.CanBePlated;
|
|
||||||
|
|
||||||
public float DisplayRadius => DrawSize.X / 2 * Scale.X * HitObject.Scale;
|
|
||||||
|
|
||||||
protected override float SamplePlaybackPosition => HitObject.X / CatchPlayfield.WIDTH;
|
protected override float SamplePlaybackPosition => HitObject.X / CatchPlayfield.WIDTH;
|
||||||
|
|
||||||
protected DrawableCatchHitObject(CatchHitObject hitObject)
|
public int RandomSeed => HitObject?.RandomSeed ?? 0;
|
||||||
|
|
||||||
|
protected DrawableCatchHitObject([CanBeNull] CatchHitObject hitObject)
|
||||||
: base(hitObject)
|
: base(hitObject)
|
||||||
{
|
{
|
||||||
X = hitObject.X;
|
Anchor = Anchor.BottomLeft;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get a random number in range [0,1) based on seed <see cref="RandomSeed"/>.
|
||||||
|
/// </summary>
|
||||||
|
public float RandomSingle(int series) => StatelessRNG.NextSingle(RandomSeed, series);
|
||||||
|
|
||||||
|
protected override void OnApply()
|
||||||
|
{
|
||||||
|
base.OnApply();
|
||||||
|
|
||||||
|
XBindable.BindTo(HitObject.XBindable);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnFree()
|
||||||
|
{
|
||||||
|
base.OnFree();
|
||||||
|
|
||||||
|
XBindable.UnbindFrom(HitObject.XBindable);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Func<CatchHitObject, bool> CheckPosition;
|
public Func<CatchHitObject, bool> CheckPosition;
|
||||||
|
@ -1,18 +1,24 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using JetBrains.Annotations;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Utils;
|
using osu.Game.Rulesets.Catch.Objects.Drawables.Pieces;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
||||||
{
|
{
|
||||||
public class DrawableDroplet : PalpableDrawableCatchHitObject<Droplet>
|
public class DrawableDroplet : DrawablePalpableCatchHitObject
|
||||||
{
|
{
|
||||||
public override bool StaysOnPlate => false;
|
public override bool StaysOnPlate => false;
|
||||||
|
|
||||||
public DrawableDroplet(Droplet h)
|
public DrawableDroplet()
|
||||||
|
: this(null)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public DrawableDroplet([CanBeNull] CatchHitObject h)
|
||||||
: base(h)
|
: base(h)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
@ -20,7 +26,17 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
|||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load()
|
||||||
{
|
{
|
||||||
ScaleContainer.Child = new SkinnableDrawable(new CatchSkinComponent(CatchSkinComponents.Droplet), _ => new DropletPiece());
|
HyperDash.BindValueChanged(_ => updatePiece(), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updatePiece()
|
||||||
|
{
|
||||||
|
ScaleContainer.Child = new SkinnableDrawable(
|
||||||
|
new CatchSkinComponent(CatchSkinComponents.Droplet),
|
||||||
|
_ => new DropletPiece
|
||||||
|
{
|
||||||
|
HyperDash = { BindTarget = HyperDash }
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void UpdateInitialTransforms()
|
protected override void UpdateInitialTransforms()
|
||||||
@ -28,7 +44,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
|||||||
base.UpdateInitialTransforms();
|
base.UpdateInitialTransforms();
|
||||||
|
|
||||||
// roughly matches osu-stable
|
// roughly matches osu-stable
|
||||||
float startRotation = RNG.NextSingle() * 20;
|
float startRotation = RandomSingle(1) * 20;
|
||||||
double duration = HitObject.TimePreempt + 2000;
|
double duration = HitObject.TimePreempt + 2000;
|
||||||
|
|
||||||
ScaleContainer.RotateTo(startRotation).RotateTo(startRotation + 720, duration);
|
ScaleContainer.RotateTo(startRotation).RotateTo(startRotation + 720, duration);
|
||||||
|
@ -2,15 +2,27 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using JetBrains.Annotations;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Utils;
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Game.Rulesets.Catch.Objects.Drawables.Pieces;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
||||||
{
|
{
|
||||||
public class DrawableFruit : PalpableDrawableCatchHitObject<Fruit>
|
public class DrawableFruit : DrawablePalpableCatchHitObject
|
||||||
{
|
{
|
||||||
public DrawableFruit(Fruit h)
|
public readonly Bindable<FruitVisualRepresentation> VisualRepresentation = new Bindable<FruitVisualRepresentation>();
|
||||||
|
|
||||||
|
protected virtual FruitVisualRepresentation GetVisualRepresentation(int indexInBeatmap) => (FruitVisualRepresentation)(indexInBeatmap % 4);
|
||||||
|
|
||||||
|
public DrawableFruit()
|
||||||
|
: this(null)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public DrawableFruit([CanBeNull] Fruit h)
|
||||||
: base(h)
|
: base(h)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
@ -18,10 +30,31 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
|||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load()
|
||||||
{
|
{
|
||||||
ScaleContainer.Child = new SkinnableDrawable(
|
IndexInBeatmap.BindValueChanged(change =>
|
||||||
new CatchSkinComponent(getComponent(HitObject.VisualRepresentation)), _ => new FruitPiece());
|
{
|
||||||
|
VisualRepresentation.Value = GetVisualRepresentation(change.NewValue);
|
||||||
|
}, true);
|
||||||
|
|
||||||
ScaleContainer.Rotation = (float)(RNG.NextDouble() - 0.5f) * 40;
|
VisualRepresentation.BindValueChanged(_ => updatePiece());
|
||||||
|
HyperDash.BindValueChanged(_ => updatePiece(), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void UpdateInitialTransforms()
|
||||||
|
{
|
||||||
|
base.UpdateInitialTransforms();
|
||||||
|
|
||||||
|
ScaleContainer.RotateTo((RandomSingle(1) - 0.5f) * 40);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updatePiece()
|
||||||
|
{
|
||||||
|
ScaleContainer.Child = new SkinnableDrawable(
|
||||||
|
new CatchSkinComponent(getComponent(VisualRepresentation.Value)),
|
||||||
|
_ => new FruitPiece
|
||||||
|
{
|
||||||
|
VisualRepresentation = { BindTarget = VisualRepresentation },
|
||||||
|
HyperDash = { BindTarget = HyperDash },
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private CatchSkinComponents getComponent(FruitVisualRepresentation hitObjectVisualRepresentation)
|
private CatchSkinComponents getComponent(FruitVisualRepresentation hitObjectVisualRepresentation)
|
||||||
@ -48,4 +81,13 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public enum FruitVisualRepresentation
|
||||||
|
{
|
||||||
|
Pear,
|
||||||
|
Grape,
|
||||||
|
Pineapple,
|
||||||
|
Raspberry,
|
||||||
|
Banana // banananananannaanana
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,37 +1,33 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System;
|
using JetBrains.Annotations;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Game.Rulesets.Objects;
|
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
using osuTK;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
||||||
{
|
{
|
||||||
public class DrawableJuiceStream : DrawableCatchHitObject<JuiceStream>
|
public class DrawableJuiceStream : DrawableCatchHitObject
|
||||||
{
|
{
|
||||||
private readonly Func<CatchHitObject, DrawableHitObject<CatchHitObject>> createDrawableRepresentation;
|
|
||||||
private readonly Container dropletContainer;
|
private readonly Container dropletContainer;
|
||||||
|
|
||||||
public override Vector2 OriginPosition => base.OriginPosition - new Vector2(0, CatchHitObject.OBJECT_RADIUS);
|
public DrawableJuiceStream()
|
||||||
|
: this(null)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
public DrawableJuiceStream(JuiceStream s, Func<CatchHitObject, DrawableHitObject<CatchHitObject>> createDrawableRepresentation = null)
|
public DrawableJuiceStream([CanBeNull] JuiceStream s)
|
||||||
: base(s)
|
: base(s)
|
||||||
{
|
{
|
||||||
this.createDrawableRepresentation = createDrawableRepresentation;
|
|
||||||
RelativeSizeAxes = Axes.X;
|
RelativeSizeAxes = Axes.X;
|
||||||
Origin = Anchor.BottomLeft;
|
Origin = Anchor.BottomLeft;
|
||||||
X = 0;
|
|
||||||
|
|
||||||
AddInternal(dropletContainer = new Container { RelativeSizeAxes = Axes.Both, });
|
AddInternal(dropletContainer = new Container { RelativeSizeAxes = Axes.Both, });
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void AddNestedHitObject(DrawableHitObject hitObject)
|
protected override void AddNestedHitObject(DrawableHitObject hitObject)
|
||||||
{
|
{
|
||||||
hitObject.Origin = Anchor.BottomCentre;
|
|
||||||
|
|
||||||
base.AddNestedHitObject(hitObject);
|
base.AddNestedHitObject(hitObject);
|
||||||
dropletContainer.Add(hitObject);
|
dropletContainer.Add(hitObject);
|
||||||
}
|
}
|
||||||
@ -39,19 +35,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
|||||||
protected override void ClearNestedHitObjects()
|
protected override void ClearNestedHitObjects()
|
||||||
{
|
{
|
||||||
base.ClearNestedHitObjects();
|
base.ClearNestedHitObjects();
|
||||||
dropletContainer.Clear();
|
dropletContainer.Clear(false);
|
||||||
}
|
|
||||||
|
|
||||||
protected override DrawableHitObject CreateNestedHitObject(HitObject hitObject)
|
|
||||||
{
|
|
||||||
switch (hitObject)
|
|
||||||
{
|
|
||||||
case CatchHitObject catchObject:
|
|
||||||
return createDrawableRepresentation?.Invoke(catchObject)?.With(o =>
|
|
||||||
((DrawableCatchHitObject)o).CheckPosition = p => CheckPosition?.Invoke(p) ?? false);
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new ArgumentException($"{nameof(hitObject)} must be of type {nameof(CatchHitObject)}.");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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 JetBrains.Annotations;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
||||||
|
{
|
||||||
|
public abstract class DrawablePalpableCatchHitObject : DrawableCatchHitObject
|
||||||
|
{
|
||||||
|
public new PalpableCatchHitObject HitObject => (PalpableCatchHitObject)base.HitObject;
|
||||||
|
|
||||||
|
public readonly Bindable<bool> HyperDash = new Bindable<bool>();
|
||||||
|
|
||||||
|
public readonly Bindable<float> ScaleBindable = new Bindable<float>(1);
|
||||||
|
|
||||||
|
public readonly Bindable<int> IndexInBeatmap = new Bindable<int>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The multiplicative factor applied to <see cref="ScaleContainer"/> scale relative to <see cref="HitObject"/> scale.
|
||||||
|
/// </summary>
|
||||||
|
protected virtual float ScaleFactor => 1;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether this hit object should stay on the catcher plate when the object is caught by the catcher.
|
||||||
|
/// </summary>
|
||||||
|
public virtual bool StaysOnPlate => true;
|
||||||
|
|
||||||
|
public float DisplayRadius => CatchHitObject.OBJECT_RADIUS * HitObject.Scale * ScaleFactor;
|
||||||
|
|
||||||
|
protected readonly Container ScaleContainer;
|
||||||
|
|
||||||
|
protected DrawablePalpableCatchHitObject([CanBeNull] CatchHitObject h)
|
||||||
|
: base(h)
|
||||||
|
{
|
||||||
|
Origin = Anchor.Centre;
|
||||||
|
Size = new Vector2(CatchHitObject.OBJECT_RADIUS * 2);
|
||||||
|
|
||||||
|
AddInternal(ScaleContainer = new Container
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
XBindable.BindValueChanged(x =>
|
||||||
|
{
|
||||||
|
if (!IsOnPlate) X = x.NewValue;
|
||||||
|
}, true);
|
||||||
|
|
||||||
|
ScaleBindable.BindValueChanged(scale =>
|
||||||
|
{
|
||||||
|
ScaleContainer.Scale = new Vector2(scale.NewValue * ScaleFactor);
|
||||||
|
}, true);
|
||||||
|
|
||||||
|
IndexInBeatmap.BindValueChanged(_ => UpdateComboColour());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnApply()
|
||||||
|
{
|
||||||
|
base.OnApply();
|
||||||
|
|
||||||
|
HyperDash.BindTo(HitObject.HyperDashBindable);
|
||||||
|
ScaleBindable.BindTo(HitObject.ScaleBindable);
|
||||||
|
IndexInBeatmap.BindTo(HitObject.IndexInBeatmapBindable);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnFree()
|
||||||
|
{
|
||||||
|
HyperDash.UnbindFrom(HitObject.HyperDashBindable);
|
||||||
|
ScaleBindable.UnbindFrom(HitObject.ScaleBindable);
|
||||||
|
IndexInBeatmap.UnbindFrom(HitObject.IndexInBeatmapBindable);
|
||||||
|
|
||||||
|
base.OnFree();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,21 +1,22 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using osu.Framework.Allocation;
|
using JetBrains.Annotations;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
||||||
{
|
{
|
||||||
public class DrawableTinyDroplet : DrawableDroplet
|
public class DrawableTinyDroplet : DrawableDroplet
|
||||||
{
|
{
|
||||||
public DrawableTinyDroplet(TinyDroplet h)
|
protected override float ScaleFactor => base.ScaleFactor / 2;
|
||||||
: base(h)
|
|
||||||
|
public DrawableTinyDroplet()
|
||||||
|
: this(null)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
public DrawableTinyDroplet([CanBeNull] TinyDroplet h)
|
||||||
private void load()
|
: base(h)
|
||||||
{
|
{
|
||||||
ScaleContainer.Scale /= 2;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,69 +0,0 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
|
||||||
|
|
||||||
using osu.Framework.Allocation;
|
|
||||||
using osu.Framework.Graphics;
|
|
||||||
using osu.Framework.Graphics.Containers;
|
|
||||||
using osu.Framework.Graphics.Shapes;
|
|
||||||
using osu.Game.Rulesets.Catch.Objects.Drawables.Pieces;
|
|
||||||
using osu.Game.Rulesets.Catch.UI;
|
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
|
||||||
using osuTK;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
|
||||||
{
|
|
||||||
public class DropletPiece : CompositeDrawable
|
|
||||||
{
|
|
||||||
public DropletPiece()
|
|
||||||
{
|
|
||||||
Size = new Vector2(CatchHitObject.OBJECT_RADIUS / 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
|
||||||
private void load(DrawableHitObject drawableObject)
|
|
||||||
{
|
|
||||||
DrawableCatchHitObject drawableCatchObject = (DrawableCatchHitObject)drawableObject;
|
|
||||||
var hitObject = drawableCatchObject.HitObject;
|
|
||||||
|
|
||||||
InternalChild = new Pulp
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
AccentColour = { BindTarget = drawableObject.AccentColour }
|
|
||||||
};
|
|
||||||
|
|
||||||
if (hitObject.HyperDash)
|
|
||||||
{
|
|
||||||
AddInternal(new Container
|
|
||||||
{
|
|
||||||
Anchor = Anchor.Centre,
|
|
||||||
Origin = Anchor.Centre,
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Size = new Vector2(2f),
|
|
||||||
Depth = 1,
|
|
||||||
Children = new Drawable[]
|
|
||||||
{
|
|
||||||
new Circle
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Anchor = Anchor.Centre,
|
|
||||||
Origin = Anchor.Centre,
|
|
||||||
BorderColour = Catcher.DEFAULT_HYPER_DASH_COLOUR,
|
|
||||||
BorderThickness = 6,
|
|
||||||
Children = new Drawable[]
|
|
||||||
{
|
|
||||||
new Box
|
|
||||||
{
|
|
||||||
AlwaysPresent = true,
|
|
||||||
Alpha = 0.3f,
|
|
||||||
Blending = BlendingParameters.Additive,
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Colour = Catcher.DEFAULT_HYPER_DASH_COLOUR,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,111 +0,0 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
|
||||||
|
|
||||||
using System;
|
|
||||||
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 osu.Game.Rulesets.Objects.Drawables;
|
|
||||||
using osuTK.Graphics;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
|
||||||
{
|
|
||||||
internal class FruitPiece : CompositeDrawable
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Because we're adding a border around the fruit, we need to scale down some.
|
|
||||||
/// </summary>
|
|
||||||
public const float RADIUS_ADJUST = 1.1f;
|
|
||||||
|
|
||||||
private Circle border;
|
|
||||||
private CatchHitObject hitObject;
|
|
||||||
|
|
||||||
public FruitPiece()
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both;
|
|
||||||
}
|
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
|
||||||
private void load(DrawableHitObject drawableObject)
|
|
||||||
{
|
|
||||||
DrawableCatchHitObject drawableCatchObject = (DrawableCatchHitObject)drawableObject;
|
|
||||||
hitObject = drawableCatchObject.HitObject;
|
|
||||||
|
|
||||||
AddRangeInternal(new[]
|
|
||||||
{
|
|
||||||
getFruitFor(drawableCatchObject.HitObject.VisualRepresentation),
|
|
||||||
border = new Circle
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Anchor = Anchor.Centre,
|
|
||||||
Origin = Anchor.Centre,
|
|
||||||
BorderColour = Color4.White,
|
|
||||||
BorderThickness = 6f * RADIUS_ADJUST,
|
|
||||||
Children = new Drawable[]
|
|
||||||
{
|
|
||||||
new Box
|
|
||||||
{
|
|
||||||
AlwaysPresent = true,
|
|
||||||
Alpha = 0,
|
|
||||||
RelativeSizeAxes = Axes.Both
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (hitObject.HyperDash)
|
|
||||||
{
|
|
||||||
AddInternal(new Circle
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Anchor = Anchor.Centre,
|
|
||||||
Origin = Anchor.Centre,
|
|
||||||
BorderColour = Catcher.DEFAULT_HYPER_DASH_COLOUR,
|
|
||||||
BorderThickness = 12f * RADIUS_ADJUST,
|
|
||||||
Children = new Drawable[]
|
|
||||||
{
|
|
||||||
new Box
|
|
||||||
{
|
|
||||||
AlwaysPresent = true,
|
|
||||||
Alpha = 0.3f,
|
|
||||||
Blending = BlendingParameters.Additive,
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Colour = Catcher.DEFAULT_HYPER_DASH_COLOUR,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void Update()
|
|
||||||
{
|
|
||||||
base.Update();
|
|
||||||
border.Alpha = (float)Math.Clamp((hitObject.StartTime - Time.Current) / 500, 0, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Drawable getFruitFor(FruitVisualRepresentation representation)
|
|
||||||
{
|
|
||||||
switch (representation)
|
|
||||||
{
|
|
||||||
case FruitVisualRepresentation.Pear:
|
|
||||||
return new PearPiece();
|
|
||||||
|
|
||||||
case FruitVisualRepresentation.Grape:
|
|
||||||
return new GrapePiece();
|
|
||||||
|
|
||||||
case FruitVisualRepresentation.Pineapple:
|
|
||||||
return new PineapplePiece();
|
|
||||||
|
|
||||||
case FruitVisualRepresentation.Banana:
|
|
||||||
return new BananaPiece();
|
|
||||||
|
|
||||||
case FruitVisualRepresentation.Raspberry:
|
|
||||||
return new RaspberryPiece();
|
|
||||||
}
|
|
||||||
|
|
||||||
return Empty();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -2,10 +2,9 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Game.Rulesets.Catch.Objects.Drawables.Pieces;
|
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
namespace osu.Game.Rulesets.Catch.Objects.Drawables.Pieces
|
||||||
{
|
{
|
||||||
public class BananaPiece : PulpFormation
|
public class BananaPiece : PulpFormation
|
||||||
{
|
{
|
@ -0,0 +1,31 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osuTK;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Catch.Objects.Drawables.Pieces
|
||||||
|
{
|
||||||
|
public class BorderPiece : Circle
|
||||||
|
{
|
||||||
|
public BorderPiece()
|
||||||
|
{
|
||||||
|
Size = new Vector2(CatchHitObject.OBJECT_RADIUS * 2);
|
||||||
|
Anchor = Anchor.Centre;
|
||||||
|
Origin = Anchor.Centre;
|
||||||
|
BorderColour = Color4.White;
|
||||||
|
BorderThickness = 6f * FruitPiece.RADIUS_ADJUST;
|
||||||
|
|
||||||
|
// Border is drawn only when there is a child drawable.
|
||||||
|
Child = new Box
|
||||||
|
{
|
||||||
|
AlwaysPresent = true,
|
||||||
|
Alpha = 0,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,37 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Catch.Objects.Drawables.Pieces
|
||||||
|
{
|
||||||
|
public class DropletPiece : CompositeDrawable
|
||||||
|
{
|
||||||
|
public readonly Bindable<bool> HyperDash = new Bindable<bool>();
|
||||||
|
|
||||||
|
public DropletPiece()
|
||||||
|
{
|
||||||
|
Size = new Vector2(CatchHitObject.OBJECT_RADIUS / 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(DrawableHitObject drawableObject)
|
||||||
|
{
|
||||||
|
InternalChild = new Pulp
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
AccentColour = { BindTarget = drawableObject.AccentColour }
|
||||||
|
};
|
||||||
|
|
||||||
|
if (HyperDash.Value)
|
||||||
|
{
|
||||||
|
AddInternal(new HyperDropletBorderPiece());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,79 @@
|
|||||||
|
// 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 JetBrains.Annotations;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Catch.Objects.Drawables.Pieces
|
||||||
|
{
|
||||||
|
internal class FruitPiece : CompositeDrawable
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Because we're adding a border around the fruit, we need to scale down some.
|
||||||
|
/// </summary>
|
||||||
|
public const float RADIUS_ADJUST = 1.1f;
|
||||||
|
|
||||||
|
public readonly Bindable<FruitVisualRepresentation> VisualRepresentation = new Bindable<FruitVisualRepresentation>();
|
||||||
|
public readonly Bindable<bool> HyperDash = new Bindable<bool>();
|
||||||
|
|
||||||
|
[CanBeNull]
|
||||||
|
private DrawableCatchHitObject drawableHitObject;
|
||||||
|
|
||||||
|
[CanBeNull]
|
||||||
|
private BorderPiece borderPiece;
|
||||||
|
|
||||||
|
public FruitPiece()
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both;
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader(permitNulls: true)]
|
||||||
|
private void load([CanBeNull] DrawableHitObject drawable)
|
||||||
|
{
|
||||||
|
drawableHitObject = (DrawableCatchHitObject)drawable;
|
||||||
|
|
||||||
|
AddInternal(getFruitFor(VisualRepresentation.Value));
|
||||||
|
|
||||||
|
// if it is not part of a DHO, the border is always invisible.
|
||||||
|
if (drawableHitObject != null)
|
||||||
|
AddInternal(borderPiece = new BorderPiece());
|
||||||
|
|
||||||
|
if (HyperDash.Value)
|
||||||
|
AddInternal(new HyperBorderPiece());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Update()
|
||||||
|
{
|
||||||
|
if (borderPiece != null && drawableHitObject?.HitObject != null)
|
||||||
|
borderPiece.Alpha = (float)Math.Clamp((drawableHitObject.HitObject.StartTime - Time.Current) / 500, 0, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Drawable getFruitFor(FruitVisualRepresentation representation)
|
||||||
|
{
|
||||||
|
switch (representation)
|
||||||
|
{
|
||||||
|
case FruitVisualRepresentation.Pear:
|
||||||
|
return new PearPiece();
|
||||||
|
|
||||||
|
case FruitVisualRepresentation.Grape:
|
||||||
|
return new GrapePiece();
|
||||||
|
|
||||||
|
case FruitVisualRepresentation.Pineapple:
|
||||||
|
return new PineapplePiece();
|
||||||
|
|
||||||
|
case FruitVisualRepresentation.Banana:
|
||||||
|
return new BananaPiece();
|
||||||
|
|
||||||
|
case FruitVisualRepresentation.Raspberry:
|
||||||
|
return new RaspberryPiece();
|
||||||
|
}
|
||||||
|
|
||||||
|
return Empty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -2,10 +2,9 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Game.Rulesets.Catch.Objects.Drawables.Pieces;
|
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
namespace osu.Game.Rulesets.Catch.Objects.Drawables.Pieces
|
||||||
{
|
{
|
||||||
public class GrapePiece : PulpFormation
|
public class GrapePiece : PulpFormation
|
||||||
{
|
{
|
@ -0,0 +1,22 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Game.Rulesets.Catch.UI;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Catch.Objects.Drawables.Pieces
|
||||||
|
{
|
||||||
|
public class HyperBorderPiece : BorderPiece
|
||||||
|
{
|
||||||
|
public HyperBorderPiece()
|
||||||
|
{
|
||||||
|
BorderColour = Catcher.DEFAULT_HYPER_DASH_COLOUR;
|
||||||
|
BorderThickness = 12f * FruitPiece.RADIUS_ADJUST;
|
||||||
|
|
||||||
|
Child.Alpha = 0.3f;
|
||||||
|
Child.Blending = BlendingParameters.Additive;
|
||||||
|
Child.Colour = Catcher.DEFAULT_HYPER_DASH_COLOUR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,14 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Catch.Objects.Drawables.Pieces
|
||||||
|
{
|
||||||
|
public class HyperDropletBorderPiece : HyperBorderPiece
|
||||||
|
{
|
||||||
|
public HyperDropletBorderPiece()
|
||||||
|
{
|
||||||
|
Size /= 2;
|
||||||
|
BorderThickness = 6f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -2,10 +2,9 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Game.Rulesets.Catch.Objects.Drawables.Pieces;
|
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
namespace osu.Game.Rulesets.Catch.Objects.Drawables.Pieces
|
||||||
{
|
{
|
||||||
public class PearPiece : PulpFormation
|
public class PearPiece : PulpFormation
|
||||||
{
|
{
|
@ -2,10 +2,9 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Game.Rulesets.Catch.Objects.Drawables.Pieces;
|
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
namespace osu.Game.Rulesets.Catch.Objects.Drawables.Pieces
|
||||||
{
|
{
|
||||||
public class PineapplePiece : PulpFormation
|
public class PineapplePiece : PulpFormation
|
||||||
{
|
{
|
@ -10,7 +10,7 @@ using osu.Game.Rulesets.Objects.Drawables;
|
|||||||
using osuTK;
|
using osuTK;
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
namespace osu.Game.Rulesets.Catch.Objects.Drawables.Pieces
|
||||||
{
|
{
|
||||||
public abstract class PulpFormation : CompositeDrawable
|
public abstract class PulpFormation : CompositeDrawable
|
||||||
{
|
{
|
@ -2,10 +2,9 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Game.Rulesets.Catch.Objects.Drawables.Pieces;
|
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
namespace osu.Game.Rulesets.Catch.Objects.Drawables.Pieces
|
||||||
{
|
{
|
||||||
public class RaspberryPiece : PulpFormation
|
public class RaspberryPiece : PulpFormation
|
||||||
{
|
{
|
@ -50,12 +50,7 @@ namespace osu.Game.Rulesets.Catch.Objects
|
|||||||
{
|
{
|
||||||
base.CreateNestedHitObjects(cancellationToken);
|
base.CreateNestedHitObjects(cancellationToken);
|
||||||
|
|
||||||
var dropletSamples = Samples.Select(s => new HitSampleInfo
|
var dropletSamples = Samples.Select(s => s.With(@"slidertick")).ToList();
|
||||||
{
|
|
||||||
Bank = s.Bank,
|
|
||||||
Name = @"slidertick",
|
|
||||||
Volume = s.Volume
|
|
||||||
}).ToList();
|
|
||||||
|
|
||||||
int nodeIndex = 0;
|
int nodeIndex = 0;
|
||||||
SliderEventDescriptor? lastEvent = null;
|
SliderEventDescriptor? lastEvent = null;
|
||||||
|
48
osu.Game.Rulesets.Catch/Objects/PalpableCatchHitObject.cs
Normal file
48
osu.Game.Rulesets.Catch/Objects/PalpableCatchHitObject.cs
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Catch.Objects
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a single object that can be caught by the catcher.
|
||||||
|
/// This includes normal fruits, droplets, and bananas but excludes objects that act only as a container of nested hit objects.
|
||||||
|
/// </summary>
|
||||||
|
public abstract class PalpableCatchHitObject : CatchHitObject, IHasComboInformation
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Difference between the distance to the next object
|
||||||
|
/// and the distance that would have triggered a hyper dash.
|
||||||
|
/// A value close to 0 indicates a difficult jump (for difficulty calculation).
|
||||||
|
/// </summary>
|
||||||
|
public float DistanceToHyperDash { get; set; }
|
||||||
|
|
||||||
|
public readonly Bindable<bool> HyperDashBindable = new Bindable<bool>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether this fruit can initiate a hyperdash.
|
||||||
|
/// </summary>
|
||||||
|
public bool HyperDash => HyperDashBindable.Value;
|
||||||
|
|
||||||
|
private CatchHitObject hyperDashTarget;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The target fruit if we are to initiate a hyperdash.
|
||||||
|
/// </summary>
|
||||||
|
public CatchHitObject HyperDashTarget
|
||||||
|
{
|
||||||
|
get => hyperDashTarget;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
hyperDashTarget = value;
|
||||||
|
HyperDashBindable.Value = value != null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Color4 IHasComboInformation.GetComboColour(IReadOnlyList<Color4> comboColours) => comboColours[(IndexInBeatmap + 1) % comboColours.Count];
|
||||||
|
}
|
||||||
|
}
|
@ -40,7 +40,7 @@ namespace osu.Game.Rulesets.Catch.Replays
|
|||||||
float lastPosition = CatchPlayfield.CENTER_X;
|
float lastPosition = CatchPlayfield.CENTER_X;
|
||||||
double lastTime = 0;
|
double lastTime = 0;
|
||||||
|
|
||||||
void moveToNext(CatchHitObject h)
|
void moveToNext(PalpableCatchHitObject h)
|
||||||
{
|
{
|
||||||
float positionChange = Math.Abs(lastPosition - h.X);
|
float positionChange = Math.Abs(lastPosition - h.X);
|
||||||
double timeAvailable = h.StartTime - lastTime;
|
double timeAvailable = h.StartTime - lastTime;
|
||||||
@ -101,23 +101,16 @@ namespace osu.Game.Rulesets.Catch.Replays
|
|||||||
|
|
||||||
foreach (var obj in Beatmap.HitObjects)
|
foreach (var obj in Beatmap.HitObjects)
|
||||||
{
|
{
|
||||||
switch (obj)
|
if (obj is PalpableCatchHitObject palpableObject)
|
||||||
{
|
{
|
||||||
case Fruit _:
|
moveToNext(palpableObject);
|
||||||
moveToNext(obj);
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var nestedObj in obj.NestedHitObjects.Cast<CatchHitObject>())
|
foreach (var nestedObj in obj.NestedHitObjects.Cast<CatchHitObject>())
|
||||||
{
|
{
|
||||||
switch (nestedObj)
|
if (nestedObj is PalpableCatchHitObject palpableNestedObject)
|
||||||
{
|
{
|
||||||
case Banana _:
|
moveToNext(palpableNestedObject);
|
||||||
case TinyDroplet _:
|
|
||||||
case Droplet _:
|
|
||||||
case Fruit _:
|
|
||||||
moveToNext(nestedObj);
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,7 +19,8 @@ namespace osu.Game.Rulesets.Catch.Skinning
|
|||||||
{
|
{
|
||||||
private readonly string lookupName;
|
private readonly string lookupName;
|
||||||
|
|
||||||
private readonly IBindable<Color4> accentColour = new Bindable<Color4>();
|
private readonly Bindable<Color4> accentColour = new Bindable<Color4>();
|
||||||
|
private readonly Bindable<bool> hyperDash = new Bindable<bool>();
|
||||||
private Sprite colouredSprite;
|
private Sprite colouredSprite;
|
||||||
|
|
||||||
public LegacyFruitPiece(string lookupName)
|
public LegacyFruitPiece(string lookupName)
|
||||||
@ -31,9 +32,10 @@ namespace osu.Game.Rulesets.Catch.Skinning
|
|||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(DrawableHitObject drawableObject, ISkinSource skin)
|
private void load(DrawableHitObject drawableObject, ISkinSource skin)
|
||||||
{
|
{
|
||||||
DrawableCatchHitObject drawableCatchObject = (DrawableCatchHitObject)drawableObject;
|
var drawableCatchObject = (DrawablePalpableCatchHitObject)drawableObject;
|
||||||
|
|
||||||
accentColour.BindTo(drawableCatchObject.AccentColour);
|
accentColour.BindTo(drawableCatchObject.AccentColour);
|
||||||
|
hyperDash.BindTo(drawableCatchObject.HyperDash);
|
||||||
|
|
||||||
InternalChildren = new Drawable[]
|
InternalChildren = new Drawable[]
|
||||||
{
|
{
|
||||||
@ -51,9 +53,9 @@ namespace osu.Game.Rulesets.Catch.Skinning
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
if (drawableCatchObject.HitObject.HyperDash)
|
if (hyperDash.Value)
|
||||||
{
|
{
|
||||||
var hyperDash = new Sprite
|
var hyperDashOverlay = new Sprite
|
||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
@ -67,7 +69,7 @@ namespace osu.Game.Rulesets.Catch.Skinning
|
|||||||
Catcher.DEFAULT_HYPER_DASH_COLOUR,
|
Catcher.DEFAULT_HYPER_DASH_COLOUR,
|
||||||
};
|
};
|
||||||
|
|
||||||
AddInternal(hyperDash);
|
AddInternal(hyperDashOverlay);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
@ -35,41 +36,53 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
|
|
||||||
public CatchPlayfield(BeatmapDifficulty difficulty, Func<CatchHitObject, DrawableHitObject<CatchHitObject>> createDrawableRepresentation)
|
public CatchPlayfield(BeatmapDifficulty difficulty, Func<CatchHitObject, DrawableHitObject<CatchHitObject>> createDrawableRepresentation)
|
||||||
{
|
{
|
||||||
var explodingFruitContainer = new Container
|
var droppedObjectContainer = new Container
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
};
|
};
|
||||||
|
|
||||||
CatcherArea = new CatcherArea(difficulty)
|
CatcherArea = new CatcherArea(droppedObjectContainer, difficulty)
|
||||||
{
|
{
|
||||||
CreateDrawableRepresentation = createDrawableRepresentation,
|
|
||||||
ExplodingFruitTarget = explodingFruitContainer,
|
|
||||||
Anchor = Anchor.BottomLeft,
|
Anchor = Anchor.BottomLeft,
|
||||||
Origin = Anchor.TopLeft,
|
Origin = Anchor.TopLeft,
|
||||||
};
|
};
|
||||||
|
|
||||||
InternalChildren = new[]
|
InternalChildren = new[]
|
||||||
{
|
{
|
||||||
explodingFruitContainer,
|
droppedObjectContainer,
|
||||||
CatcherArea.MovableCatcher.CreateProxiedContent(),
|
CatcherArea.MovableCatcher.CreateProxiedContent(),
|
||||||
HitObjectContainer,
|
HitObjectContainer,
|
||||||
CatcherArea,
|
CatcherArea,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool CheckIfWeCanCatch(CatchHitObject obj) => CatcherArea.AttemptCatch(obj);
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
public override void Add(DrawableHitObject h)
|
|
||||||
{
|
{
|
||||||
h.OnNewResult += onNewResult;
|
RegisterPool<Droplet, DrawableDroplet>(50);
|
||||||
h.OnRevertResult += onRevertResult;
|
RegisterPool<TinyDroplet, DrawableTinyDroplet>(50);
|
||||||
|
RegisterPool<Fruit, DrawableFruit>(100);
|
||||||
base.Add(h);
|
RegisterPool<Banana, DrawableBanana>(100);
|
||||||
|
RegisterPool<JuiceStream, DrawableJuiceStream>(10);
|
||||||
var fruit = (DrawableCatchHitObject)h;
|
RegisterPool<BananaShower, DrawableBananaShower>(2);
|
||||||
fruit.CheckPosition = CheckIfWeCanCatch;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
// these subscriptions need to be done post constructor to ensure externally bound components have a chance to populate required fields (ScoreProcessor / ComboAtJudgement in this case).
|
||||||
|
NewResult += onNewResult;
|
||||||
|
RevertResult += onRevertResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnNewDrawableHitObject(DrawableHitObject d)
|
||||||
|
{
|
||||||
|
((DrawableCatchHitObject)d).CheckPosition = checkIfWeCanCatch;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool checkIfWeCanCatch(CatchHitObject obj) => CatcherArea.AttemptCatch(obj);
|
||||||
|
|
||||||
private void onNewResult(DrawableHitObject judgedObject, JudgementResult result)
|
private void onNewResult(DrawableHitObject judgedObject, JudgementResult result)
|
||||||
=> CatcherArea.OnNewResult((DrawableCatchHitObject)judgedObject, result);
|
=> CatcherArea.OnNewResult((DrawableCatchHitObject)judgedObject, result);
|
||||||
|
|
||||||
|
@ -9,6 +9,7 @@ using osu.Framework.Bindables;
|
|||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Animations;
|
using osu.Framework.Graphics.Animations;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Pooling;
|
||||||
using osu.Framework.Input.Bindings;
|
using osu.Framework.Input.Bindings;
|
||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
@ -16,7 +17,6 @@ using osu.Game.Configuration;
|
|||||||
using osu.Game.Rulesets.Catch.Objects;
|
using osu.Game.Rulesets.Catch.Objects;
|
||||||
using osu.Game.Rulesets.Catch.Objects.Drawables;
|
using osu.Game.Rulesets.Catch.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.Catch.Skinning;
|
using osu.Game.Rulesets.Catch.Skinning;
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
@ -46,19 +46,15 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public const double BASE_SPEED = 1.0;
|
public const double BASE_SPEED = 1.0;
|
||||||
|
|
||||||
public Container ExplodingFruitTarget;
|
|
||||||
|
|
||||||
private Container<DrawableHitObject> caughtFruitContainer { get; } = new Container<DrawableHitObject>
|
|
||||||
{
|
|
||||||
Anchor = Anchor.TopCentre,
|
|
||||||
Origin = Anchor.BottomCentre,
|
|
||||||
};
|
|
||||||
|
|
||||||
[NotNull]
|
[NotNull]
|
||||||
private readonly Container trailsTarget;
|
private readonly Container trailsTarget;
|
||||||
|
|
||||||
private CatcherTrailDisplay trails;
|
private CatcherTrailDisplay trails;
|
||||||
|
|
||||||
|
private readonly Container droppedObjectTarget;
|
||||||
|
|
||||||
|
private readonly Container<DrawablePalpableCatchHitObject> caughtFruitContainer;
|
||||||
|
|
||||||
public CatcherAnimationState CurrentState { get; private set; }
|
public CatcherAnimationState CurrentState { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -91,9 +87,9 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private readonly float catchWidth;
|
private readonly float catchWidth;
|
||||||
|
|
||||||
private CatcherSprite catcherIdle;
|
private readonly CatcherSprite catcherIdle;
|
||||||
private CatcherSprite catcherKiai;
|
private readonly CatcherSprite catcherKiai;
|
||||||
private CatcherSprite catcherFail;
|
private readonly CatcherSprite catcherFail;
|
||||||
|
|
||||||
private CatcherSprite currentCatcher;
|
private CatcherSprite currentCatcher;
|
||||||
|
|
||||||
@ -107,9 +103,13 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
private float hyperDashTargetPosition;
|
private float hyperDashTargetPosition;
|
||||||
private Bindable<bool> hitLighting;
|
private Bindable<bool> hitLighting;
|
||||||
|
|
||||||
public Catcher([NotNull] Container trailsTarget, BeatmapDifficulty difficulty = null)
|
private readonly DrawablePool<HitExplosion> hitExplosionPool;
|
||||||
|
private readonly Container<HitExplosion> hitExplosionContainer;
|
||||||
|
|
||||||
|
public Catcher([NotNull] Container trailsTarget, [NotNull] Container droppedObjectTarget, BeatmapDifficulty difficulty = null)
|
||||||
{
|
{
|
||||||
this.trailsTarget = trailsTarget;
|
this.trailsTarget = trailsTarget;
|
||||||
|
this.droppedObjectTarget = droppedObjectTarget;
|
||||||
|
|
||||||
Origin = Anchor.TopCentre;
|
Origin = Anchor.TopCentre;
|
||||||
|
|
||||||
@ -118,16 +118,15 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
Scale = calculateScale(difficulty);
|
Scale = calculateScale(difficulty);
|
||||||
|
|
||||||
catchWidth = CalculateCatchWidth(Scale);
|
catchWidth = CalculateCatchWidth(Scale);
|
||||||
}
|
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
|
||||||
private void load(OsuConfigManager config)
|
|
||||||
{
|
|
||||||
hitLighting = config.GetBindable<bool>(OsuSetting.HitLighting);
|
|
||||||
|
|
||||||
InternalChildren = new Drawable[]
|
InternalChildren = new Drawable[]
|
||||||
{
|
{
|
||||||
caughtFruitContainer,
|
hitExplosionPool = new DrawablePool<HitExplosion>(10),
|
||||||
|
caughtFruitContainer = new Container<DrawablePalpableCatchHitObject>
|
||||||
|
{
|
||||||
|
Anchor = Anchor.TopCentre,
|
||||||
|
Origin = Anchor.BottomCentre,
|
||||||
|
},
|
||||||
catcherIdle = new CatcherSprite(CatcherAnimationState.Idle)
|
catcherIdle = new CatcherSprite(CatcherAnimationState.Idle)
|
||||||
{
|
{
|
||||||
Anchor = Anchor.TopCentre,
|
Anchor = Anchor.TopCentre,
|
||||||
@ -142,9 +141,19 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
{
|
{
|
||||||
Anchor = Anchor.TopCentre,
|
Anchor = Anchor.TopCentre,
|
||||||
Alpha = 0,
|
Alpha = 0,
|
||||||
}
|
},
|
||||||
|
hitExplosionContainer = new Container<HitExplosion>
|
||||||
|
{
|
||||||
|
Anchor = Anchor.TopCentre,
|
||||||
|
Origin = Anchor.BottomCentre,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(OsuConfigManager config)
|
||||||
|
{
|
||||||
|
hitLighting = config.GetBindable<bool>(OsuSetting.HitLighting);
|
||||||
trails = new CatcherTrailDisplay(this);
|
trails = new CatcherTrailDisplay(this);
|
||||||
|
|
||||||
updateCatcher();
|
updateCatcher();
|
||||||
@ -166,65 +175,28 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Calculates the scale of the catcher based off the provided beatmap difficulty.
|
/// Calculates the scale of the catcher based off the provided beatmap difficulty.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private static Vector2 calculateScale(BeatmapDifficulty difficulty)
|
private static Vector2 calculateScale(BeatmapDifficulty difficulty) => new Vector2(1.0f - 0.7f * (difficulty.CircleSize - 5) / 5);
|
||||||
=> new Vector2(1.0f - 0.7f * (difficulty.CircleSize - 5) / 5);
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Calculates the width of the area used for attempting catches in gameplay.
|
/// Calculates the width of the area used for attempting catches in gameplay.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="scale">The scale of the catcher.</param>
|
/// <param name="scale">The scale of the catcher.</param>
|
||||||
internal static float CalculateCatchWidth(Vector2 scale)
|
internal static float CalculateCatchWidth(Vector2 scale) => CatcherArea.CATCHER_SIZE * Math.Abs(scale.X) * ALLOWED_CATCH_RANGE;
|
||||||
=> CatcherArea.CATCHER_SIZE * Math.Abs(scale.X) * ALLOWED_CATCH_RANGE;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Calculates the width of the area used for attempting catches in gameplay.
|
/// Calculates the width of the area used for attempting catches in gameplay.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="difficulty">The beatmap difficulty.</param>
|
/// <param name="difficulty">The beatmap difficulty.</param>
|
||||||
internal static float CalculateCatchWidth(BeatmapDifficulty difficulty)
|
internal static float CalculateCatchWidth(BeatmapDifficulty difficulty) => CalculateCatchWidth(calculateScale(difficulty));
|
||||||
=> CalculateCatchWidth(calculateScale(difficulty));
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Add a caught fruit to the catcher's stack.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="fruit">The fruit that was caught.</param>
|
|
||||||
public void PlaceOnPlate(DrawableCatchHitObject fruit)
|
|
||||||
{
|
|
||||||
var ourRadius = fruit.DisplayRadius;
|
|
||||||
float theirRadius = 0;
|
|
||||||
|
|
||||||
const float allowance = 10;
|
|
||||||
|
|
||||||
while (caughtFruitContainer.Any(f =>
|
|
||||||
f.LifetimeEnd == double.MaxValue &&
|
|
||||||
Vector2Extensions.Distance(f.Position, fruit.Position) < (ourRadius + (theirRadius = f.DrawSize.X / 2 * f.Scale.X)) / (allowance / 2)))
|
|
||||||
{
|
|
||||||
var diff = (ourRadius + theirRadius) / allowance;
|
|
||||||
fruit.X += (RNG.NextSingle() - 0.5f) * diff * 2;
|
|
||||||
fruit.Y -= RNG.NextSingle() * diff;
|
|
||||||
}
|
|
||||||
|
|
||||||
fruit.X = Math.Clamp(fruit.X, -CatcherArea.CATCHER_SIZE / 2, CatcherArea.CATCHER_SIZE / 2);
|
|
||||||
|
|
||||||
caughtFruitContainer.Add(fruit);
|
|
||||||
|
|
||||||
if (hitLighting.Value)
|
|
||||||
{
|
|
||||||
AddInternal(new HitExplosion(fruit)
|
|
||||||
{
|
|
||||||
X = fruit.X,
|
|
||||||
Scale = new Vector2(fruit.HitObject.Scale)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Let the catcher attempt to catch a fruit.
|
/// Let the catcher attempt to catch a fruit.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="fruit">The fruit to catch.</param>
|
/// <param name="hitObject">The fruit to catch.</param>
|
||||||
/// <returns>Whether the catch is possible.</returns>
|
/// <returns>Whether the catch is possible.</returns>
|
||||||
public bool AttemptCatch(CatchHitObject fruit)
|
public bool AttemptCatch(CatchHitObject hitObject)
|
||||||
{
|
{
|
||||||
if (!fruit.CanBePlated)
|
if (!(hitObject is PalpableCatchHitObject fruit))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
var halfCatchWidth = catchWidth * 0.5f;
|
var halfCatchWidth = catchWidth * 0.5f;
|
||||||
@ -237,7 +209,10 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
catchObjectPosition >= catcherPosition - halfCatchWidth &&
|
catchObjectPosition >= catcherPosition - halfCatchWidth &&
|
||||||
catchObjectPosition <= catcherPosition + halfCatchWidth;
|
catchObjectPosition <= catcherPosition + halfCatchWidth;
|
||||||
|
|
||||||
// only update hyperdash state if we are not catching a tiny droplet.
|
if (validCatch)
|
||||||
|
placeCaughtObject(fruit);
|
||||||
|
|
||||||
|
// droplet doesn't affect the catcher state
|
||||||
if (fruit is TinyDroplet) return validCatch;
|
if (fruit is TinyDroplet) return validCatch;
|
||||||
|
|
||||||
if (validCatch && fruit.HyperDash)
|
if (validCatch && fruit.HyperDash)
|
||||||
@ -291,23 +266,16 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void runHyperDashStateTransition(bool hyperDashing)
|
public void UpdatePosition(float position)
|
||||||
{
|
{
|
||||||
updateTrailVisibility();
|
position = Math.Clamp(position, 0, CatchPlayfield.WIDTH);
|
||||||
|
|
||||||
if (hyperDashing)
|
if (position == X)
|
||||||
{
|
return;
|
||||||
this.FadeColour(hyperDashColour, HYPER_DASH_TRANSITION_DURATION, Easing.OutQuint);
|
|
||||||
this.FadeTo(0.2f, HYPER_DASH_TRANSITION_DURATION, Easing.OutQuint);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
this.FadeColour(Color4.White, HYPER_DASH_TRANSITION_DURATION, Easing.OutQuint);
|
|
||||||
this.FadeTo(1f, HYPER_DASH_TRANSITION_DURATION, Easing.OutQuint);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateTrailVisibility() => trails.DisplayTrail = Dashing || HyperDashing;
|
Scale = new Vector2(Math.Abs(Scale.X) * (position > X ? 1 : -1), Scale.Y);
|
||||||
|
X = position;
|
||||||
|
}
|
||||||
|
|
||||||
public bool OnPressed(CatchAction action)
|
public bool OnPressed(CatchAction action)
|
||||||
{
|
{
|
||||||
@ -347,55 +315,33 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UpdatePosition(float position)
|
|
||||||
{
|
|
||||||
position = Math.Clamp(position, 0, CatchPlayfield.WIDTH);
|
|
||||||
|
|
||||||
if (position == X)
|
|
||||||
return;
|
|
||||||
|
|
||||||
Scale = new Vector2(Math.Abs(Scale.X) * (position > X ? 1 : -1), Scale.Y);
|
|
||||||
X = position;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Drop any fruit off the plate.
|
/// Drop any fruit off the plate.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void Drop()
|
public void Drop() => clearPlate(DroppedObjectAnimation.Drop);
|
||||||
{
|
|
||||||
foreach (var f in caughtFruitContainer.ToArray())
|
|
||||||
Drop(f);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Explode any fruit off the plate.
|
/// Explode all fruit off the plate.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void Explode()
|
public void Explode() => clearPlate(DroppedObjectAnimation.Explode);
|
||||||
|
|
||||||
|
private void runHyperDashStateTransition(bool hyperDashing)
|
||||||
{
|
{
|
||||||
foreach (var f in caughtFruitContainer.ToArray())
|
updateTrailVisibility();
|
||||||
Explode(f);
|
|
||||||
|
if (hyperDashing)
|
||||||
|
{
|
||||||
|
this.FadeColour(hyperDashColour, HYPER_DASH_TRANSITION_DURATION, Easing.OutQuint);
|
||||||
|
this.FadeTo(0.2f, HYPER_DASH_TRANSITION_DURATION, Easing.OutQuint);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
this.FadeColour(Color4.White, HYPER_DASH_TRANSITION_DURATION, Easing.OutQuint);
|
||||||
|
this.FadeTo(1f, HYPER_DASH_TRANSITION_DURATION, Easing.OutQuint);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Drop(DrawableHitObject fruit)
|
private void updateTrailVisibility() => trails.DisplayTrail = Dashing || HyperDashing;
|
||||||
{
|
|
||||||
removeFromPlateWithTransform(fruit, f =>
|
|
||||||
{
|
|
||||||
f.MoveToY(f.Y + 75, 750, Easing.InSine);
|
|
||||||
f.FadeOut(750);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Explode(DrawableHitObject fruit)
|
|
||||||
{
|
|
||||||
var originalX = fruit.X * Scale.X;
|
|
||||||
|
|
||||||
removeFromPlateWithTransform(fruit, f =>
|
|
||||||
{
|
|
||||||
f.MoveToY(f.Y - 50, 250, Easing.OutSine).Then().MoveToY(f.Y + 50, 500, Easing.InSine);
|
|
||||||
f.MoveToX(f.X + originalX * 6, 1000);
|
|
||||||
f.FadeOut(750);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void SkinChanged(ISkinSource skin, bool allowFallback)
|
protected override void SkinChanged(ISkinSource skin, bool allowFallback)
|
||||||
{
|
{
|
||||||
@ -469,33 +415,143 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
updateCatcher();
|
updateCatcher();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void removeFromPlateWithTransform(DrawableHitObject fruit, Action<DrawableHitObject> action)
|
private void placeCaughtObject(PalpableCatchHitObject source)
|
||||||
{
|
{
|
||||||
if (ExplodingFruitTarget != null)
|
var caughtObject = createCaughtObject(source);
|
||||||
{
|
|
||||||
fruit.Anchor = Anchor.TopLeft;
|
|
||||||
fruit.Position = caughtFruitContainer.ToSpaceOfOtherDrawable(fruit.DrawPosition, ExplodingFruitTarget);
|
|
||||||
|
|
||||||
if (!caughtFruitContainer.Remove(fruit))
|
if (caughtObject == null) return;
|
||||||
// we may have already been removed by a previous operation (due to the weird OnLoadComplete scheduling).
|
|
||||||
// this avoids a crash on potentially attempting to Add a fruit to ExplodingFruitTarget twice.
|
|
||||||
return;
|
|
||||||
|
|
||||||
ExplodingFruitTarget.Add(fruit);
|
caughtObject.RelativePositionAxes = Axes.None;
|
||||||
|
caughtObject.X = source.X - X;
|
||||||
|
caughtObject.IsOnPlate = true;
|
||||||
|
|
||||||
|
caughtObject.Anchor = Anchor.TopCentre;
|
||||||
|
caughtObject.Origin = Anchor.Centre;
|
||||||
|
caughtObject.Scale *= 0.5f;
|
||||||
|
caughtObject.LifetimeStart = source.StartTime;
|
||||||
|
caughtObject.LifetimeEnd = double.MaxValue;
|
||||||
|
|
||||||
|
adjustPositionInStack(caughtObject);
|
||||||
|
|
||||||
|
caughtFruitContainer.Add(caughtObject);
|
||||||
|
|
||||||
|
addLighting(caughtObject);
|
||||||
|
|
||||||
|
if (!caughtObject.StaysOnPlate)
|
||||||
|
removeFromPlate(caughtObject, DroppedObjectAnimation.Explode);
|
||||||
}
|
}
|
||||||
|
|
||||||
var actionTime = Clock.CurrentTime;
|
private void adjustPositionInStack(DrawablePalpableCatchHitObject caughtObject)
|
||||||
|
|
||||||
fruit.ApplyCustomUpdateState += onFruitOnApplyCustomUpdateState;
|
|
||||||
onFruitOnApplyCustomUpdateState(fruit, fruit.State.Value);
|
|
||||||
|
|
||||||
void onFruitOnApplyCustomUpdateState(DrawableHitObject o, ArmedState state)
|
|
||||||
{
|
{
|
||||||
using (fruit.BeginAbsoluteSequence(actionTime))
|
const float radius_div_2 = CatchHitObject.OBJECT_RADIUS / 2;
|
||||||
action(fruit);
|
const float allowance = 10;
|
||||||
|
|
||||||
fruit.Expire();
|
float caughtObjectRadius = caughtObject.DisplayRadius;
|
||||||
|
|
||||||
|
while (caughtFruitContainer.Any(f => Vector2Extensions.Distance(f.Position, caughtObject.Position) < (caughtObjectRadius + radius_div_2) / (allowance / 2)))
|
||||||
|
{
|
||||||
|
float diff = (caughtObjectRadius + radius_div_2) / allowance;
|
||||||
|
|
||||||
|
caughtObject.X += (RNG.NextSingle() - 0.5f) * diff * 2;
|
||||||
|
caughtObject.Y -= RNG.NextSingle() * diff;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
caughtObject.X = Math.Clamp(caughtObject.X, -CatcherArea.CATCHER_SIZE / 2, CatcherArea.CATCHER_SIZE / 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addLighting(DrawablePalpableCatchHitObject caughtObject)
|
||||||
|
{
|
||||||
|
if (!hitLighting.Value) return;
|
||||||
|
|
||||||
|
HitExplosion hitExplosion = hitExplosionPool.Get();
|
||||||
|
hitExplosion.X = caughtObject.X;
|
||||||
|
hitExplosion.Scale = new Vector2(caughtObject.HitObject.Scale);
|
||||||
|
hitExplosion.ObjectColour = caughtObject.AccentColour.Value;
|
||||||
|
hitExplosionContainer.Add(hitExplosion);
|
||||||
|
}
|
||||||
|
|
||||||
|
private DrawablePalpableCatchHitObject createCaughtObject(PalpableCatchHitObject source)
|
||||||
|
{
|
||||||
|
switch (source)
|
||||||
|
{
|
||||||
|
case Banana banana:
|
||||||
|
return new DrawableBanana(banana);
|
||||||
|
|
||||||
|
case Fruit fruit:
|
||||||
|
return new DrawableFruit(fruit);
|
||||||
|
|
||||||
|
case TinyDroplet tiny:
|
||||||
|
return new DrawableTinyDroplet(tiny);
|
||||||
|
|
||||||
|
case Droplet droplet:
|
||||||
|
return new DrawableDroplet(droplet);
|
||||||
|
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void clearPlate(DroppedObjectAnimation animation)
|
||||||
|
{
|
||||||
|
var caughtObjects = caughtFruitContainer.Children.ToArray();
|
||||||
|
caughtFruitContainer.Clear(false);
|
||||||
|
|
||||||
|
droppedObjectTarget.AddRange(caughtObjects);
|
||||||
|
|
||||||
|
foreach (var caughtObject in caughtObjects)
|
||||||
|
drop(caughtObject, animation);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void removeFromPlate(DrawablePalpableCatchHitObject caughtObject, DroppedObjectAnimation animation)
|
||||||
|
{
|
||||||
|
if (!caughtFruitContainer.Remove(caughtObject))
|
||||||
|
throw new InvalidOperationException("Can only drop a caught object on the plate");
|
||||||
|
|
||||||
|
droppedObjectTarget.Add(caughtObject);
|
||||||
|
|
||||||
|
drop(caughtObject, animation);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void drop(DrawablePalpableCatchHitObject d, DroppedObjectAnimation animation)
|
||||||
|
{
|
||||||
|
var originalX = d.X * Scale.X;
|
||||||
|
var startTime = Clock.CurrentTime;
|
||||||
|
|
||||||
|
d.Anchor = Anchor.TopLeft;
|
||||||
|
d.Position = caughtFruitContainer.ToSpaceOfOtherDrawable(d.DrawPosition, droppedObjectTarget);
|
||||||
|
|
||||||
|
// we cannot just apply the transforms because DHO clears transforms when state is updated
|
||||||
|
d.ApplyCustomUpdateState += (o, state) => animate(o, animation, originalX, startTime);
|
||||||
|
if (d.IsLoaded)
|
||||||
|
animate(d, animation, originalX, startTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void animate(Drawable d, DroppedObjectAnimation animation, float originalX, double startTime)
|
||||||
|
{
|
||||||
|
using (d.BeginAbsoluteSequence(startTime))
|
||||||
|
{
|
||||||
|
switch (animation)
|
||||||
|
{
|
||||||
|
case DroppedObjectAnimation.Drop:
|
||||||
|
d.MoveToY(d.Y + 75, 750, Easing.InSine);
|
||||||
|
d.FadeOut(750);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case DroppedObjectAnimation.Explode:
|
||||||
|
d.MoveToY(d.Y - 50, 250, Easing.OutSine).Then().MoveToY(d.Y + 50, 500, Easing.InSine);
|
||||||
|
d.MoveToX(d.X + originalX * 6, 1000);
|
||||||
|
d.FadeOut(750);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
d.Expire();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum DroppedObjectAnimation
|
||||||
|
{
|
||||||
|
Drop,
|
||||||
|
Explode
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System;
|
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
@ -10,7 +9,6 @@ using osu.Game.Rulesets.Catch.Objects;
|
|||||||
using osu.Game.Rulesets.Catch.Objects.Drawables;
|
using osu.Game.Rulesets.Catch.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.Catch.Replays;
|
using osu.Game.Rulesets.Catch.Replays;
|
||||||
using osu.Game.Rulesets.Judgements;
|
using osu.Game.Rulesets.Judgements;
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
|
||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
using osu.Game.Rulesets.UI;
|
using osu.Game.Rulesets.UI;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
@ -21,19 +19,10 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
{
|
{
|
||||||
public const float CATCHER_SIZE = 106.75f;
|
public const float CATCHER_SIZE = 106.75f;
|
||||||
|
|
||||||
public Func<CatchHitObject, DrawableHitObject<CatchHitObject>> CreateDrawableRepresentation;
|
|
||||||
|
|
||||||
public readonly Catcher MovableCatcher;
|
public readonly Catcher MovableCatcher;
|
||||||
private readonly CatchComboDisplay comboDisplay;
|
private readonly CatchComboDisplay comboDisplay;
|
||||||
|
|
||||||
public Container ExplodingFruitTarget
|
public CatcherArea(Container droppedObjectContainer, BeatmapDifficulty difficulty = null)
|
||||||
{
|
|
||||||
set => MovableCatcher.ExplodingFruitTarget = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
private DrawableCatchHitObject lastPlateableFruit;
|
|
||||||
|
|
||||||
public CatcherArea(BeatmapDifficulty difficulty = null)
|
|
||||||
{
|
{
|
||||||
Size = new Vector2(CatchPlayfield.WIDTH, CATCHER_SIZE);
|
Size = new Vector2(CatchPlayfield.WIDTH, CATCHER_SIZE);
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
@ -47,70 +36,29 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
Margin = new MarginPadding { Bottom = 350f },
|
Margin = new MarginPadding { Bottom = 350f },
|
||||||
X = CatchPlayfield.CENTER_X
|
X = CatchPlayfield.CENTER_X
|
||||||
},
|
},
|
||||||
MovableCatcher = new Catcher(this, difficulty) { X = CatchPlayfield.CENTER_X },
|
MovableCatcher = new Catcher(this, droppedObjectContainer, difficulty) { X = CatchPlayfield.CENTER_X },
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public void OnNewResult(DrawableCatchHitObject fruit, JudgementResult result)
|
public void OnNewResult(DrawableCatchHitObject hitObject, JudgementResult result)
|
||||||
{
|
{
|
||||||
if (!result.Type.IsScorable())
|
if (!result.Type.IsScorable())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
void runAfterLoaded(Action action)
|
if (hitObject.HitObject.LastInCombo)
|
||||||
{
|
|
||||||
if (lastPlateableFruit == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// this is required to make this run after the last caught fruit runs updateState() at least once.
|
|
||||||
// TODO: find a better alternative
|
|
||||||
if (lastPlateableFruit.IsLoaded)
|
|
||||||
action();
|
|
||||||
else
|
|
||||||
lastPlateableFruit.OnLoadComplete += _ => action();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (result.IsHit && fruit.HitObject.CanBePlated)
|
|
||||||
{
|
|
||||||
// create a new (cloned) fruit to stay on the plate. the original is faded out immediately.
|
|
||||||
var caughtFruit = (DrawableCatchHitObject)CreateDrawableRepresentation?.Invoke(fruit.HitObject);
|
|
||||||
|
|
||||||
if (caughtFruit == null) return;
|
|
||||||
|
|
||||||
caughtFruit.RelativePositionAxes = Axes.None;
|
|
||||||
caughtFruit.Position = new Vector2(MovableCatcher.ToLocalSpace(fruit.ScreenSpaceDrawQuad.Centre).X - MovableCatcher.DrawSize.X / 2, 0);
|
|
||||||
caughtFruit.IsOnPlate = true;
|
|
||||||
|
|
||||||
caughtFruit.Anchor = Anchor.TopCentre;
|
|
||||||
caughtFruit.Origin = Anchor.Centre;
|
|
||||||
caughtFruit.Scale *= 0.5f;
|
|
||||||
caughtFruit.LifetimeStart = caughtFruit.HitObject.StartTime;
|
|
||||||
caughtFruit.LifetimeEnd = double.MaxValue;
|
|
||||||
|
|
||||||
MovableCatcher.PlaceOnPlate(caughtFruit);
|
|
||||||
lastPlateableFruit = caughtFruit;
|
|
||||||
|
|
||||||
if (!fruit.StaysOnPlate)
|
|
||||||
runAfterLoaded(() => MovableCatcher.Explode(caughtFruit));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fruit.HitObject.LastInCombo)
|
|
||||||
{
|
{
|
||||||
if (result.Judgement is CatchJudgement catchJudgement && catchJudgement.ShouldExplodeFor(result))
|
if (result.Judgement is CatchJudgement catchJudgement && catchJudgement.ShouldExplodeFor(result))
|
||||||
runAfterLoaded(() => MovableCatcher.Explode());
|
MovableCatcher.Explode();
|
||||||
else
|
else
|
||||||
MovableCatcher.Drop();
|
MovableCatcher.Drop();
|
||||||
}
|
}
|
||||||
|
|
||||||
comboDisplay.OnNewResult(fruit, result);
|
comboDisplay.OnNewResult(hitObject, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void OnRevertResult(DrawableCatchHitObject fruit, JudgementResult result)
|
public void OnRevertResult(DrawableCatchHitObject fruit, JudgementResult result)
|
||||||
=> comboDisplay.OnRevertResult(fruit, result);
|
=> comboDisplay.OnRevertResult(fruit, result);
|
||||||
|
|
||||||
public void OnReleased(CatchAction action)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool AttemptCatch(CatchHitObject obj)
|
public bool AttemptCatch(CatchHitObject obj)
|
||||||
{
|
{
|
||||||
return MovableCatcher.AttemptCatch(obj);
|
return MovableCatcher.AttemptCatch(obj);
|
||||||
|
@ -8,7 +8,6 @@ using osu.Game.Configuration;
|
|||||||
using osu.Game.Input.Handlers;
|
using osu.Game.Input.Handlers;
|
||||||
using osu.Game.Replays;
|
using osu.Game.Replays;
|
||||||
using osu.Game.Rulesets.Catch.Objects;
|
using osu.Game.Rulesets.Catch.Objects;
|
||||||
using osu.Game.Rulesets.Catch.Objects.Drawables;
|
|
||||||
using osu.Game.Rulesets.Catch.Replays;
|
using osu.Game.Rulesets.Catch.Replays;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
@ -40,30 +39,6 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
|
|
||||||
protected override PassThroughInputManager CreateInputManager() => new CatchInputManager(Ruleset.RulesetInfo);
|
protected override PassThroughInputManager CreateInputManager() => new CatchInputManager(Ruleset.RulesetInfo);
|
||||||
|
|
||||||
public override DrawableHitObject<CatchHitObject> CreateDrawableRepresentation(CatchHitObject h)
|
public override DrawableHitObject<CatchHitObject> CreateDrawableRepresentation(CatchHitObject h) => null;
|
||||||
{
|
|
||||||
switch (h)
|
|
||||||
{
|
|
||||||
case Banana banana:
|
|
||||||
return new DrawableBanana(banana);
|
|
||||||
|
|
||||||
case Fruit fruit:
|
|
||||||
return new DrawableFruit(fruit);
|
|
||||||
|
|
||||||
case JuiceStream stream:
|
|
||||||
return new DrawableJuiceStream(stream, CreateDrawableRepresentation);
|
|
||||||
|
|
||||||
case BananaShower shower:
|
|
||||||
return new DrawableBananaShower(shower, CreateDrawableRepresentation);
|
|
||||||
|
|
||||||
case TinyDroplet tiny:
|
|
||||||
return new DrawableTinyDroplet(tiny);
|
|
||||||
|
|
||||||
case Droplet droplet:
|
|
||||||
return new DrawableDroplet(droplet);
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,35 +5,43 @@ using osu.Framework.Extensions.Color4Extensions;
|
|||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Effects;
|
using osu.Framework.Graphics.Effects;
|
||||||
|
using osu.Framework.Graphics.Pooling;
|
||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
using osu.Game.Rulesets.Catch.Objects.Drawables;
|
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.UI
|
namespace osu.Game.Rulesets.Catch.UI
|
||||||
{
|
{
|
||||||
public class HitExplosion : CompositeDrawable
|
public class HitExplosion : PoolableDrawable
|
||||||
{
|
{
|
||||||
private readonly CircularContainer largeFaint;
|
private Color4 objectColour;
|
||||||
|
|
||||||
public HitExplosion(DrawableCatchHitObject fruit)
|
public Color4 ObjectColour
|
||||||
|
{
|
||||||
|
get => objectColour;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (objectColour == value) return;
|
||||||
|
|
||||||
|
objectColour = value;
|
||||||
|
onColourChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly CircularContainer largeFaint;
|
||||||
|
private readonly CircularContainer smallFaint;
|
||||||
|
private readonly CircularContainer directionalGlow1;
|
||||||
|
private readonly CircularContainer directionalGlow2;
|
||||||
|
|
||||||
|
public HitExplosion()
|
||||||
{
|
{
|
||||||
Size = new Vector2(20);
|
Size = new Vector2(20);
|
||||||
Anchor = Anchor.TopCentre;
|
Anchor = Anchor.TopCentre;
|
||||||
Origin = Anchor.BottomCentre;
|
Origin = Anchor.BottomCentre;
|
||||||
|
|
||||||
Color4 objectColour = fruit.AccentColour.Value;
|
|
||||||
|
|
||||||
// scale roughly in-line with visual appearance of notes
|
// scale roughly in-line with visual appearance of notes
|
||||||
|
|
||||||
const float angle_variangle = 15; // should be less than 45
|
|
||||||
|
|
||||||
const float roundness = 100;
|
|
||||||
|
|
||||||
const float initial_height = 10;
|
const float initial_height = 10;
|
||||||
|
|
||||||
var colour = Interpolation.ValueAt(0.4f, objectColour, Color4.White, 0, 1);
|
|
||||||
|
|
||||||
InternalChildren = new Drawable[]
|
InternalChildren = new Drawable[]
|
||||||
{
|
{
|
||||||
largeFaint = new CircularContainer
|
largeFaint = new CircularContainer
|
||||||
@ -42,81 +50,84 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Masking = true,
|
Masking = true,
|
||||||
// we want our size to be very small so the glow dominates it.
|
|
||||||
Size = new Vector2(0.8f),
|
|
||||||
Blending = BlendingParameters.Additive,
|
Blending = BlendingParameters.Additive,
|
||||||
EdgeEffect = new EdgeEffectParameters
|
},
|
||||||
|
smallFaint = new CircularContainer
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Masking = true,
|
||||||
|
Blending = BlendingParameters.Additive,
|
||||||
|
},
|
||||||
|
directionalGlow1 = new CircularContainer
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Masking = true,
|
||||||
|
Size = new Vector2(0.01f, initial_height),
|
||||||
|
Blending = BlendingParameters.Additive,
|
||||||
|
},
|
||||||
|
directionalGlow2 = new CircularContainer
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Masking = true,
|
||||||
|
Size = new Vector2(0.01f, initial_height),
|
||||||
|
Blending = BlendingParameters.Additive,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void PrepareForUse()
|
||||||
|
{
|
||||||
|
base.PrepareForUse();
|
||||||
|
|
||||||
|
const double duration = 400;
|
||||||
|
|
||||||
|
// we want our size to be very small so the glow dominates it.
|
||||||
|
largeFaint.Size = new Vector2(0.8f);
|
||||||
|
largeFaint
|
||||||
|
.ResizeTo(largeFaint.Size * new Vector2(5, 1), duration, Easing.OutQuint)
|
||||||
|
.FadeOut(duration * 2);
|
||||||
|
|
||||||
|
const float angle_variangle = 15; // should be less than 45
|
||||||
|
directionalGlow1.Rotation = RNG.NextSingle(-angle_variangle, angle_variangle);
|
||||||
|
directionalGlow2.Rotation = RNG.NextSingle(-angle_variangle, angle_variangle);
|
||||||
|
|
||||||
|
this.FadeInFromZero(50).Then().FadeOut(duration, Easing.Out);
|
||||||
|
Expire(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onColourChanged()
|
||||||
|
{
|
||||||
|
const float roundness = 100;
|
||||||
|
|
||||||
|
largeFaint.EdgeEffect = new EdgeEffectParameters
|
||||||
{
|
{
|
||||||
Type = EdgeEffectType.Glow,
|
Type = EdgeEffectType.Glow,
|
||||||
Colour = Interpolation.ValueAt(0.1f, objectColour, Color4.White, 0, 1).Opacity(0.3f),
|
Colour = Interpolation.ValueAt(0.1f, objectColour, Color4.White, 0, 1).Opacity(0.3f),
|
||||||
Roundness = 160,
|
Roundness = 160,
|
||||||
Radius = 200,
|
Radius = 200,
|
||||||
},
|
};
|
||||||
},
|
|
||||||
new CircularContainer
|
smallFaint.EdgeEffect = new EdgeEffectParameters
|
||||||
{
|
|
||||||
Anchor = Anchor.Centre,
|
|
||||||
Origin = Anchor.Centre,
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Masking = true,
|
|
||||||
Blending = BlendingParameters.Additive,
|
|
||||||
EdgeEffect = new EdgeEffectParameters
|
|
||||||
{
|
{
|
||||||
Type = EdgeEffectType.Glow,
|
Type = EdgeEffectType.Glow,
|
||||||
Colour = Interpolation.ValueAt(0.6f, objectColour, Color4.White, 0, 1),
|
Colour = Interpolation.ValueAt(0.6f, objectColour, Color4.White, 0, 1),
|
||||||
Roundness = 20,
|
Roundness = 20,
|
||||||
Radius = 50,
|
Radius = 50,
|
||||||
},
|
|
||||||
},
|
|
||||||
new CircularContainer
|
|
||||||
{
|
|
||||||
Anchor = Anchor.Centre,
|
|
||||||
Origin = Anchor.Centre,
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Masking = true,
|
|
||||||
Size = new Vector2(0.01f, initial_height),
|
|
||||||
Blending = BlendingParameters.Additive,
|
|
||||||
Rotation = RNG.NextSingle(-angle_variangle, angle_variangle),
|
|
||||||
EdgeEffect = new EdgeEffectParameters
|
|
||||||
{
|
|
||||||
Type = EdgeEffectType.Glow,
|
|
||||||
Colour = colour,
|
|
||||||
Roundness = roundness,
|
|
||||||
Radius = 40,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
new CircularContainer
|
|
||||||
{
|
|
||||||
Anchor = Anchor.Centre,
|
|
||||||
Origin = Anchor.Centre,
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Masking = true,
|
|
||||||
Size = new Vector2(0.01f, initial_height),
|
|
||||||
Blending = BlendingParameters.Additive,
|
|
||||||
Rotation = RNG.NextSingle(-angle_variangle, angle_variangle),
|
|
||||||
EdgeEffect = new EdgeEffectParameters
|
|
||||||
{
|
|
||||||
Type = EdgeEffectType.Glow,
|
|
||||||
Colour = colour,
|
|
||||||
Roundness = roundness,
|
|
||||||
Radius = 40,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
}
|
|
||||||
|
|
||||||
protected override void LoadComplete()
|
directionalGlow1.EdgeEffect = directionalGlow2.EdgeEffect = new EdgeEffectParameters
|
||||||
{
|
{
|
||||||
base.LoadComplete();
|
Type = EdgeEffectType.Glow,
|
||||||
|
Colour = Interpolation.ValueAt(0.4f, objectColour, Color4.White, 0, 1),
|
||||||
const double duration = 400;
|
Roundness = roundness,
|
||||||
|
Radius = 40,
|
||||||
largeFaint
|
};
|
||||||
.ResizeTo(largeFaint.Size * new Vector2(5, 1), duration, Easing.OutQuint)
|
|
||||||
.FadeOut(duration * 2);
|
|
||||||
|
|
||||||
this.FadeInFromZero(50).Then().FadeOut(duration, Easing.Out);
|
|
||||||
Expire(true);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -96,6 +96,11 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor
|
|||||||
throw new System.NotImplementedException();
|
throw new System.NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override SnapResult SnapScreenSpacePositionToValidPosition(Vector2 screenSpacePosition)
|
||||||
|
{
|
||||||
|
throw new System.NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
public override float GetBeatSnapDistanceAt(double referenceTime)
|
public override float GetBeatSnapDistanceAt(double referenceTime)
|
||||||
{
|
{
|
||||||
throw new System.NotImplementedException();
|
throw new System.NotImplementedException();
|
||||||
|
@ -24,7 +24,10 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
|
|||||||
if (hitWindows.IsHitResultAllowed(result))
|
if (hitWindows.IsHitResultAllowed(result))
|
||||||
{
|
{
|
||||||
AddStep("Show " + result.GetDescription(), () => SetContents(() =>
|
AddStep("Show " + result.GetDescription(), () => SetContents(() =>
|
||||||
new DrawableManiaJudgement(new JudgementResult(new HitObject(), new Judgement()) { Type = result }, null)
|
new DrawableManiaJudgement(new JudgementResult(new HitObject { StartTime = Time.Current }, new Judgement())
|
||||||
|
{
|
||||||
|
Type = result
|
||||||
|
}, null)
|
||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
<Import Project="..\osu.TestProject.props" />
|
<Import Project="..\osu.TestProject.props" />
|
||||||
<ItemGroup Label="Package References">
|
<ItemGroup Label="Package References">
|
||||||
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
|
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.1" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.0" />
|
||||||
<PackageReference Include="NUnit" Version="3.12.0" />
|
<PackageReference Include="NUnit" Version="3.12.0" />
|
||||||
<PackageReference Include="NUnit3TestAdapter" Version="3.17.0" />
|
<PackageReference Include="NUnit3TestAdapter" Version="3.17.0" />
|
||||||
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
|
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
|
||||||
|
@ -78,9 +78,9 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
|
|||||||
|
|
||||||
private double originalStartTime;
|
private double originalStartTime;
|
||||||
|
|
||||||
public override void UpdatePosition(SnapResult result)
|
public override void UpdateTimeAndPosition(SnapResult result)
|
||||||
{
|
{
|
||||||
base.UpdatePosition(result);
|
base.UpdateTimeAndPosition(result);
|
||||||
|
|
||||||
if (PlacementActive)
|
if (PlacementActive)
|
||||||
{
|
{
|
||||||
|
@ -48,9 +48,9 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void UpdatePosition(SnapResult result)
|
public override void UpdateTimeAndPosition(SnapResult result)
|
||||||
{
|
{
|
||||||
base.UpdatePosition(result);
|
base.UpdateTimeAndPosition(result);
|
||||||
|
|
||||||
if (!PlacementActive)
|
if (!PlacementActive)
|
||||||
Column = result.Playfield as Column;
|
Column = result.Playfield as Column;
|
||||||
|
@ -22,9 +22,9 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
|
|||||||
InternalChild = piece = new EditNotePiece { Origin = Anchor.Centre };
|
InternalChild = piece = new EditNotePiece { Origin = Anchor.Centre };
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void UpdatePosition(SnapResult result)
|
public override void UpdateTimeAndPosition(SnapResult result)
|
||||||
{
|
{
|
||||||
base.UpdatePosition(result);
|
base.UpdateTimeAndPosition(result);
|
||||||
|
|
||||||
if (result.Playfield != null)
|
if (result.Playfield != null)
|
||||||
{
|
{
|
||||||
|
@ -5,7 +5,7 @@ using osu.Game.Rulesets.Mods;
|
|||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.Mods
|
namespace osu.Game.Rulesets.Mania.Mods
|
||||||
{
|
{
|
||||||
public class ManiaModEasy : ModEasy
|
public class ManiaModEasy : ModEasyWithExtraLives
|
||||||
{
|
{
|
||||||
public override string Description => @"More forgiving HP drain, less accuracy required, and three lives!";
|
public override string Description => @"More forgiving HP drain, less accuracy required, and three lives!";
|
||||||
}
|
}
|
||||||
|
@ -30,6 +30,8 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
|||||||
|
|
||||||
public void UpdateResult() => base.UpdateResult(true);
|
public void UpdateResult() => base.UpdateResult(true);
|
||||||
|
|
||||||
|
protected override double MaximumJudgementOffset => base.MaximumJudgementOffset * release_window_lenience;
|
||||||
|
|
||||||
protected override void CheckForResult(bool userTriggered, double timeOffset)
|
protected override void CheckForResult(bool userTriggered, double timeOffset)
|
||||||
{
|
{
|
||||||
Debug.Assert(HitObject.HitWindows != null);
|
Debug.Assert(HitObject.HitWindows != null);
|
||||||
|
@ -97,7 +97,7 @@ namespace osu.Game.Rulesets.Mania.UI
|
|||||||
DrawableManiaHitObject maniaObject = (DrawableManiaHitObject)hitObject;
|
DrawableManiaHitObject maniaObject = (DrawableManiaHitObject)hitObject;
|
||||||
maniaObject.CheckHittable = hitPolicy.IsHittable;
|
maniaObject.CheckHittable = hitPolicy.IsHittable;
|
||||||
|
|
||||||
HitObjectContainer.Add(hitObject);
|
base.Add(hitObject);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool Remove(DrawableHitObject h)
|
public override bool Remove(DrawableHitObject h)
|
||||||
|
@ -5,6 +5,7 @@ using osu.Framework.Graphics;
|
|||||||
using osu.Game.Rulesets.Judgements;
|
using osu.Game.Rulesets.Judgements;
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.UI
|
namespace osu.Game.Rulesets.Mania.UI
|
||||||
{
|
{
|
||||||
@ -19,22 +20,42 @@ namespace osu.Game.Rulesets.Mania.UI
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override double FadeInDuration => 50;
|
protected override void ApplyMissAnimations()
|
||||||
|
{
|
||||||
|
if (!(JudgementBody.Drawable is DefaultManiaJudgementPiece))
|
||||||
|
{
|
||||||
|
// this is temporary logic until mania's skin transformer returns IAnimatableJudgements
|
||||||
|
JudgementBody.ScaleTo(1.6f);
|
||||||
|
JudgementBody.ScaleTo(1, 100, Easing.In);
|
||||||
|
|
||||||
|
JudgementBody.MoveTo(Vector2.Zero);
|
||||||
|
JudgementBody.MoveToOffset(new Vector2(0, 100), 800, Easing.InQuint);
|
||||||
|
|
||||||
|
JudgementBody.RotateTo(0);
|
||||||
|
JudgementBody.RotateTo(40, 800, Easing.InQuint);
|
||||||
|
JudgementBody.FadeOutFromOne(800);
|
||||||
|
|
||||||
|
LifetimeEnd = JudgementBody.LatestTransformEndTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
base.ApplyMissAnimations();
|
||||||
|
}
|
||||||
|
|
||||||
protected override void ApplyHitAnimations()
|
protected override void ApplyHitAnimations()
|
||||||
{
|
{
|
||||||
JudgementBody.ScaleTo(0.8f);
|
JudgementBody.ScaleTo(0.8f);
|
||||||
JudgementBody.ScaleTo(1, 250, Easing.OutElastic);
|
JudgementBody.ScaleTo(1, 250, Easing.OutElastic);
|
||||||
|
|
||||||
JudgementBody.Delay(FadeInDuration).ScaleTo(0.75f, 250);
|
JudgementBody.Delay(50)
|
||||||
this.Delay(FadeInDuration).FadeOut(200);
|
.ScaleTo(0.75f, 250)
|
||||||
|
.FadeOut(200);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override Drawable CreateDefaultJudgement(HitResult result) => new ManiaJudgementPiece(result);
|
protected override Drawable CreateDefaultJudgement(HitResult result) => new DefaultManiaJudgementPiece(result);
|
||||||
|
|
||||||
private class ManiaJudgementPiece : DefaultJudgementPiece
|
private class DefaultManiaJudgementPiece : DefaultJudgementPiece
|
||||||
{
|
{
|
||||||
public ManiaJudgementPiece(HitResult result)
|
public DefaultManiaJudgementPiece(HitResult result)
|
||||||
: base(result)
|
: base(result)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,41 @@
|
|||||||
|
// 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.Beatmaps;
|
||||||
|
using osu.Game.Rulesets.Osu.UI;
|
||||||
|
using osu.Game.Tests.Beatmaps;
|
||||||
|
using osuTK.Input;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class TestSceneObjectBeatSnap : TestSceneOsuEditor
|
||||||
|
{
|
||||||
|
private OsuPlayfield playfield;
|
||||||
|
|
||||||
|
protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new TestBeatmap(Ruleset.Value, false);
|
||||||
|
|
||||||
|
public override void SetUpSteps()
|
||||||
|
{
|
||||||
|
base.SetUpSteps();
|
||||||
|
AddStep("get playfield", () => playfield = Editor.ChildrenOfType<OsuPlayfield>().First());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestBeatSnapHitCircle()
|
||||||
|
{
|
||||||
|
double firstTimingPointTime() => Beatmap.Value.Beatmap.ControlPointInfo.TimingPoints.First().Time;
|
||||||
|
|
||||||
|
AddStep("seek some milliseconds forward", () => EditorClock.Seek(firstTimingPointTime() + 10));
|
||||||
|
|
||||||
|
AddStep("move mouse to centre", () => InputManager.MoveMouseTo(playfield.ScreenSpaceDrawQuad.Centre));
|
||||||
|
AddStep("enter placement mode", () => InputManager.Key(Key.Number2));
|
||||||
|
AddStep("place first object", () => InputManager.Click(MouseButton.Left));
|
||||||
|
|
||||||
|
AddAssert("ensure object snapped back to correct time", () => EditorBeatmap.HitObjects.First().StartTime == firstTimingPointTime());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -25,6 +25,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
|||||||
{
|
{
|
||||||
base.SetUpSteps();
|
base.SetUpSteps();
|
||||||
AddStep("get playfield", () => playfield = Editor.ChildrenOfType<OsuPlayfield>().First());
|
AddStep("get playfield", () => playfield = Editor.ChildrenOfType<OsuPlayfield>().First());
|
||||||
|
AddStep("seek to first control point", () => EditorClock.Seek(Beatmap.Value.Beatmap.ControlPointInfo.TimingPoints.First().Time));
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestCase(true)]
|
[TestCase(true)]
|
||||||
@ -51,7 +52,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
|||||||
var first = (OsuHitObject)objects.First();
|
var first = (OsuHitObject)objects.First();
|
||||||
var second = (OsuHitObject)objects.Last();
|
var second = (OsuHitObject)objects.Last();
|
||||||
|
|
||||||
return first.Position == second.Position;
|
return Precision.AlmostEquals(first.EndPosition, second.Position);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -66,13 +67,13 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
|||||||
|
|
||||||
AddStep("start slider placement", () => InputManager.Click(MouseButton.Left));
|
AddStep("start slider placement", () => InputManager.Click(MouseButton.Left));
|
||||||
|
|
||||||
AddStep("move to place end", () => InputManager.MoveMouseTo(playfield.ScreenSpaceDrawQuad.Centre + new Vector2(playfield.ScreenSpaceDrawQuad.Width * 0.185f, 0)));
|
AddStep("move to place end", () => InputManager.MoveMouseTo(playfield.ScreenSpaceDrawQuad.Centre + new Vector2(playfield.ScreenSpaceDrawQuad.Width * 0.225f, 0)));
|
||||||
|
|
||||||
AddStep("end slider placement", () => InputManager.Click(MouseButton.Right));
|
AddStep("end slider placement", () => InputManager.Click(MouseButton.Right));
|
||||||
|
|
||||||
AddStep("enter circle placement mode", () => InputManager.Key(Key.Number2));
|
AddStep("enter circle placement mode", () => InputManager.Key(Key.Number2));
|
||||||
|
|
||||||
AddStep("move mouse slightly", () => InputManager.MoveMouseTo(playfield.ScreenSpaceDrawQuad.Centre + new Vector2(playfield.ScreenSpaceDrawQuad.Width * 0.20f, 0)));
|
AddStep("move mouse slightly", () => InputManager.MoveMouseTo(playfield.ScreenSpaceDrawQuad.Centre + new Vector2(playfield.ScreenSpaceDrawQuad.Width * 0.235f, 0)));
|
||||||
|
|
||||||
AddStep("place second object", () => InputManager.Click(MouseButton.Left));
|
AddStep("place second object", () => InputManager.Click(MouseButton.Left));
|
||||||
|
|
||||||
@ -86,5 +87,64 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
|||||||
return Precision.AlmostEquals(first.EndPosition, second.Position);
|
return Precision.AlmostEquals(first.EndPosition, second.Position);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestSecondCircleInSelectionAlsoSnaps()
|
||||||
|
{
|
||||||
|
AddStep("move mouse to centre", () => InputManager.MoveMouseTo(playfield.ScreenSpaceDrawQuad.Centre));
|
||||||
|
|
||||||
|
AddStep("disable distance snap", () => InputManager.Key(Key.Q));
|
||||||
|
|
||||||
|
AddStep("enter placement mode", () => InputManager.Key(Key.Number2));
|
||||||
|
|
||||||
|
AddStep("place first object", () => InputManager.Click(MouseButton.Left));
|
||||||
|
|
||||||
|
AddStep("increment time", () => EditorClock.SeekForward(true));
|
||||||
|
|
||||||
|
AddStep("move mouse right", () => InputManager.MoveMouseTo(playfield.ScreenSpaceDrawQuad.Centre + new Vector2(playfield.ScreenSpaceDrawQuad.Width * 0.2f, 0)));
|
||||||
|
AddStep("place second object", () => InputManager.Click(MouseButton.Left));
|
||||||
|
|
||||||
|
AddStep("increment time", () => EditorClock.SeekForward(true));
|
||||||
|
|
||||||
|
AddStep("move mouse down", () => InputManager.MoveMouseTo(playfield.ScreenSpaceDrawQuad.Centre + new Vector2(0, playfield.ScreenSpaceDrawQuad.Width * 0.2f)));
|
||||||
|
AddStep("place third object", () => InputManager.Click(MouseButton.Left));
|
||||||
|
|
||||||
|
AddStep("enter selection mode", () => InputManager.Key(Key.Number1));
|
||||||
|
|
||||||
|
AddStep("select objects 2 and 3", () =>
|
||||||
|
{
|
||||||
|
// add selection backwards to test non-sequential time ordering
|
||||||
|
EditorBeatmap.SelectedHitObjects.Add(EditorBeatmap.HitObjects[2]);
|
||||||
|
EditorBeatmap.SelectedHitObjects.Add(EditorBeatmap.HitObjects[1]);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("begin drag", () => InputManager.PressButton(MouseButton.Left));
|
||||||
|
|
||||||
|
AddStep("move mouse slightly off centre", () => InputManager.MoveMouseTo(playfield.ScreenSpaceDrawQuad.Centre + new Vector2(playfield.ScreenSpaceDrawQuad.Width * 0.02f, 0)));
|
||||||
|
|
||||||
|
AddAssert("object 3 snapped to 1", () =>
|
||||||
|
{
|
||||||
|
var objects = EditorBeatmap.HitObjects;
|
||||||
|
|
||||||
|
var first = (OsuHitObject)objects.First();
|
||||||
|
var third = (OsuHitObject)objects.Last();
|
||||||
|
|
||||||
|
return Precision.AlmostEquals(first.EndPosition, third.Position);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("move mouse slightly off centre", () => InputManager.MoveMouseTo(playfield.ScreenSpaceDrawQuad.Centre + new Vector2(playfield.ScreenSpaceDrawQuad.Width * -0.22f, playfield.ScreenSpaceDrawQuad.Width * 0.21f)));
|
||||||
|
|
||||||
|
AddAssert("object 2 snapped to 1", () =>
|
||||||
|
{
|
||||||
|
var objects = EditorBeatmap.HitObjects;
|
||||||
|
|
||||||
|
var first = (OsuHitObject)objects.First();
|
||||||
|
var second = (OsuHitObject)objects.ElementAt(1);
|
||||||
|
|
||||||
|
return Precision.AlmostEquals(first.EndPosition, second.Position);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("end drag", () => InputManager.ReleaseButton(MouseButton.Left));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -174,6 +174,9 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
|||||||
|
|
||||||
private class SnapProvider : IPositionSnapProvider
|
private class SnapProvider : IPositionSnapProvider
|
||||||
{
|
{
|
||||||
|
public SnapResult SnapScreenSpacePositionToValidPosition(Vector2 screenSpacePosition) =>
|
||||||
|
new SnapResult(screenSpacePosition, null);
|
||||||
|
|
||||||
public SnapResult SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition) => new SnapResult(screenSpacePosition, 0);
|
public SnapResult SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition) => new SnapResult(screenSpacePosition, 0);
|
||||||
|
|
||||||
public float GetBeatSnapDistanceAt(double referenceTime) => (float)beat_length;
|
public float GetBeatSnapDistanceAt(double referenceTime) => (float)beat_length;
|
||||||
|
@ -5,7 +5,7 @@ using osu.Game.Tests.Visual;
|
|||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Tests.Mods
|
namespace osu.Game.Rulesets.Osu.Tests.Mods
|
||||||
{
|
{
|
||||||
public class OsuModTestScene : ModTestScene
|
public abstract class OsuModTestScene : ModTestScene
|
||||||
{
|
{
|
||||||
protected override Ruleset CreatePlayerRuleset() => new OsuRuleset();
|
protected override Ruleset CreatePlayerRuleset() => new OsuRuleset();
|
||||||
}
|
}
|
||||||
|
@ -2,12 +2,15 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Testing;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.Objects.Types;
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
using osu.Game.Rulesets.Osu.Mods;
|
using osu.Game.Rulesets.Osu.Mods;
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
|
using osu.Game.Screens.Play;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Tests.Mods
|
namespace osu.Game.Rulesets.Osu.Tests.Mods
|
||||||
@ -17,15 +20,15 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestDefaultBeatmapTest() => CreateModTest(new ModTestData
|
public void TestDefaultBeatmapTest() => CreateModTest(new ModTestData
|
||||||
{
|
{
|
||||||
Mod = new OsuModHidden(),
|
Mod = new TestOsuModHidden(),
|
||||||
Autoplay = true,
|
Autoplay = true,
|
||||||
PassCondition = checkSomeHit
|
PassCondition = () => checkSomeHit() && objectWithIncreasedVisibilityHasIndex(0)
|
||||||
});
|
});
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void FirstCircleAfterTwoSpinners() => CreateModTest(new ModTestData
|
public void FirstCircleAfterTwoSpinners() => CreateModTest(new ModTestData
|
||||||
{
|
{
|
||||||
Mod = new OsuModHidden(),
|
Mod = new TestOsuModHidden(),
|
||||||
Autoplay = true,
|
Autoplay = true,
|
||||||
Beatmap = new Beatmap
|
Beatmap = new Beatmap
|
||||||
{
|
{
|
||||||
@ -54,13 +57,13 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
PassCondition = checkSomeHit
|
PassCondition = () => checkSomeHit() && objectWithIncreasedVisibilityHasIndex(2)
|
||||||
});
|
});
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void FirstSliderAfterTwoSpinners() => CreateModTest(new ModTestData
|
public void FirstSliderAfterTwoSpinners() => CreateModTest(new ModTestData
|
||||||
{
|
{
|
||||||
Mod = new OsuModHidden(),
|
Mod = new TestOsuModHidden(),
|
||||||
Autoplay = true,
|
Autoplay = true,
|
||||||
Beatmap = new Beatmap
|
Beatmap = new Beatmap
|
||||||
{
|
{
|
||||||
@ -89,12 +92,41 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
PassCondition = () => checkSomeHit() && objectWithIncreasedVisibilityHasIndex(2)
|
||||||
|
});
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestWithSliderReuse() => CreateModTest(new ModTestData
|
||||||
|
{
|
||||||
|
Mod = new TestOsuModHidden(),
|
||||||
|
Autoplay = true,
|
||||||
|
Beatmap = new Beatmap
|
||||||
|
{
|
||||||
|
HitObjects = new List<HitObject>
|
||||||
|
{
|
||||||
|
new Slider
|
||||||
|
{
|
||||||
|
StartTime = 1000,
|
||||||
|
Path = new SliderPath(PathType.Linear, new[] { Vector2.Zero, new Vector2(100, 0), })
|
||||||
|
},
|
||||||
|
new Slider
|
||||||
|
{
|
||||||
|
StartTime = 4000,
|
||||||
|
Path = new SliderPath(PathType.Linear, new[] { Vector2.Zero, new Vector2(100, 0), })
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
PassCondition = checkSomeHit
|
PassCondition = checkSomeHit
|
||||||
});
|
});
|
||||||
|
|
||||||
private bool checkSomeHit()
|
private bool checkSomeHit() => Player.ScoreProcessor.JudgedHits >= 4;
|
||||||
|
|
||||||
|
private bool objectWithIncreasedVisibilityHasIndex(int index)
|
||||||
|
=> Player.Mods.Value.OfType<TestOsuModHidden>().Single().FirstObject == Player.ChildrenOfType<GameplayBeatmap>().Single().HitObjects[index];
|
||||||
|
|
||||||
|
private class TestOsuModHidden : OsuModHidden
|
||||||
{
|
{
|
||||||
return Player.ScoreProcessor.JudgedHits >= 4;
|
public new HitObject FirstObject => base.FirstObject;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -43,10 +43,8 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
showResult(HitResult.Great);
|
showResult(HitResult.Great);
|
||||||
|
|
||||||
AddUntilStep("judgements shown", () => this.ChildrenOfType<TestDrawableOsuJudgement>().Any());
|
AddUntilStep("judgements shown", () => this.ChildrenOfType<TestDrawableOsuJudgement>().Any());
|
||||||
AddAssert("judgement body immediately visible",
|
AddAssert("hit lighting has no transforms", () => this.ChildrenOfType<TestDrawableOsuJudgement>().All(judgement => !judgement.Lighting.Transforms.Any()));
|
||||||
() => this.ChildrenOfType<TestDrawableOsuJudgement>().All(judgement => judgement.JudgementBody.Alpha == 1));
|
AddAssert("hit lighting hidden", () => this.ChildrenOfType<TestDrawableOsuJudgement>().All(judgement => judgement.Lighting.Alpha == 0));
|
||||||
AddAssert("hit lighting hidden",
|
|
||||||
() => this.ChildrenOfType<TestDrawableOsuJudgement>().All(judgement => judgement.Lighting.Alpha == 0));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -57,10 +55,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
showResult(HitResult.Great);
|
showResult(HitResult.Great);
|
||||||
|
|
||||||
AddUntilStep("judgements shown", () => this.ChildrenOfType<TestDrawableOsuJudgement>().Any());
|
AddUntilStep("judgements shown", () => this.ChildrenOfType<TestDrawableOsuJudgement>().Any());
|
||||||
AddAssert("judgement body not immediately visible",
|
AddUntilStep("hit lighting shown", () => this.ChildrenOfType<TestDrawableOsuJudgement>().Any(judgement => judgement.Lighting.Alpha > 0));
|
||||||
() => this.ChildrenOfType<TestDrawableOsuJudgement>().All(judgement => judgement.JudgementBody.Alpha > 0 && judgement.JudgementBody.Alpha < 1));
|
|
||||||
AddAssert("hit lighting shown",
|
|
||||||
() => this.ChildrenOfType<TestDrawableOsuJudgement>().All(judgement => judgement.Lighting.Alpha > 0));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void showResult(HitResult result)
|
private void showResult(HitResult result)
|
||||||
@ -89,7 +84,13 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
pool,
|
pool,
|
||||||
pool.Get(j => j.Apply(new JudgementResult(new HitObject(), new Judgement()) { Type = result }, null)).With(j =>
|
pool.Get(j => j.Apply(new JudgementResult(new HitObject
|
||||||
|
{
|
||||||
|
StartTime = Time.Current
|
||||||
|
}, new Judgement())
|
||||||
|
{
|
||||||
|
Type = result,
|
||||||
|
}, null)).With(j =>
|
||||||
{
|
{
|
||||||
j.Anchor = Anchor.Centre;
|
j.Anchor = Anchor.Centre;
|
||||||
j.Origin = Anchor.Centre;
|
j.Origin = Anchor.Centre;
|
||||||
@ -106,7 +107,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
private class TestDrawableOsuJudgement : DrawableOsuJudgement
|
private class TestDrawableOsuJudgement : DrawableOsuJudgement
|
||||||
{
|
{
|
||||||
public new SkinnableSprite Lighting => base.Lighting;
|
public new SkinnableSprite Lighting => base.Lighting;
|
||||||
public new Container JudgementBody => base.JudgementBody;
|
public new SkinnableDrawable JudgementBody => base.JudgementBody;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,7 @@ using NUnit.Framework;
|
|||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
|
using osu.Framework.Timing;
|
||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Beatmaps.ControlPoints;
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
@ -94,9 +95,19 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
{
|
{
|
||||||
addMultipleObjectsStep();
|
addMultipleObjectsStep();
|
||||||
|
|
||||||
AddStep("move hitobject", () => getObject(2).HitObject.Position = new Vector2(300, 100));
|
AddStep("move hitobject", () =>
|
||||||
|
{
|
||||||
|
var manualClock = new ManualClock();
|
||||||
|
followPointRenderer.Clock = new FramedClock(manualClock);
|
||||||
|
|
||||||
|
manualClock.CurrentTime = getObject(1).HitObject.StartTime;
|
||||||
|
followPointRenderer.UpdateSubTree();
|
||||||
|
|
||||||
|
getObject(2).HitObject.Position = new Vector2(300, 100);
|
||||||
|
});
|
||||||
|
|
||||||
assertGroups();
|
assertGroups();
|
||||||
|
assertDirections();
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestCase(0, 0)] // Start -> Start
|
[TestCase(0, 0)] // Start -> Start
|
||||||
@ -207,7 +218,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
|
|
||||||
private void assertGroups()
|
private void assertGroups()
|
||||||
{
|
{
|
||||||
AddAssert("has correct group count", () => followPointRenderer.Connections.Count == hitObjectContainer.Count);
|
AddAssert("has correct group count", () => followPointRenderer.Entries.Count == hitObjectContainer.Count);
|
||||||
AddAssert("group endpoints are correct", () =>
|
AddAssert("group endpoints are correct", () =>
|
||||||
{
|
{
|
||||||
for (int i = 0; i < hitObjectContainer.Count; i++)
|
for (int i = 0; i < hitObjectContainer.Count; i++)
|
||||||
@ -215,10 +226,10 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
DrawableOsuHitObject expectedStart = getObject(i);
|
DrawableOsuHitObject expectedStart = getObject(i);
|
||||||
DrawableOsuHitObject expectedEnd = i < hitObjectContainer.Count - 1 ? getObject(i + 1) : null;
|
DrawableOsuHitObject expectedEnd = i < hitObjectContainer.Count - 1 ? getObject(i + 1) : null;
|
||||||
|
|
||||||
if (getGroup(i).Start != expectedStart.HitObject)
|
if (getEntry(i).Start != expectedStart.HitObject)
|
||||||
throw new AssertionException($"Object {i} expected to be the start of group {i}.");
|
throw new AssertionException($"Object {i} expected to be the start of group {i}.");
|
||||||
|
|
||||||
if (getGroup(i).End != expectedEnd?.HitObject)
|
if (getEntry(i).End != expectedEnd?.HitObject)
|
||||||
throw new AssertionException($"Object {(expectedEnd == null ? "null" : i.ToString())} expected to be the end of group {i}.");
|
throw new AssertionException($"Object {(expectedEnd == null ? "null" : i.ToString())} expected to be the end of group {i}.");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -238,6 +249,12 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
if (expectedEnd == null)
|
if (expectedEnd == null)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
var manualClock = new ManualClock();
|
||||||
|
followPointRenderer.Clock = new FramedClock(manualClock);
|
||||||
|
|
||||||
|
manualClock.CurrentTime = expectedStart.HitObject.StartTime;
|
||||||
|
followPointRenderer.UpdateSubTree();
|
||||||
|
|
||||||
var points = getGroup(i).ChildrenOfType<FollowPoint>().ToArray();
|
var points = getGroup(i).ChildrenOfType<FollowPoint>().ToArray();
|
||||||
if (points.Length == 0)
|
if (points.Length == 0)
|
||||||
continue;
|
continue;
|
||||||
@ -255,7 +272,9 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
|
|
||||||
private DrawableOsuHitObject getObject(int index) => hitObjectContainer[index];
|
private DrawableOsuHitObject getObject(int index) => hitObjectContainer[index];
|
||||||
|
|
||||||
private FollowPointConnection getGroup(int index) => followPointRenderer.Connections[index];
|
private FollowPointLifetimeEntry getEntry(int index) => followPointRenderer.Entries[index];
|
||||||
|
|
||||||
|
private FollowPointConnection getGroup(int index) => followPointRenderer.ChildrenOfType<FollowPointConnection>().Single(c => c.Entry == getEntry(index));
|
||||||
|
|
||||||
private class TestHitObjectContainer : Container<DrawableOsuHitObject>
|
private class TestHitObjectContainer : Container<DrawableOsuHitObject>
|
||||||
{
|
{
|
||||||
|
@ -1,17 +1,17 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using osu.Framework.Graphics;
|
|
||||||
using osu.Framework.Graphics.Containers;
|
|
||||||
using osu.Game.Beatmaps;
|
|
||||||
using osu.Game.Beatmaps.ControlPoints;
|
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
|
||||||
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
|
||||||
using osuTK;
|
|
||||||
using osu.Game.Rulesets.Mods;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
|
using osu.Game.Rulesets.Mods;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||||
|
using osu.Game.Rulesets.Osu.UI;
|
||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Tests
|
namespace osu.Game.Rulesets.Osu.Tests
|
||||||
{
|
{
|
||||||
@ -38,13 +38,37 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
}
|
}
|
||||||
|
|
||||||
private Drawable testSingle(float circleSize, bool auto = false, double timeOffset = 0, Vector2? positionOffset = null)
|
private Drawable testSingle(float circleSize, bool auto = false, double timeOffset = 0, Vector2? positionOffset = null)
|
||||||
|
{
|
||||||
|
var drawable = createSingle(circleSize, auto, timeOffset, positionOffset);
|
||||||
|
|
||||||
|
var playfield = new TestOsuPlayfield();
|
||||||
|
playfield.Add(drawable);
|
||||||
|
return playfield;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Drawable testStream(float circleSize, bool auto = false)
|
||||||
|
{
|
||||||
|
var playfield = new TestOsuPlayfield();
|
||||||
|
|
||||||
|
Vector2 pos = new Vector2(-250, 0);
|
||||||
|
|
||||||
|
for (int i = 0; i <= 1000; i += 100)
|
||||||
|
{
|
||||||
|
playfield.Add(createSingle(circleSize, auto, i, pos));
|
||||||
|
pos.X += 50;
|
||||||
|
}
|
||||||
|
|
||||||
|
return playfield;
|
||||||
|
}
|
||||||
|
|
||||||
|
private TestDrawableHitCircle createSingle(float circleSize, bool auto, double timeOffset, Vector2? positionOffset)
|
||||||
{
|
{
|
||||||
positionOffset ??= Vector2.Zero;
|
positionOffset ??= Vector2.Zero;
|
||||||
|
|
||||||
var circle = new HitCircle
|
var circle = new HitCircle
|
||||||
{
|
{
|
||||||
StartTime = Time.Current + 1000 + timeOffset,
|
StartTime = Time.Current + 1000 + timeOffset,
|
||||||
Position = positionOffset.Value,
|
Position = OsuPlayfield.BASE_SIZE / 4 + positionOffset.Value,
|
||||||
};
|
};
|
||||||
|
|
||||||
circle.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty { CircleSize = circleSize });
|
circle.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty { CircleSize = circleSize });
|
||||||
@ -53,31 +77,14 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
|
|
||||||
foreach (var mod in SelectedMods.Value.OfType<IApplicableToDrawableHitObjects>())
|
foreach (var mod in SelectedMods.Value.OfType<IApplicableToDrawableHitObjects>())
|
||||||
mod.ApplyToDrawableHitObjects(new[] { drawable });
|
mod.ApplyToDrawableHitObjects(new[] { drawable });
|
||||||
|
|
||||||
return drawable;
|
return drawable;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual TestDrawableHitCircle CreateDrawableHitCircle(HitCircle circle, bool auto) => new TestDrawableHitCircle(circle, auto)
|
protected virtual TestDrawableHitCircle CreateDrawableHitCircle(HitCircle circle, bool auto) => new TestDrawableHitCircle(circle, auto)
|
||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
|
||||||
Depth = depthIndex++
|
Depth = depthIndex++
|
||||||
};
|
};
|
||||||
|
|
||||||
private Drawable testStream(float circleSize, bool auto = false)
|
|
||||||
{
|
|
||||||
var container = new Container { RelativeSizeAxes = Axes.Both };
|
|
||||||
|
|
||||||
Vector2 pos = new Vector2(-250, 0);
|
|
||||||
|
|
||||||
for (int i = 0; i <= 1000; i += 100)
|
|
||||||
{
|
|
||||||
container.Add(testSingle(circleSize, auto, i, pos));
|
|
||||||
pos.X += 50;
|
|
||||||
}
|
|
||||||
|
|
||||||
return container;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected class TestDrawableHitCircle : DrawableHitCircle
|
protected class TestDrawableHitCircle : DrawableHitCircle
|
||||||
{
|
{
|
||||||
private readonly bool auto;
|
private readonly bool auto;
|
||||||
@ -101,5 +108,13 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
base.CheckForResult(userTriggered, timeOffset);
|
base.CheckForResult(userTriggered, timeOffset);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected class TestOsuPlayfield : OsuPlayfield
|
||||||
|
{
|
||||||
|
public TestOsuPlayfield()
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,11 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
using osu.Framework.Threading;
|
||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
|
|
||||||
@ -10,6 +13,19 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
{
|
{
|
||||||
public class TestSceneShaking : TestSceneHitCircle
|
public class TestSceneShaking : TestSceneHitCircle
|
||||||
{
|
{
|
||||||
|
private readonly List<ScheduledDelegate> scheduledTasks = new List<ScheduledDelegate>();
|
||||||
|
|
||||||
|
protected override IBeatmap CreateBeatmapForSkinProvider()
|
||||||
|
{
|
||||||
|
// best way to run cleanup before a new step is run
|
||||||
|
foreach (var task in scheduledTasks)
|
||||||
|
task.Cancel();
|
||||||
|
|
||||||
|
scheduledTasks.Clear();
|
||||||
|
|
||||||
|
return base.CreateBeatmapForSkinProvider();
|
||||||
|
}
|
||||||
|
|
||||||
protected override TestDrawableHitCircle CreateDrawableHitCircle(HitCircle circle, bool auto)
|
protected override TestDrawableHitCircle CreateDrawableHitCircle(HitCircle circle, bool auto)
|
||||||
{
|
{
|
||||||
var drawableHitObject = base.CreateDrawableHitCircle(circle, auto);
|
var drawableHitObject = base.CreateDrawableHitCircle(circle, auto);
|
||||||
@ -17,7 +33,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
Debug.Assert(drawableHitObject.HitObject.HitWindows != null);
|
Debug.Assert(drawableHitObject.HitObject.HitWindows != null);
|
||||||
|
|
||||||
double delay = drawableHitObject.HitObject.StartTime - (drawableHitObject.HitObject.HitWindows.WindowFor(HitResult.Miss) + RNG.Next(0, 300)) - Time.Current;
|
double delay = drawableHitObject.HitObject.StartTime - (drawableHitObject.HitObject.HitWindows.WindowFor(HitResult.Miss) + RNG.Next(0, 300)) - Time.Current;
|
||||||
Scheduler.AddDelayed(() => drawableHitObject.TriggerJudgement(), delay);
|
scheduledTasks.Add(Scheduler.AddDelayed(() => drawableHitObject.TriggerJudgement(), delay));
|
||||||
|
|
||||||
return drawableHitObject;
|
return drawableHitObject;
|
||||||
}
|
}
|
||||||
|
@ -108,8 +108,8 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
|
|
||||||
AddStep("change samples", () => slider.HitObject.Samples = new[]
|
AddStep("change samples", () => slider.HitObject.Samples = new[]
|
||||||
{
|
{
|
||||||
new HitSampleInfo { Name = HitSampleInfo.HIT_CLAP },
|
new HitSampleInfo(HitSampleInfo.HIT_CLAP),
|
||||||
new HitSampleInfo { Name = HitSampleInfo.HIT_WHISTLE },
|
new HitSampleInfo(HitSampleInfo.HIT_WHISTLE),
|
||||||
});
|
});
|
||||||
|
|
||||||
AddAssert("head samples updated", () => assertSamples(slider.HitObject.HeadCircle));
|
AddAssert("head samples updated", () => assertSamples(slider.HitObject.HeadCircle));
|
||||||
@ -136,15 +136,15 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
slider = (DrawableSlider)createSlider(repeats: 1);
|
slider = (DrawableSlider)createSlider(repeats: 1);
|
||||||
|
|
||||||
for (int i = 0; i < 2; i++)
|
for (int i = 0; i < 2; i++)
|
||||||
slider.HitObject.NodeSamples.Add(new List<HitSampleInfo> { new HitSampleInfo { Name = HitSampleInfo.HIT_FINISH } });
|
slider.HitObject.NodeSamples.Add(new List<HitSampleInfo> { new HitSampleInfo(HitSampleInfo.HIT_FINISH) });
|
||||||
|
|
||||||
Add(slider);
|
Add(slider);
|
||||||
});
|
});
|
||||||
|
|
||||||
AddStep("change samples", () => slider.HitObject.Samples = new[]
|
AddStep("change samples", () => slider.HitObject.Samples = new[]
|
||||||
{
|
{
|
||||||
new HitSampleInfo { Name = HitSampleInfo.HIT_CLAP },
|
new HitSampleInfo(HitSampleInfo.HIT_CLAP),
|
||||||
new HitSampleInfo { Name = HitSampleInfo.HIT_WHISTLE },
|
new HitSampleInfo(HitSampleInfo.HIT_WHISTLE),
|
||||||
});
|
});
|
||||||
|
|
||||||
AddAssert("head samples not updated", () => assertSamples(slider.HitObject.HeadCircle));
|
AddAssert("head samples not updated", () => assertSamples(slider.HitObject.HeadCircle));
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
<Import Project="..\osu.TestProject.props" />
|
<Import Project="..\osu.TestProject.props" />
|
||||||
<ItemGroup Label="Package References">
|
<ItemGroup Label="Package References">
|
||||||
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
|
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.1" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.0" />
|
||||||
<PackageReference Include="NUnit" Version="3.12.0" />
|
<PackageReference Include="NUnit" Version="3.12.0" />
|
||||||
<PackageReference Include="NUnit3TestAdapter" Version="3.17.0" />
|
<PackageReference Include="NUnit3TestAdapter" Version="3.17.0" />
|
||||||
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
|
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
|
||||||
|
@ -45,9 +45,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles
|
|||||||
return base.OnMouseDown(e);
|
return base.OnMouseDown(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void UpdatePosition(SnapResult result)
|
public override void UpdateTimeAndPosition(SnapResult result)
|
||||||
{
|
{
|
||||||
base.UpdatePosition(result);
|
base.UpdateTimeAndPosition(result);
|
||||||
HitObject.Position = ToLocalSpace(result.ScreenSpacePosition);
|
HitObject.Position = ToLocalSpace(result.ScreenSpacePosition);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -44,6 +44,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
|||||||
private OsuColour colours { get; set; }
|
private OsuColour colours { get; set; }
|
||||||
|
|
||||||
private IBindable<Vector2> sliderPosition;
|
private IBindable<Vector2> sliderPosition;
|
||||||
|
private IBindable<float> sliderScale;
|
||||||
private IBindable<Vector2> controlPointPosition;
|
private IBindable<Vector2> controlPointPosition;
|
||||||
|
|
||||||
public PathControlPointPiece(Slider slider, PathControlPoint controlPoint)
|
public PathControlPointPiece(Slider slider, PathControlPoint controlPoint)
|
||||||
@ -69,13 +70,13 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
|||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
Size = new Vector2(10),
|
Size = new Vector2(20),
|
||||||
},
|
},
|
||||||
markerRing = new CircularContainer
|
markerRing = new CircularContainer
|
||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
Size = new Vector2(14),
|
Size = new Vector2(28),
|
||||||
Masking = true,
|
Masking = true,
|
||||||
BorderThickness = 2,
|
BorderThickness = 2,
|
||||||
BorderColour = Color4.White,
|
BorderColour = Color4.White,
|
||||||
@ -102,6 +103,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
|||||||
controlPointPosition = ControlPoint.Position.GetBoundCopy();
|
controlPointPosition = ControlPoint.Position.GetBoundCopy();
|
||||||
controlPointPosition.BindValueChanged(_ => updateMarkerDisplay());
|
controlPointPosition.BindValueChanged(_ => updateMarkerDisplay());
|
||||||
|
|
||||||
|
sliderScale = slider.ScaleBindable.GetBoundCopy();
|
||||||
|
sliderScale.BindValueChanged(_ => updateMarkerDisplay());
|
||||||
|
|
||||||
IsSelected.BindValueChanged(_ => updateMarkerDisplay());
|
IsSelected.BindValueChanged(_ => updateMarkerDisplay());
|
||||||
|
|
||||||
updateMarkerDisplay();
|
updateMarkerDisplay();
|
||||||
@ -143,6 +147,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
|||||||
|
|
||||||
protected override bool OnClick(ClickEvent e) => RequestSelection != null;
|
protected override bool OnClick(ClickEvent e) => RequestSelection != null;
|
||||||
|
|
||||||
|
private Vector2 dragStartPosition;
|
||||||
|
|
||||||
protected override bool OnDragStart(DragStartEvent e)
|
protected override bool OnDragStart(DragStartEvent e)
|
||||||
{
|
{
|
||||||
if (RequestSelection == null)
|
if (RequestSelection == null)
|
||||||
@ -150,6 +156,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
|||||||
|
|
||||||
if (e.Button == MouseButton.Left)
|
if (e.Button == MouseButton.Left)
|
||||||
{
|
{
|
||||||
|
dragStartPosition = ControlPoint.Position.Value;
|
||||||
changeHandler?.BeginChange();
|
changeHandler?.BeginChange();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -174,7 +181,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
|||||||
slider.Path.ControlPoints[i].Position.Value -= movementDelta;
|
slider.Path.ControlPoints[i].Position.Value -= movementDelta;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
ControlPoint.Position.Value += e.Delta;
|
ControlPoint.Position.Value = dragStartPosition + (e.MousePosition - e.MouseDownPosition);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnDragEnd(DragEndEvent e) => changeHandler?.EndChange();
|
protected override void OnDragEnd(DragEndEvent e) => changeHandler?.EndChange();
|
||||||
@ -194,6 +201,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
|||||||
colour = colour.Lighten(1);
|
colour = colour.Lighten(1);
|
||||||
|
|
||||||
marker.Colour = colour;
|
marker.Colour = colour;
|
||||||
|
marker.Scale = new Vector2(slider.Scale);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,12 +20,15 @@ using osu.Game.Rulesets.Objects;
|
|||||||
using osu.Game.Rulesets.Objects.Types;
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
using osu.Game.Screens.Edit;
|
using osu.Game.Screens.Edit;
|
||||||
|
using osuTK;
|
||||||
using osuTK.Input;
|
using osuTK.Input;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
||||||
{
|
{
|
||||||
public class PathControlPointVisualiser : CompositeDrawable, IKeyBindingHandler<PlatformAction>, IHasContextMenu
|
public class PathControlPointVisualiser : CompositeDrawable, IKeyBindingHandler<PlatformAction>, IHasContextMenu
|
||||||
{
|
{
|
||||||
|
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; // allow context menu to appear outside of the playfield.
|
||||||
|
|
||||||
internal readonly Container<PathControlPointPiece> Pieces;
|
internal readonly Container<PathControlPointPiece> Pieces;
|
||||||
internal readonly Container<PathControlPointConnectionPiece> Connections;
|
internal readonly Container<PathControlPointConnectionPiece> Connections;
|
||||||
|
|
||||||
|
@ -67,9 +67,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
inputManager = GetContainingInputManager();
|
inputManager = GetContainingInputManager();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void UpdatePosition(SnapResult result)
|
public override void UpdateTimeAndPosition(SnapResult result)
|
||||||
{
|
{
|
||||||
base.UpdatePosition(result);
|
base.UpdateTimeAndPosition(result);
|
||||||
|
|
||||||
switch (state)
|
switch (state)
|
||||||
{
|
{
|
||||||
|
@ -44,6 +44,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
[Resolved(CanBeNull = true)]
|
[Resolved(CanBeNull = true)]
|
||||||
private IEditorChangeHandler changeHandler { get; set; }
|
private IEditorChangeHandler changeHandler { get; set; }
|
||||||
|
|
||||||
|
private readonly BindableList<PathControlPoint> controlPoints = new BindableList<PathControlPoint>();
|
||||||
|
private readonly IBindable<int> pathVersion = new Bindable<int>();
|
||||||
|
|
||||||
public SliderSelectionBlueprint(DrawableSlider slider)
|
public SliderSelectionBlueprint(DrawableSlider slider)
|
||||||
: base(slider)
|
: base(slider)
|
||||||
{
|
{
|
||||||
@ -61,13 +64,13 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private IBindable<int> pathVersion;
|
|
||||||
|
|
||||||
protected override void LoadComplete()
|
protected override void LoadComplete()
|
||||||
{
|
{
|
||||||
base.LoadComplete();
|
base.LoadComplete();
|
||||||
|
|
||||||
pathVersion = HitObject.Path.Version.GetBoundCopy();
|
controlPoints.BindTo(HitObject.Path.ControlPoints);
|
||||||
|
|
||||||
|
pathVersion.BindTo(HitObject.Path.Version);
|
||||||
pathVersion.BindValueChanged(_ => updatePath());
|
pathVersion.BindValueChanged(_ => updatePath());
|
||||||
|
|
||||||
BodyPiece.UpdateFrom(HitObject);
|
BodyPiece.UpdateFrom(HitObject);
|
||||||
@ -164,8 +167,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private BindableList<PathControlPoint> controlPoints => HitObject.Path.ControlPoints;
|
|
||||||
|
|
||||||
private int addControlPoint(Vector2 position)
|
private int addControlPoint(Vector2 position)
|
||||||
{
|
{
|
||||||
position -= HitObject.Position;
|
position -= HitObject.Position;
|
||||||
|
@ -1,63 +0,0 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Linq;
|
|
||||||
using osu.Framework.Graphics;
|
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
|
||||||
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Edit
|
|
||||||
{
|
|
||||||
public class DrawableOsuEditPool<T> : DrawableOsuPool<T>
|
|
||||||
where T : DrawableHitObject, new()
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Hit objects are intentionally made to fade out at a constant slower rate than in gameplay.
|
|
||||||
/// This allows a mapper to gain better historical context and use recent hitobjects as reference / snap points.
|
|
||||||
/// </summary>
|
|
||||||
private const double editor_hit_object_fade_out_extension = 700;
|
|
||||||
|
|
||||||
public DrawableOsuEditPool(Func<DrawableHitObject, double, bool> checkHittable, Action<Drawable> onLoaded, int initialSize, int? maximumSize = null)
|
|
||||||
: base(checkHittable, onLoaded, initialSize, maximumSize)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override T CreateNewDrawable() => base.CreateNewDrawable().With(d => d.ApplyCustomUpdateState += updateState);
|
|
||||||
|
|
||||||
private void updateState(DrawableHitObject hitObject, ArmedState state)
|
|
||||||
{
|
|
||||||
if (state == ArmedState.Idle)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// adjust the visuals of certain object types to make them stay on screen for longer than usual.
|
|
||||||
switch (hitObject)
|
|
||||||
{
|
|
||||||
default:
|
|
||||||
// there are quite a few drawable hit types we don't want to extend (spinners, ticks etc.)
|
|
||||||
return;
|
|
||||||
|
|
||||||
case DrawableSlider _:
|
|
||||||
// no specifics to sliders but let them fade slower below.
|
|
||||||
break;
|
|
||||||
|
|
||||||
case DrawableHitCircle circle: // also handles slider heads
|
|
||||||
circle.ApproachCircle
|
|
||||||
.FadeOutFromOne(editor_hit_object_fade_out_extension)
|
|
||||||
.Expire();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the existing fade out transform
|
|
||||||
var existing = hitObject.Transforms.LastOrDefault(t => t.TargetMember == nameof(Alpha));
|
|
||||||
|
|
||||||
if (existing == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
hitObject.RemoveTransform(existing);
|
|
||||||
|
|
||||||
using (hitObject.BeginAbsoluteSequence(existing.StartTime))
|
|
||||||
hitObject.FadeOut(editor_hit_object_fade_out_extension).Expire();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -2,9 +2,12 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using osu.Framework.Graphics.Pooling;
|
using System.Linq;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.Osu.UI;
|
using osu.Game.Rulesets.Osu.UI;
|
||||||
using osu.Game.Rulesets.UI;
|
using osu.Game.Rulesets.UI;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
@ -26,8 +29,51 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
{
|
{
|
||||||
protected override GameplayCursorContainer CreateCursor() => null;
|
protected override GameplayCursorContainer CreateCursor() => null;
|
||||||
|
|
||||||
protected override DrawablePool<TDrawable> CreatePool<TDrawable>(int initialSize, int? maximumSize = null)
|
protected override void OnNewDrawableHitObject(DrawableHitObject d)
|
||||||
=> new DrawableOsuEditPool<TDrawable>(CheckHittable, OnHitObjectLoaded, initialSize, maximumSize);
|
{
|
||||||
|
d.ApplyCustomUpdateState += updateState;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Hit objects are intentionally made to fade out at a constant slower rate than in gameplay.
|
||||||
|
/// This allows a mapper to gain better historical context and use recent hitobjects as reference / snap points.
|
||||||
|
/// </summary>
|
||||||
|
private const double editor_hit_object_fade_out_extension = 700;
|
||||||
|
|
||||||
|
private void updateState(DrawableHitObject hitObject, ArmedState state)
|
||||||
|
{
|
||||||
|
if (state == ArmedState.Idle)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// adjust the visuals of certain object types to make them stay on screen for longer than usual.
|
||||||
|
switch (hitObject)
|
||||||
|
{
|
||||||
|
default:
|
||||||
|
// there are quite a few drawable hit types we don't want to extend (spinners, ticks etc.)
|
||||||
|
return;
|
||||||
|
|
||||||
|
case DrawableSlider _:
|
||||||
|
// no specifics to sliders but let them fade slower below.
|
||||||
|
break;
|
||||||
|
|
||||||
|
case DrawableHitCircle circle: // also handles slider heads
|
||||||
|
circle.ApproachCircle
|
||||||
|
.FadeOutFromOne(editor_hit_object_fade_out_extension)
|
||||||
|
.Expire();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the existing fade out transform
|
||||||
|
var existing = hitObject.Transforms.LastOrDefault(t => t.TargetMember == nameof(Alpha));
|
||||||
|
|
||||||
|
if (existing == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
hitObject.RemoveTransform(existing);
|
||||||
|
|
||||||
|
using (hitObject.BeginAbsoluteSequence(existing.StartTime))
|
||||||
|
hitObject.FadeOut(editor_hit_object_fade_out_extension).Expire();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -105,11 +105,20 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override SnapResult SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition)
|
public override SnapResult SnapScreenSpacePositionToValidPosition(Vector2 screenSpacePosition)
|
||||||
{
|
{
|
||||||
if (snapToVisibleBlueprints(screenSpacePosition, out var snapResult))
|
if (snapToVisibleBlueprints(screenSpacePosition, out var snapResult))
|
||||||
return snapResult;
|
return snapResult;
|
||||||
|
|
||||||
|
return new SnapResult(screenSpacePosition, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override SnapResult SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition)
|
||||||
|
{
|
||||||
|
var positionSnap = SnapScreenSpacePositionToValidPosition(screenSpacePosition);
|
||||||
|
if (positionSnap.ScreenSpacePosition != screenSpacePosition)
|
||||||
|
return positionSnap;
|
||||||
|
|
||||||
// will be null if distance snap is disabled or not feasible for the current time value.
|
// will be null if distance snap is disabled or not feasible for the current time value.
|
||||||
if (distanceSnapGrid == null)
|
if (distanceSnapGrid == null)
|
||||||
return base.SnapScreenSpacePositionToValidTime(screenSpacePosition);
|
return base.SnapScreenSpacePositionToValidTime(screenSpacePosition);
|
||||||
|
@ -5,7 +5,7 @@ using osu.Game.Rulesets.Mods;
|
|||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Mods
|
namespace osu.Game.Rulesets.Osu.Mods
|
||||||
{
|
{
|
||||||
public class OsuModEasy : ModEasy
|
public class OsuModEasy : ModEasyWithExtraLives
|
||||||
{
|
{
|
||||||
public override string Description => @"Larger circles, more forgiving HP drain, less accuracy required, and three lives!";
|
public override string Description => @"Larger circles, more forgiving HP drain, less accuracy required, and three lives!";
|
||||||
}
|
}
|
||||||
|
@ -2,9 +2,10 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
@ -23,26 +24,22 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
private const double fade_in_duration_multiplier = 0.4;
|
private const double fade_in_duration_multiplier = 0.4;
|
||||||
private const double fade_out_duration_multiplier = 0.3;
|
private const double fade_out_duration_multiplier = 0.3;
|
||||||
|
|
||||||
protected override bool IsFirstAdjustableObject(HitObject hitObject) => !(hitObject is Spinner);
|
protected override bool IsFirstAdjustableObject(HitObject hitObject) => !(hitObject is Spinner || hitObject is SpinnerTick);
|
||||||
|
|
||||||
public override void ApplyToDrawableHitObjects(IEnumerable<DrawableHitObject> drawables)
|
public override void ApplyToBeatmap(IBeatmap beatmap)
|
||||||
{
|
{
|
||||||
foreach (var d in drawables)
|
base.ApplyToBeatmap(beatmap);
|
||||||
d.HitObjectApplied += applyFadeInAdjustment;
|
|
||||||
|
|
||||||
base.ApplyToDrawableHitObjects(drawables);
|
foreach (var obj in beatmap.HitObjects.OfType<OsuHitObject>())
|
||||||
}
|
applyFadeInAdjustment(obj);
|
||||||
|
|
||||||
private void applyFadeInAdjustment(DrawableHitObject hitObject)
|
static void applyFadeInAdjustment(OsuHitObject osuObject)
|
||||||
{
|
{
|
||||||
if (!(hitObject is DrawableOsuHitObject d))
|
osuObject.TimeFadeIn = osuObject.TimePreempt * fade_in_duration_multiplier;
|
||||||
return;
|
foreach (var nested in osuObject.NestedHitObjects.OfType<OsuHitObject>())
|
||||||
|
|
||||||
d.HitObject.TimeFadeIn = d.HitObject.TimePreempt * fade_in_duration_multiplier;
|
|
||||||
|
|
||||||
foreach (var nested in d.NestedHitObjects)
|
|
||||||
applyFadeInAdjustment(nested);
|
applyFadeInAdjustment(nested);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected override void ApplyIncreasedVisibilityState(DrawableHitObject hitObject, ArmedState state)
|
protected override void ApplyIncreasedVisibilityState(DrawableHitObject hitObject, ArmedState state)
|
||||||
{
|
{
|
||||||
@ -56,37 +53,27 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
applyState(hitObject, false);
|
applyState(hitObject, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void applyState(DrawableHitObject drawable, bool increaseVisibility)
|
private void applyState(DrawableHitObject drawableObject, bool increaseVisibility)
|
||||||
{
|
{
|
||||||
if (!(drawable is DrawableOsuHitObject d))
|
if (!(drawableObject is DrawableOsuHitObject drawableOsuObject))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var h = d.HitObject;
|
OsuHitObject hitObject = drawableOsuObject.HitObject;
|
||||||
|
|
||||||
var fadeOutStartTime = h.StartTime - h.TimePreempt + h.TimeFadeIn;
|
(double startTime, double duration) fadeOut = getFadeOutParameters(drawableOsuObject);
|
||||||
var fadeOutDuration = h.TimePreempt * fade_out_duration_multiplier;
|
|
||||||
|
|
||||||
// new duration from completed fade in to end (before fading out)
|
switch (drawableObject)
|
||||||
var longFadeDuration = h.GetEndTime() - fadeOutStartTime;
|
|
||||||
|
|
||||||
switch (drawable)
|
|
||||||
{
|
{
|
||||||
case DrawableSliderTail sliderTail:
|
case DrawableSliderTail _:
|
||||||
// use stored values from head circle to achieve same fade sequence.
|
using (drawableObject.BeginAbsoluteSequence(fadeOut.startTime, true))
|
||||||
var tailFadeOutParameters = getFadeOutParametersFromSliderHead(h);
|
drawableObject.FadeOut(fadeOut.duration);
|
||||||
|
|
||||||
using (drawable.BeginAbsoluteSequence(tailFadeOutParameters.startTime, true))
|
|
||||||
sliderTail.FadeOut(tailFadeOutParameters.duration);
|
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case DrawableSliderRepeat sliderRepeat:
|
case DrawableSliderRepeat sliderRepeat:
|
||||||
// use stored values from head circle to achieve same fade sequence.
|
using (drawableObject.BeginAbsoluteSequence(fadeOut.startTime, true))
|
||||||
var repeatFadeOutParameters = getFadeOutParametersFromSliderHead(h);
|
|
||||||
|
|
||||||
using (drawable.BeginAbsoluteSequence(repeatFadeOutParameters.startTime, true))
|
|
||||||
// only apply to circle piece – reverse arrow is not affected by hidden.
|
// only apply to circle piece – reverse arrow is not affected by hidden.
|
||||||
sliderRepeat.CirclePiece.FadeOut(repeatFadeOutParameters.duration);
|
sliderRepeat.CirclePiece.FadeOut(fadeOut.duration);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@ -101,29 +88,23 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
// we don't want to see the approach circle
|
// we don't want to see the approach circle
|
||||||
using (circle.BeginAbsoluteSequence(h.StartTime - h.TimePreempt, true))
|
using (circle.BeginAbsoluteSequence(hitObject.StartTime - hitObject.TimePreempt, true))
|
||||||
circle.ApproachCircle.Hide();
|
circle.ApproachCircle.Hide();
|
||||||
}
|
}
|
||||||
|
|
||||||
// fade out immediately after fade in.
|
using (drawableObject.BeginAbsoluteSequence(fadeOut.startTime, true))
|
||||||
using (drawable.BeginAbsoluteSequence(fadeOutStartTime, true))
|
fadeTarget.FadeOut(fadeOut.duration);
|
||||||
fadeTarget.FadeOut(fadeOutDuration);
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case DrawableSlider slider:
|
case DrawableSlider slider:
|
||||||
associateNestedSliderCirclesWithHead(slider.HitObject);
|
using (slider.BeginAbsoluteSequence(fadeOut.startTime, true))
|
||||||
|
slider.Body.FadeOut(fadeOut.duration, Easing.Out);
|
||||||
using (slider.BeginAbsoluteSequence(fadeOutStartTime, true))
|
|
||||||
slider.Body.FadeOut(longFadeDuration, Easing.Out);
|
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case DrawableSliderTick sliderTick:
|
case DrawableSliderTick sliderTick:
|
||||||
// slider ticks fade out over up to one second
|
using (sliderTick.BeginAbsoluteSequence(fadeOut.startTime, true))
|
||||||
var tickFadeOutDuration = Math.Min(sliderTick.HitObject.TimePreempt - DrawableSliderTick.ANIM_DURATION, 1000);
|
sliderTick.FadeOut(fadeOut.duration);
|
||||||
|
|
||||||
using (sliderTick.BeginAbsoluteSequence(sliderTick.HitObject.StartTime - tickFadeOutDuration, true))
|
|
||||||
sliderTick.FadeOut(tickFadeOutDuration);
|
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@ -131,30 +112,55 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
// hide elements we don't care about.
|
// hide elements we don't care about.
|
||||||
// todo: hide background
|
// todo: hide background
|
||||||
|
|
||||||
using (spinner.BeginAbsoluteSequence(fadeOutStartTime + longFadeDuration, true))
|
using (spinner.BeginAbsoluteSequence(fadeOut.startTime, true))
|
||||||
spinner.FadeOut(fadeOutDuration);
|
spinner.FadeOut(fadeOut.duration);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly Dictionary<HitObject, SliderHeadCircle> correspondingSliderHeadForObject = new Dictionary<HitObject, SliderHeadCircle>();
|
private (double startTime, double duration) getFadeOutParameters(DrawableOsuHitObject drawableObject)
|
||||||
|
|
||||||
private void associateNestedSliderCirclesWithHead(Slider slider)
|
|
||||||
{
|
{
|
||||||
var sliderHead = slider.NestedHitObjects.Single(obj => obj is SliderHeadCircle);
|
switch (drawableObject)
|
||||||
|
|
||||||
foreach (var nested in slider.NestedHitObjects)
|
|
||||||
{
|
{
|
||||||
if ((nested is SliderRepeat || nested is SliderEndCircle) && !correspondingSliderHeadForObject.ContainsKey(nested))
|
case DrawableSliderTail tail:
|
||||||
correspondingSliderHeadForObject[nested] = (SliderHeadCircle)sliderHead;
|
// Use the same fade sequence as the slider head.
|
||||||
}
|
Debug.Assert(tail.Slider != null);
|
||||||
|
return getParameters(tail.Slider.HeadCircle);
|
||||||
|
|
||||||
|
case DrawableSliderRepeat repeat:
|
||||||
|
// Use the same fade sequence as the slider head.
|
||||||
|
Debug.Assert(repeat.Slider != null);
|
||||||
|
return getParameters(repeat.Slider.HeadCircle);
|
||||||
|
|
||||||
|
default:
|
||||||
|
return getParameters(drawableObject.HitObject);
|
||||||
}
|
}
|
||||||
|
|
||||||
private (double startTime, double duration) getFadeOutParametersFromSliderHead(OsuHitObject h)
|
static (double startTime, double duration) getParameters(OsuHitObject hitObject)
|
||||||
{
|
{
|
||||||
var sliderHead = correspondingSliderHeadForObject[h];
|
var fadeOutStartTime = hitObject.StartTime - hitObject.TimePreempt + hitObject.TimeFadeIn;
|
||||||
return (sliderHead.StartTime - sliderHead.TimePreempt + sliderHead.TimeFadeIn, sliderHead.TimePreempt * fade_out_duration_multiplier);
|
var fadeOutDuration = hitObject.TimePreempt * fade_out_duration_multiplier;
|
||||||
|
|
||||||
|
// new duration from completed fade in to end (before fading out)
|
||||||
|
var longFadeDuration = hitObject.GetEndTime() - fadeOutStartTime;
|
||||||
|
|
||||||
|
switch (hitObject)
|
||||||
|
{
|
||||||
|
case Slider _:
|
||||||
|
return (fadeOutStartTime, longFadeDuration);
|
||||||
|
|
||||||
|
case SliderTick _:
|
||||||
|
var tickFadeOutDuration = Math.Min(hitObject.TimePreempt - DrawableSliderTick.ANIM_DURATION, 1000);
|
||||||
|
return (hitObject.StartTime - tickFadeOutDuration, tickFadeOutDuration);
|
||||||
|
|
||||||
|
case Spinner _:
|
||||||
|
return (fadeOutStartTime + longFadeDuration, fadeOutDuration);
|
||||||
|
|
||||||
|
default:
|
||||||
|
return (fadeOutStartTime, fadeOutDuration);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,14 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Bindables;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
using osu.Framework.Extensions.Color4Extensions;
|
using osu.Framework.Extensions.Color4Extensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Effects;
|
using osu.Framework.Graphics.Effects;
|
||||||
|
using osu.Framework.Graphics.Pooling;
|
||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
|
|
||||||
@ -15,7 +17,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// A single follow point positioned between two adjacent <see cref="DrawableOsuHitObject"/>s.
|
/// A single follow point positioned between two adjacent <see cref="DrawableOsuHitObject"/>s.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class FollowPoint : Container, IAnimationTimeReference
|
public class FollowPoint : PoolableDrawable, IAnimationTimeReference
|
||||||
{
|
{
|
||||||
private const float width = 8;
|
private const float width = 8;
|
||||||
|
|
||||||
@ -25,7 +27,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
|
|||||||
{
|
{
|
||||||
Origin = Anchor.Centre;
|
Origin = Anchor.Centre;
|
||||||
|
|
||||||
Child = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.FollowPoint), _ => new CircularContainer
|
InternalChild = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.FollowPoint), _ => new CircularContainer
|
||||||
{
|
{
|
||||||
Masking = true,
|
Masking = true,
|
||||||
AutoSizeAxes = Axes.Both,
|
AutoSizeAxes = Axes.Both,
|
||||||
@ -46,6 +48,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public double AnimationStartTime { get; set; }
|
public Bindable<double> AnimationStartTime { get; } = new BindableDouble();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,11 +2,8 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Diagnostics;
|
|
||||||
using JetBrains.Annotations;
|
|
||||||
using osu.Framework.Bindables;
|
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Pooling;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
@ -15,150 +12,106 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Visualises the <see cref="FollowPoint"/>s between two <see cref="DrawableOsuHitObject"/>s.
|
/// Visualises the <see cref="FollowPoint"/>s between two <see cref="DrawableOsuHitObject"/>s.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class FollowPointConnection : CompositeDrawable
|
public class FollowPointConnection : PoolableDrawable
|
||||||
{
|
{
|
||||||
// Todo: These shouldn't be constants
|
// Todo: These shouldn't be constants
|
||||||
private const int spacing = 32;
|
public const int SPACING = 32;
|
||||||
private const double preempt = 800;
|
public const double PREEMPT = 800;
|
||||||
|
|
||||||
public override bool RemoveWhenNotAlive => false;
|
public FollowPointLifetimeEntry Entry;
|
||||||
|
public DrawablePool<FollowPoint> Pool;
|
||||||
|
|
||||||
/// <summary>
|
protected override void PrepareForUse()
|
||||||
/// The start time of <see cref="Start"/>.
|
|
||||||
/// </summary>
|
|
||||||
public readonly Bindable<double> StartTime = new BindableDouble();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The <see cref="DrawableOsuHitObject"/> which <see cref="FollowPoint"/>s will exit from.
|
|
||||||
/// </summary>
|
|
||||||
[NotNull]
|
|
||||||
public readonly OsuHitObject Start;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Creates a new <see cref="FollowPointConnection"/>.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="start">The <see cref="DrawableOsuHitObject"/> which <see cref="FollowPoint"/>s will exit from.</param>
|
|
||||||
public FollowPointConnection([NotNull] OsuHitObject start)
|
|
||||||
{
|
{
|
||||||
Start = start;
|
base.PrepareForUse();
|
||||||
|
|
||||||
RelativeSizeAxes = Axes.Both;
|
Entry.Invalidated += onEntryInvalidated;
|
||||||
|
|
||||||
StartTime.BindTo(start.StartTimeBindable);
|
refreshPoints();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void LoadComplete()
|
protected override void FreeAfterUse()
|
||||||
{
|
{
|
||||||
base.LoadComplete();
|
base.FreeAfterUse();
|
||||||
bindEvents(Start);
|
|
||||||
|
Entry.Invalidated -= onEntryInvalidated;
|
||||||
|
|
||||||
|
// Return points to the pool.
|
||||||
|
ClearInternal(false);
|
||||||
|
|
||||||
|
Entry = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private OsuHitObject end;
|
private void onEntryInvalidated() => refreshPoints();
|
||||||
|
|
||||||
/// <summary>
|
private void refreshPoints()
|
||||||
/// The <see cref="DrawableOsuHitObject"/> which <see cref="FollowPoint"/>s will enter.
|
|
||||||
/// </summary>
|
|
||||||
[CanBeNull]
|
|
||||||
public OsuHitObject End
|
|
||||||
{
|
{
|
||||||
get => end;
|
ClearInternal(false);
|
||||||
set
|
|
||||||
{
|
|
||||||
end = value;
|
|
||||||
|
|
||||||
if (end != null)
|
OsuHitObject start = Entry.Start;
|
||||||
bindEvents(end);
|
OsuHitObject end = Entry.End;
|
||||||
|
|
||||||
if (IsLoaded)
|
double startTime = start.GetEndTime();
|
||||||
scheduleRefresh();
|
|
||||||
else
|
|
||||||
refresh();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void bindEvents(OsuHitObject obj)
|
Vector2 startPosition = start.StackedEndPosition;
|
||||||
{
|
Vector2 endPosition = end.StackedPosition;
|
||||||
obj.PositionBindable.BindValueChanged(_ => scheduleRefresh());
|
|
||||||
obj.DefaultsApplied += _ => scheduleRefresh();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void scheduleRefresh()
|
|
||||||
{
|
|
||||||
Scheduler.AddOnce(refresh);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void refresh()
|
|
||||||
{
|
|
||||||
double startTime = Start.GetEndTime();
|
|
||||||
|
|
||||||
LifetimeStart = startTime;
|
|
||||||
|
|
||||||
if (End == null || End.NewCombo || Start is Spinner || End is Spinner)
|
|
||||||
{
|
|
||||||
// ensure we always set a lifetime for full LifetimeManagementContainer benefits
|
|
||||||
LifetimeEnd = LifetimeStart;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Vector2 startPosition = Start.StackedEndPosition;
|
|
||||||
Vector2 endPosition = End.StackedPosition;
|
|
||||||
double endTime = End.StartTime;
|
|
||||||
|
|
||||||
Vector2 distanceVector = endPosition - startPosition;
|
Vector2 distanceVector = endPosition - startPosition;
|
||||||
int distance = (int)distanceVector.Length;
|
int distance = (int)distanceVector.Length;
|
||||||
float rotation = (float)(Math.Atan2(distanceVector.Y, distanceVector.X) * (180 / Math.PI));
|
float rotation = (float)(Math.Atan2(distanceVector.Y, distanceVector.X) * (180 / Math.PI));
|
||||||
double duration = endTime - startTime;
|
|
||||||
|
|
||||||
double? firstTransformStartTime = null;
|
|
||||||
double finalTransformEndTime = startTime;
|
double finalTransformEndTime = startTime;
|
||||||
|
|
||||||
int point = 0;
|
for (int d = (int)(SPACING * 1.5); d < distance - SPACING; d += SPACING)
|
||||||
|
|
||||||
ClearInternal();
|
|
||||||
|
|
||||||
for (int d = (int)(spacing * 1.5); d < distance - spacing; d += spacing)
|
|
||||||
{
|
{
|
||||||
float fraction = (float)d / distance;
|
float fraction = (float)d / distance;
|
||||||
Vector2 pointStartPosition = startPosition + (fraction - 0.1f) * distanceVector;
|
Vector2 pointStartPosition = startPosition + (fraction - 0.1f) * distanceVector;
|
||||||
Vector2 pointEndPosition = startPosition + fraction * distanceVector;
|
Vector2 pointEndPosition = startPosition + fraction * distanceVector;
|
||||||
double fadeOutTime = startTime + fraction * duration;
|
|
||||||
double fadeInTime = fadeOutTime - preempt;
|
GetFadeTimes(start, end, (float)d / distance, out var fadeInTime, out var fadeOutTime);
|
||||||
|
|
||||||
FollowPoint fp;
|
FollowPoint fp;
|
||||||
|
|
||||||
AddInternal(fp = new FollowPoint());
|
AddInternal(fp = Pool.Get());
|
||||||
|
|
||||||
Debug.Assert(End != null);
|
|
||||||
|
|
||||||
|
fp.ClearTransforms();
|
||||||
fp.Position = pointStartPosition;
|
fp.Position = pointStartPosition;
|
||||||
fp.Rotation = rotation;
|
fp.Rotation = rotation;
|
||||||
fp.Alpha = 0;
|
fp.Alpha = 0;
|
||||||
fp.Scale = new Vector2(1.5f * End.Scale);
|
fp.Scale = new Vector2(1.5f * end.Scale);
|
||||||
|
|
||||||
firstTransformStartTime ??= fadeInTime;
|
fp.AnimationStartTime.Value = fadeInTime;
|
||||||
|
|
||||||
fp.AnimationStartTime = fadeInTime;
|
|
||||||
|
|
||||||
using (fp.BeginAbsoluteSequence(fadeInTime))
|
using (fp.BeginAbsoluteSequence(fadeInTime))
|
||||||
{
|
{
|
||||||
fp.FadeIn(End.TimeFadeIn);
|
fp.FadeIn(end.TimeFadeIn);
|
||||||
fp.ScaleTo(End.Scale, End.TimeFadeIn, Easing.Out);
|
fp.ScaleTo(end.Scale, end.TimeFadeIn, Easing.Out);
|
||||||
fp.MoveTo(pointEndPosition, End.TimeFadeIn, Easing.Out);
|
fp.MoveTo(pointEndPosition, end.TimeFadeIn, Easing.Out);
|
||||||
fp.Delay(fadeOutTime - fadeInTime).FadeOut(End.TimeFadeIn);
|
fp.Delay(fadeOutTime - fadeInTime).FadeOut(end.TimeFadeIn);
|
||||||
|
|
||||||
finalTransformEndTime = fadeOutTime + End.TimeFadeIn;
|
finalTransformEndTime = fadeOutTime + end.TimeFadeIn;
|
||||||
}
|
}
|
||||||
|
|
||||||
point++;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int excessPoints = InternalChildren.Count - point;
|
|
||||||
for (int i = 0; i < excessPoints; i++)
|
|
||||||
RemoveInternal(InternalChildren[^1]);
|
|
||||||
|
|
||||||
// todo: use Expire() on FollowPoints and take lifetime from them when https://github.com/ppy/osu-framework/issues/3300 is fixed.
|
// todo: use Expire() on FollowPoints and take lifetime from them when https://github.com/ppy/osu-framework/issues/3300 is fixed.
|
||||||
LifetimeStart = firstTransformStartTime ?? startTime;
|
Entry.LifetimeEnd = finalTransformEndTime;
|
||||||
LifetimeEnd = finalTransformEndTime;
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Computes the fade time of follow point positioned between two hitobjects.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="start">The first <see cref="OsuHitObject"/>, where follow points should originate from.</param>
|
||||||
|
/// <param name="end">The second <see cref="OsuHitObject"/>, which follow points should target.</param>
|
||||||
|
/// <param name="fraction">The fractional distance along <paramref name="start"/> and <paramref name="end"/> at which the follow point is to be located.</param>
|
||||||
|
/// <param name="fadeInTime">The fade-in time of the follow point/</param>
|
||||||
|
/// <param name="fadeOutTime">The fade-out time of the follow point.</param>
|
||||||
|
public static void GetFadeTimes(OsuHitObject start, OsuHitObject end, float fraction, out double fadeInTime, out double fadeOutTime)
|
||||||
|
{
|
||||||
|
double startTime = start.GetEndTime();
|
||||||
|
double duration = end.StartTime - startTime;
|
||||||
|
|
||||||
|
fadeOutTime = startTime + fraction * duration;
|
||||||
|
fadeInTime = fadeOutTime - PREEMPT;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,98 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Graphics.Performance;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
|
||||||
|
{
|
||||||
|
public class FollowPointLifetimeEntry : LifetimeEntry
|
||||||
|
{
|
||||||
|
public event Action Invalidated;
|
||||||
|
public readonly OsuHitObject Start;
|
||||||
|
|
||||||
|
public FollowPointLifetimeEntry(OsuHitObject start)
|
||||||
|
{
|
||||||
|
Start = start;
|
||||||
|
LifetimeStart = Start.StartTime;
|
||||||
|
|
||||||
|
bindEvents();
|
||||||
|
}
|
||||||
|
|
||||||
|
private OsuHitObject end;
|
||||||
|
|
||||||
|
public OsuHitObject End
|
||||||
|
{
|
||||||
|
get => end;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
UnbindEvents();
|
||||||
|
|
||||||
|
end = value;
|
||||||
|
|
||||||
|
bindEvents();
|
||||||
|
|
||||||
|
refreshLifetimes();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void bindEvents()
|
||||||
|
{
|
||||||
|
UnbindEvents();
|
||||||
|
|
||||||
|
// Note: Positions are bound for instantaneous feedback from positional changes from the editor, before ApplyDefaults() is called on hitobjects.
|
||||||
|
Start.DefaultsApplied += onDefaultsApplied;
|
||||||
|
Start.PositionBindable.ValueChanged += onPositionChanged;
|
||||||
|
|
||||||
|
if (End != null)
|
||||||
|
{
|
||||||
|
End.DefaultsApplied += onDefaultsApplied;
|
||||||
|
End.PositionBindable.ValueChanged += onPositionChanged;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UnbindEvents()
|
||||||
|
{
|
||||||
|
if (Start != null)
|
||||||
|
{
|
||||||
|
Start.DefaultsApplied -= onDefaultsApplied;
|
||||||
|
Start.PositionBindable.ValueChanged -= onPositionChanged;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (End != null)
|
||||||
|
{
|
||||||
|
End.DefaultsApplied -= onDefaultsApplied;
|
||||||
|
End.PositionBindable.ValueChanged -= onPositionChanged;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onDefaultsApplied(HitObject obj) => refreshLifetimes();
|
||||||
|
|
||||||
|
private void onPositionChanged(ValueChangedEvent<Vector2> obj) => refreshLifetimes();
|
||||||
|
|
||||||
|
private void refreshLifetimes()
|
||||||
|
{
|
||||||
|
if (End == null || End.NewCombo || Start is Spinner || End is Spinner)
|
||||||
|
{
|
||||||
|
LifetimeEnd = LifetimeStart;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector2 startPosition = Start.StackedEndPosition;
|
||||||
|
Vector2 endPosition = End.StackedPosition;
|
||||||
|
Vector2 distanceVector = endPosition - startPosition;
|
||||||
|
|
||||||
|
// The lifetime start will match the fade-in time of the first follow point.
|
||||||
|
float fraction = (int)(FollowPointConnection.SPACING * 1.5) / distanceVector.Length;
|
||||||
|
FollowPointConnection.GetFadeTimes(Start, End, fraction, out var fadeInTime, out _);
|
||||||
|
|
||||||
|
LifetimeStart = fadeInTime;
|
||||||
|
LifetimeEnd = double.MaxValue; // This will be set by the connection.
|
||||||
|
|
||||||
|
Invalidated?.Invoke();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -2,53 +2,74 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Extensions;
|
using osu.Framework.Extensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Performance;
|
||||||
|
using osu.Framework.Graphics.Pooling;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
|
namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Visualises connections between <see cref="DrawableOsuHitObject"/>s.
|
/// Visualises connections between <see cref="DrawableOsuHitObject"/>s.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class FollowPointRenderer : LifetimeManagementContainer
|
public class FollowPointRenderer : CompositeDrawable
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// All the <see cref="FollowPointConnection"/>s contained by this <see cref="FollowPointRenderer"/>.
|
|
||||||
/// </summary>
|
|
||||||
internal IReadOnlyList<FollowPointConnection> Connections => connections;
|
|
||||||
|
|
||||||
private readonly List<FollowPointConnection> connections = new List<FollowPointConnection>();
|
|
||||||
|
|
||||||
public override bool RemoveCompletedTransforms => false;
|
public override bool RemoveCompletedTransforms => false;
|
||||||
|
|
||||||
/// <summary>
|
public IReadOnlyList<FollowPointLifetimeEntry> Entries => lifetimeEntries;
|
||||||
/// Adds the <see cref="FollowPoint"/>s around an <see cref="OsuHitObject"/>.
|
|
||||||
/// This includes <see cref="FollowPoint"/>s leading into <paramref name="hitObject"/>, and <see cref="FollowPoint"/>s exiting <paramref name="hitObject"/>.
|
private DrawablePool<FollowPointConnection> connectionPool;
|
||||||
/// </summary>
|
private DrawablePool<FollowPoint> pointPool;
|
||||||
/// <param name="hitObject">The <see cref="OsuHitObject"/> to add <see cref="FollowPoint"/>s for.</param>
|
|
||||||
|
private readonly List<FollowPointLifetimeEntry> lifetimeEntries = new List<FollowPointLifetimeEntry>();
|
||||||
|
private readonly Dictionary<LifetimeEntry, FollowPointConnection> connectionsInUse = new Dictionary<LifetimeEntry, FollowPointConnection>();
|
||||||
|
private readonly Dictionary<HitObject, IBindable> startTimeMap = new Dictionary<HitObject, IBindable>();
|
||||||
|
private readonly LifetimeEntryManager lifetimeManager = new LifetimeEntryManager();
|
||||||
|
|
||||||
|
public FollowPointRenderer()
|
||||||
|
{
|
||||||
|
lifetimeManager.EntryBecameAlive += onEntryBecameAlive;
|
||||||
|
lifetimeManager.EntryBecameDead += onEntryBecameDead;
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
InternalChildren = new Drawable[]
|
||||||
|
{
|
||||||
|
connectionPool = new DrawablePoolNoLifetime<FollowPointConnection>(1, 200),
|
||||||
|
pointPool = new DrawablePoolNoLifetime<FollowPoint>(50, 1000)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
public void AddFollowPoints(OsuHitObject hitObject)
|
public void AddFollowPoints(OsuHitObject hitObject)
|
||||||
=> addConnection(new FollowPointConnection(hitObject).With(g => g.StartTime.BindValueChanged(_ => onStartTimeChanged(g))));
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Removes the <see cref="FollowPoint"/>s around an <see cref="OsuHitObject"/>.
|
|
||||||
/// This includes <see cref="FollowPoint"/>s leading into <paramref name="hitObject"/>, and <see cref="FollowPoint"/>s exiting <paramref name="hitObject"/>.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="hitObject">The <see cref="OsuHitObject"/> to remove <see cref="FollowPoint"/>s for.</param>
|
|
||||||
public void RemoveFollowPoints(OsuHitObject hitObject) => removeGroup(connections.Single(g => g.Start == hitObject));
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Adds a <see cref="FollowPointConnection"/> to this <see cref="FollowPointRenderer"/>.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="connection">The <see cref="FollowPointConnection"/> to add.</param>
|
|
||||||
/// <returns>The index of <paramref name="connection"/> in <see cref="connections"/>.</returns>
|
|
||||||
private void addConnection(FollowPointConnection connection)
|
|
||||||
{
|
{
|
||||||
// Groups are sorted by their start time when added such that the index can be used to post-process other surrounding connections
|
addEntry(hitObject);
|
||||||
int index = connections.AddInPlace(connection, Comparer<FollowPointConnection>.Create((g1, g2) =>
|
|
||||||
|
var startTimeBindable = hitObject.StartTimeBindable.GetBoundCopy();
|
||||||
|
startTimeBindable.ValueChanged += _ => onStartTimeChanged(hitObject);
|
||||||
|
startTimeMap[hitObject] = startTimeBindable;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RemoveFollowPoints(OsuHitObject hitObject)
|
||||||
{
|
{
|
||||||
int comp = g1.StartTime.Value.CompareTo(g2.StartTime.Value);
|
removeEntry(hitObject);
|
||||||
|
|
||||||
|
startTimeMap[hitObject].UnbindAll();
|
||||||
|
startTimeMap.Remove(hitObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addEntry(OsuHitObject hitObject)
|
||||||
|
{
|
||||||
|
var newEntry = new FollowPointLifetimeEntry(hitObject);
|
||||||
|
|
||||||
|
var index = lifetimeEntries.AddInPlace(newEntry, Comparer<FollowPointLifetimeEntry>.Create((e1, e2) =>
|
||||||
|
{
|
||||||
|
int comp = e1.Start.StartTime.CompareTo(e2.Start.StartTime);
|
||||||
|
|
||||||
if (comp != 0)
|
if (comp != 0)
|
||||||
return comp;
|
return comp;
|
||||||
@ -61,19 +82,19 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
|
|||||||
return -1;
|
return -1;
|
||||||
}));
|
}));
|
||||||
|
|
||||||
if (index < connections.Count - 1)
|
if (index < lifetimeEntries.Count - 1)
|
||||||
{
|
{
|
||||||
// Update the connection's end point to the next connection's start point
|
// Update the connection's end point to the next connection's start point
|
||||||
// h1 -> -> -> h2
|
// h1 -> -> -> h2
|
||||||
// connection nextGroup
|
// connection nextGroup
|
||||||
|
|
||||||
FollowPointConnection nextConnection = connections[index + 1];
|
FollowPointLifetimeEntry nextEntry = lifetimeEntries[index + 1];
|
||||||
connection.End = nextConnection.Start;
|
newEntry.End = nextEntry.Start;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// The end point may be non-null during re-ordering
|
// The end point may be non-null during re-ordering
|
||||||
connection.End = null;
|
newEntry.End = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (index > 0)
|
if (index > 0)
|
||||||
@ -82,23 +103,22 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
|
|||||||
// h1 -> -> -> h2
|
// h1 -> -> -> h2
|
||||||
// prevGroup connection
|
// prevGroup connection
|
||||||
|
|
||||||
FollowPointConnection previousConnection = connections[index - 1];
|
FollowPointLifetimeEntry previousEntry = lifetimeEntries[index - 1];
|
||||||
previousConnection.End = connection.Start;
|
previousEntry.End = newEntry.Start;
|
||||||
}
|
}
|
||||||
|
|
||||||
AddInternal(connection);
|
lifetimeManager.AddEntry(newEntry);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
private void removeEntry(OsuHitObject hitObject)
|
||||||
/// Removes a <see cref="FollowPointConnection"/> from this <see cref="FollowPointRenderer"/>.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="connection">The <see cref="FollowPointConnection"/> to remove.</param>
|
|
||||||
/// <returns>Whether <paramref name="connection"/> was removed.</returns>
|
|
||||||
private void removeGroup(FollowPointConnection connection)
|
|
||||||
{
|
{
|
||||||
RemoveInternal(connection);
|
int index = lifetimeEntries.FindIndex(e => e.Start == hitObject);
|
||||||
|
|
||||||
int index = connections.IndexOf(connection);
|
var entry = lifetimeEntries[index];
|
||||||
|
entry.UnbindEvents();
|
||||||
|
|
||||||
|
lifetimeEntries.RemoveAt(index);
|
||||||
|
lifetimeManager.RemoveEntry(entry);
|
||||||
|
|
||||||
if (index > 0)
|
if (index > 0)
|
||||||
{
|
{
|
||||||
@ -106,18 +126,61 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
|
|||||||
// h1 -> -> -> h2 -> -> -> h3
|
// h1 -> -> -> h2 -> -> -> h3
|
||||||
// prevGroup connection nextGroup
|
// prevGroup connection nextGroup
|
||||||
// The current connection's end point is used since there may not be a next connection
|
// The current connection's end point is used since there may not be a next connection
|
||||||
FollowPointConnection previousConnection = connections[index - 1];
|
FollowPointLifetimeEntry previousEntry = lifetimeEntries[index - 1];
|
||||||
previousConnection.End = connection.End;
|
previousEntry.End = entry.End;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
connections.Remove(connection);
|
protected override bool CheckChildrenLife()
|
||||||
}
|
|
||||||
|
|
||||||
private void onStartTimeChanged(FollowPointConnection connection)
|
|
||||||
{
|
{
|
||||||
// Naive but can be improved if performance becomes an issue
|
bool anyAliveChanged = base.CheckChildrenLife();
|
||||||
removeGroup(connection);
|
anyAliveChanged |= lifetimeManager.Update(Time.Current);
|
||||||
addConnection(connection);
|
return anyAliveChanged;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onEntryBecameAlive(LifetimeEntry entry)
|
||||||
|
{
|
||||||
|
var connection = connectionPool.Get(c =>
|
||||||
|
{
|
||||||
|
c.Entry = (FollowPointLifetimeEntry)entry;
|
||||||
|
c.Pool = pointPool;
|
||||||
|
});
|
||||||
|
|
||||||
|
connectionsInUse[entry] = connection;
|
||||||
|
|
||||||
|
AddInternal(connection);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onEntryBecameDead(LifetimeEntry entry)
|
||||||
|
{
|
||||||
|
RemoveInternal(connectionsInUse[entry]);
|
||||||
|
connectionsInUse.Remove(entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onStartTimeChanged(OsuHitObject hitObject)
|
||||||
|
{
|
||||||
|
removeEntry(hitObject);
|
||||||
|
addEntry(hitObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Dispose(bool isDisposing)
|
||||||
|
{
|
||||||
|
base.Dispose(isDisposing);
|
||||||
|
|
||||||
|
foreach (var entry in lifetimeEntries)
|
||||||
|
entry.UnbindEvents();
|
||||||
|
lifetimeEntries.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
private class DrawablePoolNoLifetime<T> : DrawablePool<T>
|
||||||
|
where T : PoolableDrawable, new()
|
||||||
|
{
|
||||||
|
public override bool RemoveWhenNotAlive => false;
|
||||||
|
|
||||||
|
public DrawablePoolNoLifetime(int initialSize, int? maximumSize = null)
|
||||||
|
: base(initialSize, maximumSize)
|
||||||
|
{
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,7 +19,7 @@ using osuTK;
|
|||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||||
{
|
{
|
||||||
public class DrawableHitCircle : DrawableOsuHitObject, IDrawableHitObjectWithProxiedApproach
|
public class DrawableHitCircle : DrawableOsuHitObject
|
||||||
{
|
{
|
||||||
public OsuAction? HitAction => HitArea.HitAction;
|
public OsuAction? HitAction => HitArea.HitAction;
|
||||||
protected virtual OsuSkinComponents CirclePieceComponent => OsuSkinComponents.HitCircle;
|
protected virtual OsuSkinComponents CirclePieceComponent => OsuSkinComponents.HitCircle;
|
||||||
|
@ -9,9 +9,7 @@ using osu.Framework.Graphics;
|
|||||||
using osu.Game.Rulesets.Judgements;
|
using osu.Game.Rulesets.Judgements;
|
||||||
using osu.Game.Rulesets.Osu.Judgements;
|
using osu.Game.Rulesets.Osu.Judgements;
|
||||||
using osu.Game.Graphics.Containers;
|
using osu.Game.Graphics.Containers;
|
||||||
using osu.Game.Rulesets.Objects;
|
|
||||||
using osu.Game.Rulesets.Osu.UI;
|
using osu.Game.Rulesets.Osu.UI;
|
||||||
using osu.Game.Rulesets.Scoring;
|
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||||
@ -53,26 +51,19 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnApply(HitObject hitObject)
|
protected override void OnApply()
|
||||||
{
|
{
|
||||||
base.OnApply(hitObject);
|
base.OnApply();
|
||||||
|
|
||||||
IndexInCurrentComboBindable.BindTo(HitObject.IndexInCurrentComboBindable);
|
IndexInCurrentComboBindable.BindTo(HitObject.IndexInCurrentComboBindable);
|
||||||
PositionBindable.BindTo(HitObject.PositionBindable);
|
PositionBindable.BindTo(HitObject.PositionBindable);
|
||||||
StackHeightBindable.BindTo(HitObject.StackHeightBindable);
|
StackHeightBindable.BindTo(HitObject.StackHeightBindable);
|
||||||
ScaleBindable.BindTo(HitObject.ScaleBindable);
|
ScaleBindable.BindTo(HitObject.ScaleBindable);
|
||||||
|
|
||||||
// Manually set to reduce the number of future alive objects to a bare minimum.
|
|
||||||
LifetimeStart = HitObject.StartTime - HitObject.TimePreempt;
|
|
||||||
|
|
||||||
// Arbitrary lifetime end to prevent past objects in idle states remaining alive in non-frame-stable contexts.
|
|
||||||
// An extra 1000ms is added to always overestimate the true lifetime, and a more exact value is set by hit transforms and the following expiry.
|
|
||||||
LifetimeEnd = HitObject.GetEndTime() + HitObject.HitWindows.WindowFor(HitResult.Miss) + 1000;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnFree(HitObject hitObject)
|
protected override void OnFree()
|
||||||
{
|
{
|
||||||
base.OnFree(hitObject);
|
base.OnFree();
|
||||||
|
|
||||||
IndexInCurrentComboBindable.UnbindFrom(HitObject.IndexInCurrentComboBindable);
|
IndexInCurrentComboBindable.UnbindFrom(HitObject.IndexInCurrentComboBindable);
|
||||||
PositionBindable.UnbindFrom(HitObject.PositionBindable);
|
PositionBindable.UnbindFrom(HitObject.PositionBindable);
|
||||||
|
@ -44,26 +44,21 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private double fadeOutDelay;
|
|
||||||
protected override double FadeOutDelay => fadeOutDelay;
|
|
||||||
|
|
||||||
protected override void ApplyHitAnimations()
|
protected override void ApplyHitAnimations()
|
||||||
{
|
{
|
||||||
bool hitLightingEnabled = config.Get<bool>(OsuSetting.HitLighting);
|
bool hitLightingEnabled = config.Get<bool>(OsuSetting.HitLighting);
|
||||||
|
|
||||||
if (hitLightingEnabled)
|
Lighting.Alpha = 0;
|
||||||
{
|
|
||||||
JudgementBody.FadeIn().Delay(FadeInDuration).FadeOut(400);
|
|
||||||
|
|
||||||
|
if (hitLightingEnabled && Lighting.Drawable != null)
|
||||||
|
{
|
||||||
|
// todo: this animation changes slightly based on new/old legacy skin versions.
|
||||||
Lighting.ScaleTo(0.8f).ScaleTo(1.2f, 600, Easing.Out);
|
Lighting.ScaleTo(0.8f).ScaleTo(1.2f, 600, Easing.Out);
|
||||||
Lighting.FadeIn(200).Then().Delay(200).FadeOut(1000);
|
Lighting.FadeIn(200).Then().Delay(200).FadeOut(1000);
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
JudgementBody.Alpha = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
fadeOutDelay = hitLightingEnabled ? 1400 : base.FadeOutDelay;
|
// extend the lifetime to cover lighting fade
|
||||||
|
LifetimeEnd = Lighting.LatestTransformEndTime;
|
||||||
|
}
|
||||||
|
|
||||||
base.ApplyHitAnimations();
|
base.ApplyHitAnimations();
|
||||||
}
|
}
|
||||||
|
@ -1,32 +0,0 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using osu.Framework.Graphics;
|
|
||||||
using osu.Framework.Graphics.Pooling;
|
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|
||||||
{
|
|
||||||
public class DrawableOsuPool<T> : DrawablePool<T>
|
|
||||||
where T : DrawableHitObject, new()
|
|
||||||
{
|
|
||||||
private readonly Func<DrawableHitObject, double, bool> checkHittable;
|
|
||||||
private readonly Action<Drawable> onLoaded;
|
|
||||||
|
|
||||||
public DrawableOsuPool(Func<DrawableHitObject, double, bool> checkHittable, Action<Drawable> onLoaded, int initialSize, int? maximumSize = null)
|
|
||||||
: base(initialSize, maximumSize)
|
|
||||||
{
|
|
||||||
this.checkHittable = checkHittable;
|
|
||||||
this.onLoaded = onLoaded;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override T CreateNewDrawable() => base.CreateNewDrawable().With(o =>
|
|
||||||
{
|
|
||||||
var osuObject = (DrawableOsuHitObject)(object)o;
|
|
||||||
|
|
||||||
osuObject.CheckHittable = checkHittable;
|
|
||||||
osuObject.OnLoadComplete += onLoaded;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
@ -20,7 +20,7 @@ using osu.Game.Skinning;
|
|||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||||
{
|
{
|
||||||
public class DrawableSlider : DrawableOsuHitObject, IDrawableHitObjectWithProxiedApproach
|
public class DrawableSlider : DrawableOsuHitObject
|
||||||
{
|
{
|
||||||
public new Slider HitObject => (Slider)base.HitObject;
|
public new Slider HitObject => (Slider)base.HitObject;
|
||||||
|
|
||||||
@ -87,18 +87,18 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
Tracking.BindValueChanged(updateSlidingSample);
|
Tracking.BindValueChanged(updateSlidingSample);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnApply(HitObject hitObject)
|
protected override void OnApply()
|
||||||
{
|
{
|
||||||
base.OnApply(hitObject);
|
base.OnApply();
|
||||||
|
|
||||||
// Ensure that the version will change after the upcoming BindTo().
|
// Ensure that the version will change after the upcoming BindTo().
|
||||||
pathVersion.Value = int.MaxValue;
|
pathVersion.Value = int.MaxValue;
|
||||||
PathVersion.BindTo(HitObject.Path.Version);
|
PathVersion.BindTo(HitObject.Path.Version);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnFree(HitObject hitObject)
|
protected override void OnFree()
|
||||||
{
|
{
|
||||||
base.OnFree(hitObject);
|
base.OnFree();
|
||||||
|
|
||||||
PathVersion.UnbindFrom(HitObject.Path.Version);
|
PathVersion.UnbindFrom(HitObject.Path.Version);
|
||||||
|
|
||||||
@ -113,8 +113,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
|
|
||||||
if (firstSample != null)
|
if (firstSample != null)
|
||||||
{
|
{
|
||||||
var clone = HitObject.SampleControlPoint.ApplyTo(firstSample);
|
var clone = HitObject.SampleControlPoint.ApplyTo(firstSample).With("sliderslide");
|
||||||
clone.Name = "sliderslide";
|
|
||||||
|
|
||||||
slidingSample.Samples = new ISampleInfo[] { clone };
|
slidingSample.Samples = new ISampleInfo[] { clone };
|
||||||
}
|
}
|
||||||
@ -250,7 +249,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
if (userTriggered || Time.Current < HitObject.EndTime)
|
if (userTriggered || Time.Current < HitObject.EndTime)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
ApplyResult(r => r.Type = r.Judgement.MaxResult);
|
ApplyResult(r => r.Type = NestedHitObjects.Any(h => h.Result.IsHit) ? r.Judgement.MaxResult : r.Judgement.MinResult);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void PlaySamples()
|
public override void PlaySamples()
|
||||||
@ -289,14 +288,14 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
{
|
{
|
||||||
case ArmedState.Hit:
|
case ArmedState.Hit:
|
||||||
Ball.ScaleTo(HitObject.Scale * 1.4f, fade_out_time, Easing.Out);
|
Ball.ScaleTo(HitObject.Scale * 1.4f, fade_out_time, Easing.Out);
|
||||||
|
if (sliderBody?.SnakingOut.Value == true)
|
||||||
|
Body.FadeOut(40); // short fade to allow for any body colour to smoothly disappear.
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.FadeOut(fade_out_time, Easing.OutQuint).Expire();
|
this.FadeOut(fade_out_time, Easing.OutQuint).Expire();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Drawable ProxiedLayer => HeadCircle.ProxiedLayer;
|
|
||||||
|
|
||||||
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => sliderBody?.ReceivePositionalInputAt(screenSpacePos) ?? base.ReceivePositionalInputAt(screenSpacePos);
|
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => sliderBody?.ReceivePositionalInputAt(screenSpacePos) ?? base.ReceivePositionalInputAt(screenSpacePos);
|
||||||
|
|
||||||
private class DefaultSliderBody : PlaySliderBody
|
private class DefaultSliderBody : PlaySliderBody
|
||||||
|
@ -2,24 +2,25 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using JetBrains.Annotations;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Game.Rulesets.Objects;
|
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
|
||||||
using osu.Game.Rulesets.Objects.Types;
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||||
{
|
{
|
||||||
public class DrawableSliderHead : DrawableHitCircle
|
public class DrawableSliderHead : DrawableHitCircle
|
||||||
{
|
{
|
||||||
|
[CanBeNull]
|
||||||
|
public Slider Slider => DrawableSlider?.HitObject;
|
||||||
|
|
||||||
|
protected DrawableSlider DrawableSlider => (DrawableSlider)ParentHitObject;
|
||||||
|
|
||||||
private readonly IBindable<int> pathVersion = new Bindable<int>();
|
private readonly IBindable<int> pathVersion = new Bindable<int>();
|
||||||
|
|
||||||
protected override OsuSkinComponents CirclePieceComponent => OsuSkinComponents.SliderHeadHitCircle;
|
protected override OsuSkinComponents CirclePieceComponent => OsuSkinComponents.SliderHeadHitCircle;
|
||||||
|
|
||||||
private DrawableSlider drawableSlider;
|
|
||||||
|
|
||||||
private Slider slider => drawableSlider?.HitObject;
|
|
||||||
|
|
||||||
public DrawableSliderHead()
|
public DrawableSliderHead()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
@ -36,34 +37,34 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
pathVersion.BindValueChanged(_ => updatePosition());
|
pathVersion.BindValueChanged(_ => updatePosition());
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnFree(HitObject hitObject)
|
protected override void OnFree()
|
||||||
{
|
{
|
||||||
base.OnFree(hitObject);
|
base.OnFree();
|
||||||
|
|
||||||
pathVersion.UnbindFrom(drawableSlider.PathVersion);
|
pathVersion.UnbindFrom(DrawableSlider.PathVersion);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnParentReceived(DrawableHitObject parent)
|
protected override void OnApply()
|
||||||
{
|
{
|
||||||
base.OnParentReceived(parent);
|
base.OnApply();
|
||||||
|
|
||||||
drawableSlider = (DrawableSlider)parent;
|
pathVersion.BindTo(DrawableSlider.PathVersion);
|
||||||
|
|
||||||
pathVersion.BindTo(drawableSlider.PathVersion);
|
OnShake = DrawableSlider.Shake;
|
||||||
|
CheckHittable = (d, t) => DrawableSlider.CheckHittable?.Invoke(d, t) ?? true;
|
||||||
OnShake = drawableSlider.Shake;
|
|
||||||
CheckHittable = (d, t) => drawableSlider.CheckHittable?.Invoke(d, t) ?? true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Update()
|
protected override void Update()
|
||||||
{
|
{
|
||||||
base.Update();
|
base.Update();
|
||||||
|
|
||||||
double completionProgress = Math.Clamp((Time.Current - slider.StartTime) / slider.Duration, 0, 1);
|
Debug.Assert(Slider != null);
|
||||||
|
|
||||||
|
double completionProgress = Math.Clamp((Time.Current - Slider.StartTime) / Slider.Duration, 0, 1);
|
||||||
|
|
||||||
//todo: we probably want to reconsider this before adding scoring, but it looks and feels nice.
|
//todo: we probably want to reconsider this before adding scoring, but it looks and feels nice.
|
||||||
if (!IsHit)
|
if (!IsHit)
|
||||||
Position = slider.CurvePositionAt(completionProgress);
|
Position = Slider.CurvePositionAt(completionProgress);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Action<double> OnShake;
|
public Action<double> OnShake;
|
||||||
@ -72,8 +73,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
|
|
||||||
private void updatePosition()
|
private void updatePosition()
|
||||||
{
|
{
|
||||||
if (slider != null)
|
if (Slider != null)
|
||||||
Position = HitObject.Position - slider.Position;
|
Position = HitObject.Position - Slider.Position;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using JetBrains.Annotations;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
@ -18,6 +19,11 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
{
|
{
|
||||||
public new SliderRepeat HitObject => (SliderRepeat)base.HitObject;
|
public new SliderRepeat HitObject => (SliderRepeat)base.HitObject;
|
||||||
|
|
||||||
|
[CanBeNull]
|
||||||
|
public Slider Slider => DrawableSlider?.HitObject;
|
||||||
|
|
||||||
|
protected DrawableSlider DrawableSlider => (DrawableSlider)ParentHitObject;
|
||||||
|
|
||||||
private double animDuration;
|
private double animDuration;
|
||||||
|
|
||||||
public Drawable CirclePiece { get; private set; }
|
public Drawable CirclePiece { get; private set; }
|
||||||
@ -26,8 +32,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
|
|
||||||
public override bool DisplayResult => false;
|
public override bool DisplayResult => false;
|
||||||
|
|
||||||
private DrawableSlider drawableSlider;
|
|
||||||
|
|
||||||
public DrawableSliderRepeat()
|
public DrawableSliderRepeat()
|
||||||
: base(null)
|
: base(null)
|
||||||
{
|
{
|
||||||
@ -60,19 +64,17 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
ScaleBindable.BindValueChanged(scale => scaleContainer.Scale = new Vector2(scale.NewValue));
|
ScaleBindable.BindValueChanged(scale => scaleContainer.Scale = new Vector2(scale.NewValue));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnParentReceived(DrawableHitObject parent)
|
protected override void OnApply()
|
||||||
{
|
{
|
||||||
base.OnParentReceived(parent);
|
base.OnApply();
|
||||||
|
|
||||||
drawableSlider = (DrawableSlider)parent;
|
Position = HitObject.Position - DrawableSlider.Position;
|
||||||
|
|
||||||
Position = HitObject.Position - drawableSlider.Position;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void CheckForResult(bool userTriggered, double timeOffset)
|
protected override void CheckForResult(bool userTriggered, double timeOffset)
|
||||||
{
|
{
|
||||||
if (HitObject.StartTime <= Time.Current)
|
if (HitObject.StartTime <= Time.Current)
|
||||||
ApplyResult(r => r.Type = drawableSlider.Tracking.Value ? r.Judgement.MaxResult : r.Judgement.MinResult);
|
ApplyResult(r => r.Type = DrawableSlider.Tracking.Value ? r.Judgement.MaxResult : r.Judgement.MinResult);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void UpdateInitialTransforms()
|
protected override void UpdateInitialTransforms()
|
||||||
@ -114,7 +116,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
if (IsHit) return;
|
if (IsHit) return;
|
||||||
|
|
||||||
bool isRepeatAtEnd = HitObject.RepeatIndex % 2 == 0;
|
bool isRepeatAtEnd = HitObject.RepeatIndex % 2 == 0;
|
||||||
List<Vector2> curve = ((PlaySliderBody)drawableSlider.Body.Drawable).CurrentCurve;
|
List<Vector2> curve = ((PlaySliderBody)DrawableSlider.Body.Drawable).CurrentCurve;
|
||||||
|
|
||||||
Position = isRepeatAtEnd ? end : start;
|
Position = isRepeatAtEnd ? end : start;
|
||||||
|
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
using JetBrains.Annotations;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
@ -15,6 +16,11 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
{
|
{
|
||||||
public new SliderTailCircle HitObject => (SliderTailCircle)base.HitObject;
|
public new SliderTailCircle HitObject => (SliderTailCircle)base.HitObject;
|
||||||
|
|
||||||
|
[CanBeNull]
|
||||||
|
public Slider Slider => DrawableSlider?.HitObject;
|
||||||
|
|
||||||
|
protected DrawableSlider DrawableSlider => (DrawableSlider)ParentHitObject;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The judgement text is provided by the <see cref="DrawableSlider"/>.
|
/// The judgement text is provided by the <see cref="DrawableSlider"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -22,6 +22,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
|
|
||||||
public override bool DisplayResult => false;
|
public override bool DisplayResult => false;
|
||||||
|
|
||||||
|
protected DrawableSlider DrawableSlider => (DrawableSlider)ParentHitObject;
|
||||||
|
|
||||||
private SkinnableDrawable scaleContainer;
|
private SkinnableDrawable scaleContainer;
|
||||||
|
|
||||||
public DrawableSliderTick()
|
public DrawableSliderTick()
|
||||||
@ -62,11 +64,11 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
ScaleBindable.BindValueChanged(scale => scaleContainer.Scale = new Vector2(scale.NewValue));
|
ScaleBindable.BindValueChanged(scale => scaleContainer.Scale = new Vector2(scale.NewValue));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnParentReceived(DrawableHitObject parent)
|
protected override void OnApply()
|
||||||
{
|
{
|
||||||
base.OnParentReceived(parent);
|
base.OnApply();
|
||||||
|
|
||||||
Position = HitObject.Position - ((DrawableSlider)parent).HitObject.Position;
|
Position = HitObject.Position - DrawableSlider.HitObject.Position;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void CheckForResult(bool userTriggered, double timeOffset)
|
protected override void CheckForResult(bool userTriggered, double timeOffset)
|
||||||
|
@ -104,9 +104,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
private const float spinning_sample_initial_frequency = 1.0f;
|
private const float spinning_sample_initial_frequency = 1.0f;
|
||||||
private const float spinning_sample_modulated_base_frequency = 0.5f;
|
private const float spinning_sample_modulated_base_frequency = 0.5f;
|
||||||
|
|
||||||
protected override void OnFree(HitObject hitObject)
|
protected override void OnFree()
|
||||||
{
|
{
|
||||||
base.OnFree(hitObject);
|
base.OnFree();
|
||||||
|
|
||||||
spinningSample.Samples = null;
|
spinningSample.Samples = null;
|
||||||
}
|
}
|
||||||
@ -119,8 +119,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
|
|
||||||
if (firstSample != null)
|
if (firstSample != null)
|
||||||
{
|
{
|
||||||
var clone = HitObject.SampleControlPoint.ApplyTo(firstSample);
|
var clone = HitObject.SampleControlPoint.ApplyTo(firstSample).With("spinnerspin");
|
||||||
clone.Name = "spinnerspin";
|
|
||||||
|
|
||||||
spinningSample.Samples = new ISampleInfo[] { clone };
|
spinningSample.Samples = new ISampleInfo[] { clone };
|
||||||
spinningSample.Frequency.Value = spinning_sample_initial_frequency;
|
spinningSample.Frequency.Value = spinning_sample_initial_frequency;
|
||||||
|
@ -7,6 +7,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
{
|
{
|
||||||
public override bool DisplayResult => false;
|
public override bool DisplayResult => false;
|
||||||
|
|
||||||
|
protected DrawableSpinner DrawableSpinner => (DrawableSpinner)ParentHitObject;
|
||||||
|
|
||||||
public DrawableSpinnerTick()
|
public DrawableSpinnerTick()
|
||||||
: base(null)
|
: base(null)
|
||||||
{
|
{
|
||||||
@ -17,6 +19,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override double MaximumJudgementOffset => DrawableSpinner.HitObject.Duration;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Apply a judgement result.
|
/// Apply a judgement result.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -38,7 +38,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly IBindable<ArmedState> state = new Bindable<ArmedState>();
|
|
||||||
private readonly IBindable<Color4> accentColour = new Bindable<Color4>();
|
private readonly IBindable<Color4> accentColour = new Bindable<Color4>();
|
||||||
private readonly IBindable<int> indexInCurrentCombo = new Bindable<int>();
|
private readonly IBindable<int> indexInCurrentCombo = new Bindable<int>();
|
||||||
|
|
||||||
@ -50,7 +49,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
|
|||||||
{
|
{
|
||||||
var drawableOsuObject = (DrawableOsuHitObject)drawableObject;
|
var drawableOsuObject = (DrawableOsuHitObject)drawableObject;
|
||||||
|
|
||||||
state.BindTo(drawableObject.State);
|
|
||||||
accentColour.BindTo(drawableObject.AccentColour);
|
accentColour.BindTo(drawableObject.AccentColour);
|
||||||
indexInCurrentCombo.BindTo(drawableOsuObject.IndexInCurrentComboBindable);
|
indexInCurrentCombo.BindTo(drawableOsuObject.IndexInCurrentComboBindable);
|
||||||
}
|
}
|
||||||
@ -59,7 +57,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
|
|||||||
{
|
{
|
||||||
base.LoadComplete();
|
base.LoadComplete();
|
||||||
|
|
||||||
state.BindValueChanged(updateState, true);
|
|
||||||
accentColour.BindValueChanged(colour =>
|
accentColour.BindValueChanged(colour =>
|
||||||
{
|
{
|
||||||
explode.Colour = colour.NewValue;
|
explode.Colour = colour.NewValue;
|
||||||
@ -68,15 +65,18 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
|
|||||||
}, true);
|
}, true);
|
||||||
|
|
||||||
indexInCurrentCombo.BindValueChanged(index => number.Text = (index.NewValue + 1).ToString(), true);
|
indexInCurrentCombo.BindValueChanged(index => number.Text = (index.NewValue + 1).ToString(), true);
|
||||||
|
|
||||||
|
drawableObject.ApplyCustomUpdateState += updateState;
|
||||||
|
updateState(drawableObject, drawableObject.State.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateState(ValueChangedEvent<ArmedState> state)
|
private void updateState(DrawableHitObject drawableObject, ArmedState state)
|
||||||
{
|
{
|
||||||
using (BeginAbsoluteSequence(drawableObject.HitStateUpdateTime, true))
|
using (BeginAbsoluteSequence(drawableObject.HitStateUpdateTime, true))
|
||||||
{
|
{
|
||||||
glow.FadeOut(400);
|
glow.FadeOut(400);
|
||||||
|
|
||||||
switch (state.NewValue)
|
switch (state)
|
||||||
{
|
{
|
||||||
case ArmedState.Hit:
|
case ArmedState.Hit:
|
||||||
const double flash_in = 40;
|
const double flash_in = 40;
|
||||||
|
@ -248,7 +248,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void trackingChanged(ValueChangedEvent<bool> tracking) =>
|
private void trackingChanged(ValueChangedEvent<bool> tracking) =>
|
||||||
box.FadeTo(tracking.NewValue ? 0.6f : 0.05f, 200, Easing.OutQuint);
|
box.FadeTo(tracking.NewValue ? 0.3f : 0.05f, 200, Easing.OutQuint);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user