mirror of
https://github.com/ppy/osu.git
synced 2024-12-18 13:52:54 +08:00
Merge branch 'master' into present-recommended
This commit is contained in:
commit
2b30a5f217
@ -5,6 +5,6 @@
|
||||
"version": "3.1.100"
|
||||
},
|
||||
"msbuild-sdks": {
|
||||
"Microsoft.Build.Traversal": "2.2.3"
|
||||
"Microsoft.Build.Traversal": "3.0.2"
|
||||
}
|
||||
}
|
@ -51,7 +51,7 @@
|
||||
<Reference Include="Java.Interop" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2020.1030.0" />
|
||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2020.1120.0" />
|
||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2020.1202.0" />
|
||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2020.1201.0" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
@ -59,7 +59,7 @@ namespace osu.Desktop
|
||||
try
|
||||
{
|
||||
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))
|
||||
return stableInstallPath;
|
||||
@ -138,7 +138,7 @@ namespace osu.Desktop
|
||||
break;
|
||||
|
||||
// SDL2 DesktopWindow
|
||||
case DesktopWindow desktopWindow:
|
||||
case SDL2DesktopWindow desktopWindow:
|
||||
desktopWindow.CursorState |= CursorState.Hidden;
|
||||
desktopWindow.SetIconFromStream(iconStream);
|
||||
desktopWindow.Title = Name;
|
||||
|
@ -22,9 +22,9 @@ namespace osu.Desktop
|
||||
{
|
||||
// Back up the cwd before DesktopGameHost changes it
|
||||
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;
|
||||
|
||||
|
@ -24,12 +24,12 @@
|
||||
<ProjectReference Include="..\osu.Game.Rulesets.Taiko\osu.Game.Rulesets.Taiko.csproj" />
|
||||
</ItemGroup>
|
||||
<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="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.6" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="2.2.6" />
|
||||
<PackageReference Include="Microsoft.Win32.Registry" Version="4.7.0" />
|
||||
<PackageReference Include="DiscordRichPresence" Version="1.0.150" />
|
||||
<PackageReference Include="Microsoft.Win32.Registry" Version="5.0.0" />
|
||||
<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. -->
|
||||
<PackageReference Include="System.IO.FileSystem.Primitives" 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
|
||||
{
|
||||
X = 0,
|
||||
StartTime = 250
|
||||
StartTime = 1000
|
||||
},
|
||||
new Fruit
|
||||
{
|
||||
X = CatchPlayfield.WIDTH,
|
||||
StartTime = 500
|
||||
StartTime = 2000
|
||||
},
|
||||
new JuiceStream
|
||||
{
|
||||
X = CatchPlayfield.CENTER_X,
|
||||
StartTime = 750,
|
||||
StartTime = 3000,
|
||||
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,
|
||||
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();
|
||||
}
|
||||
}
|
@ -10,14 +10,12 @@ using osu.Framework.Testing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Rulesets.Catch.Beatmaps;
|
||||
using osu.Game.Rulesets.Catch.Judgements;
|
||||
using osu.Game.Rulesets.Catch.Objects;
|
||||
using osu.Game.Rulesets.Catch.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Catch.UI;
|
||||
using osu.Game.Rulesets.Judgements;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Rulesets.UI;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Tests
|
||||
{
|
||||
@ -103,7 +101,6 @@ namespace osu.Game.Rulesets.Catch.Tests
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.TopCentre,
|
||||
CreateDrawableRepresentation = ((DrawableRuleset<CatchHitObject>)catchRuleset.CreateInstance().CreateDrawableRulesetWith(new CatchBeatmap())).CreateDrawableRepresentation
|
||||
},
|
||||
});
|
||||
}
|
||||
|
@ -136,7 +136,7 @@ namespace osu.Game.Rulesets.Catch.Tests
|
||||
if (juice.NestedHitObjects.Last() is CatchHitObject tail)
|
||||
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)
|
||||
|
@ -1,9 +1,10 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Rulesets.Catch.Objects;
|
||||
using osu.Game.Rulesets.Catch.Objects.Drawables;
|
||||
using osuTK;
|
||||
@ -17,34 +18,49 @@ namespace osu.Game.Rulesets.Catch.Tests
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
foreach (FruitVisualRepresentation rep in Enum.GetValues(typeof(FruitVisualRepresentation)))
|
||||
AddStep($"show {rep}", () => SetContents(() => createDrawableFruit(rep)));
|
||||
AddStep("show pear", () => SetContents(() => createDrawableFruit(0)));
|
||||
AddStep("show grape", () => SetContents(() => createDrawableFruit(1)));
|
||||
AddStep("show pineapple / apple", () => SetContents(() => createDrawableFruit(2)));
|
||||
AddStep("show raspberry / orange", () => SetContents(() => createDrawableFruit(3)));
|
||||
|
||||
AddStep("show banana", () => SetContents(createDrawableBanana));
|
||||
|
||||
AddStep("show droplet", () => SetContents(() => createDrawableDroplet()));
|
||||
AddStep("show tiny droplet", () => SetContents(createDrawableTinyDroplet));
|
||||
|
||||
foreach (FruitVisualRepresentation rep in Enum.GetValues(typeof(FruitVisualRepresentation)))
|
||||
AddStep($"show hyperdash {rep}", () => SetContents(() => createDrawableFruit(rep, true)));
|
||||
AddStep("show hyperdash pear", () => SetContents(() => createDrawableFruit(0, true)));
|
||||
AddStep("show hyperdash grape", () => SetContents(() => createDrawableFruit(1, true)));
|
||||
AddStep("show hyperdash pineapple / apple", () => SetContents(() => createDrawableFruit(2, true)));
|
||||
AddStep("show hyperdash raspberry / orange", () => SetContents(() => createDrawableFruit(3, true)));
|
||||
|
||||
AddStep("show hyperdash droplet", () => SetContents(() => createDrawableDroplet(true)));
|
||||
}
|
||||
|
||||
private Drawable createDrawableFruit(FruitVisualRepresentation rep, bool hyperdash = false) =>
|
||||
setProperties(new DrawableFruit(new TestCatchFruit(rep)), hyperdash);
|
||||
private Drawable createDrawableFruit(int indexInBeatmap, bool hyperdash = false) =>
|
||||
SetProperties(new DrawableFruit(new Fruit
|
||||
{
|
||||
IndexInBeatmap = indexInBeatmap,
|
||||
HyperDashBindable = { Value = hyperdash }
|
||||
}));
|
||||
|
||||
private Drawable createDrawableDroplet(bool hyperdash = false) => setProperties(new DrawableDroplet(new Droplet()), hyperdash);
|
||||
private Drawable createDrawableBanana() =>
|
||||
SetProperties(new DrawableBanana(new Banana()));
|
||||
|
||||
private Drawable createDrawableTinyDroplet() => setProperties(new DrawableTinyDroplet(new TinyDroplet()));
|
||||
private Drawable createDrawableDroplet(bool hyperdash = false) =>
|
||||
SetProperties(new DrawableDroplet(new Droplet
|
||||
{
|
||||
HyperDashBindable = { Value = hyperdash }
|
||||
}));
|
||||
|
||||
private DrawableCatchHitObject setProperties(DrawableCatchHitObject d, bool hyperdash = false)
|
||||
private Drawable createDrawableTinyDroplet() => SetProperties(new DrawableTinyDroplet(new TinyDroplet()));
|
||||
|
||||
protected virtual DrawableCatchHitObject SetProperties(DrawableCatchHitObject d)
|
||||
{
|
||||
var hitObject = d.HitObject;
|
||||
hitObject.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty { CircleSize = 0 });
|
||||
hitObject.StartTime = 1000000000000;
|
||||
hitObject.Scale = 1.5f;
|
||||
|
||||
if (hyperdash)
|
||||
hitObject.HyperDashTarget = new Banana();
|
||||
|
||||
d.Anchor = Anchor.Centre;
|
||||
d.RelativePositionAxes = Axes.None;
|
||||
d.Position = Vector2.Zero;
|
||||
@ -55,15 +71,5 @@ namespace osu.Game.Rulesets.Catch.Tests
|
||||
};
|
||||
return d;
|
||||
}
|
||||
|
||||
public class TestCatchFruit : Fruit
|
||||
{
|
||||
public TestCatchFruit(FruitVisualRepresentation rep)
|
||||
{
|
||||
VisualRepresentation = rep;
|
||||
}
|
||||
|
||||
public override FruitVisualRepresentation VisualRepresentation { get; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
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(() => SetProperties(new DrawableFruit(new Fruit
|
||||
{
|
||||
IndexInBeatmapBindable = { BindTarget = indexInBeatmap },
|
||||
HyperDashBindable = { BindTarget = hyperDash },
|
||||
}))));
|
||||
|
||||
AddStep("droplet changes hyper", () => SetContents(() => SetProperties(new DrawableDroplet(new Droplet
|
||||
{
|
||||
HyperDashBindable = { BindTarget = hyperDash },
|
||||
}))));
|
||||
|
||||
Scheduler.AddDelayed(() => indexInBeatmap.Value++, 250, true);
|
||||
Scheduler.AddDelayed(() => hyperDash.Value = !hyperDash.Value, 1000, true);
|
||||
}
|
||||
}
|
||||
}
|
@ -2,7 +2,7 @@
|
||||
<Import Project="..\osu.TestProject.props" />
|
||||
<ItemGroup Label="Package References">
|
||||
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.1" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.0" />
|
||||
<PackageReference Include="NUnit" Version="3.12.0" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="3.17.0" />
|
||||
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
|
||||
|
@ -5,11 +5,11 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Catch.MathUtils;
|
||||
using osu.Game.Rulesets.Catch.Objects;
|
||||
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.Objects.Types;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Beatmaps
|
||||
{
|
||||
@ -192,24 +192,24 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
|
||||
|
||||
private static void initialiseHyperDash(IBeatmap beatmap)
|
||||
{
|
||||
List<CatchHitObject> objectWithDroplets = new List<CatchHitObject>();
|
||||
List<PalpableCatchHitObject> palpableObjects = new List<PalpableCatchHitObject>();
|
||||
|
||||
foreach (var currentObject in beatmap.HitObjects)
|
||||
{
|
||||
if (currentObject is Fruit fruitObject)
|
||||
objectWithDroplets.Add(fruitObject);
|
||||
palpableObjects.Add(fruitObject);
|
||||
|
||||
if (currentObject is JuiceStream)
|
||||
{
|
||||
foreach (var currentJuiceElement in currentObject.NestedHitObjects)
|
||||
foreach (var juice in currentObject.NestedHitObjects)
|
||||
{
|
||||
if (!(currentJuiceElement is TinyDroplet))
|
||||
objectWithDroplets.Add((CatchHitObject)currentJuiceElement);
|
||||
if (juice is PalpableCatchHitObject palpableObject && !(juice is TinyDroplet))
|
||||
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;
|
||||
|
||||
@ -221,10 +221,10 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
|
||||
int lastDirection = 0;
|
||||
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];
|
||||
CatchHitObject nextObject = objectWithDroplets[i + 1];
|
||||
var currentObject = palpableObjects[i];
|
||||
var nextObject = palpableObjects[i + 1];
|
||||
|
||||
// Reset variables in-case values have changed (e.g. after applying HR)
|
||||
currentObject.HyperDashTarget = null;
|
||||
|
@ -12,9 +12,9 @@ namespace osu.Game.Rulesets.Catch.Difficulty.Preprocessing
|
||||
{
|
||||
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 LastNormalizedPosition;
|
||||
|
@ -5,7 +5,7 @@ using osu.Game.Rulesets.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!";
|
||||
}
|
||||
|
@ -1,22 +1,27 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
#nullable enable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Audio;
|
||||
using osu.Game.Rulesets.Catch.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
|
||||
{
|
||||
public class Banana : Fruit
|
||||
public class Banana : Fruit, IHasComboInformation
|
||||
{
|
||||
/// <summary>
|
||||
/// Index of banana in current shower.
|
||||
/// </summary>
|
||||
public int BananaIndex;
|
||||
|
||||
public override FruitVisualRepresentation VisualRepresentation => FruitVisualRepresentation.Banana;
|
||||
|
||||
public override Judgement CreateJudgement() => new CatchBananaJudgement();
|
||||
|
||||
private static readonly List<HitSampleInfo> samples = new List<HitSampleInfo> { new BananaHitSampleInfo() };
|
||||
@ -26,11 +31,50 @@ namespace osu.Game.Rulesets.Catch.Objects
|
||||
Samples = samples;
|
||||
}
|
||||
|
||||
private class BananaHitSampleInfo : HitSampleInfo
|
||||
{
|
||||
private static string[] lookupNames { get; } = { "metronomelow", "catch-banana" };
|
||||
private Color4? colour;
|
||||
|
||||
public override IEnumerable<string> LookupNames => lookupNames;
|
||||
Color4 IHasComboInformation.GetComboColour(IReadOnlyList<Color4> comboColours)
|
||||
{
|
||||
// override any external colour changes with banananana
|
||||
return colour ??= getBananaColour();
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
private class BananaHitSampleInfo : HitSampleInfo, IEquatable<BananaHitSampleInfo>
|
||||
{
|
||||
private static readonly string[] lookup_names = { "Gameplay/metronomelow", "Gameplay/catch-banana" };
|
||||
|
||||
public override IEnumerable<string> LookupNames => lookup_names;
|
||||
|
||||
public BananaHitSampleInfo(int volume = 0)
|
||||
: base(string.Empty, volume: volume)
|
||||
{
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -9,8 +9,6 @@ namespace osu.Game.Rulesets.Catch.Objects
|
||||
{
|
||||
public class BananaShower : CatchHitObject, IHasDuration
|
||||
{
|
||||
public override FruitVisualRepresentation VisualRepresentation => FruitVisualRepresentation.Banana;
|
||||
|
||||
public override bool LastInCombo => true;
|
||||
|
||||
public override Judgement CreateJudgement() => new IgnoreJudgement();
|
||||
|
@ -16,32 +16,47 @@ namespace osu.Game.Rulesets.Catch.Objects
|
||||
{
|
||||
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>
|
||||
/// The horizontal position of the fruit between 0 and <see cref="CatchPlayfield.WIDTH"/>.
|
||||
/// </summary>
|
||||
public float X
|
||||
{
|
||||
get => x + XOffset;
|
||||
set => x = value;
|
||||
// TODO: I don't like this asymmetry.
|
||||
get => XBindable.Value;
|
||||
// originalX is set by `XBindable.BindValueChanged`
|
||||
set => XBindable.Value = value + xOffset;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Whether this object can be placed on the catcher's plate.
|
||||
/// </summary>
|
||||
public virtual bool CanBePlated => false;
|
||||
private float xOffset;
|
||||
|
||||
/// <summary>
|
||||
/// A random offset applied to <see cref="X"/>, set by the <see cref="CatchBeatmapProcessor"/>.
|
||||
/// </summary>
|
||||
internal float XOffset { get; set; }
|
||||
internal float XOffset
|
||||
{
|
||||
get => xOffset;
|
||||
set
|
||||
{
|
||||
xOffset = value;
|
||||
XBindable.Value = originalX + xOffset;
|
||||
}
|
||||
}
|
||||
|
||||
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; }
|
||||
|
||||
@ -63,13 +78,6 @@ namespace osu.Game.Rulesets.Catch.Objects
|
||||
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>();
|
||||
|
||||
/// <summary>
|
||||
@ -81,17 +89,13 @@ namespace osu.Game.Rulesets.Catch.Objects
|
||||
set => LastInComboBindable.Value = value;
|
||||
}
|
||||
|
||||
public float Scale { get; set; } = 1;
|
||||
public readonly Bindable<float> ScaleBindable = new Bindable<float>(1);
|
||||
|
||||
/// <summary>
|
||||
/// Whether this fruit can initiate a hyperdash.
|
||||
/// </summary>
|
||||
public bool HyperDash => HyperDashTarget != null;
|
||||
|
||||
/// <summary>
|
||||
/// The target fruit if we are to initiate a hyperdash.
|
||||
/// </summary>
|
||||
public CatchHitObject HyperDashTarget;
|
||||
public float Scale
|
||||
{
|
||||
get => ScaleBindable.Value;
|
||||
set => ScaleBindable.Value = value;
|
||||
}
|
||||
|
||||
protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty)
|
||||
{
|
||||
@ -103,22 +107,10 @@ namespace osu.Game.Rulesets.Catch.Objects
|
||||
}
|
||||
|
||||
protected override HitWindows CreateHitWindows() => HitWindows.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a single object that can be caught by the catcher.
|
||||
/// </summary>
|
||||
public abstract class PalpableCatchHitObject : CatchHitObject
|
||||
protected CatchHitObject()
|
||||
{
|
||||
public override bool CanBePlated => true;
|
||||
}
|
||||
|
||||
public enum FruitVisualRepresentation
|
||||
{
|
||||
Pear,
|
||||
Grape,
|
||||
Pineapple,
|
||||
Raspberry,
|
||||
Banana // banananananannaanana
|
||||
XBindable.BindValueChanged(x => originalX = x.NewValue - xOffset);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,26 +1,24 @@
|
||||
// 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 JetBrains.Annotations;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Utils;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
||||
{
|
||||
public class DrawableBanana : DrawableFruit
|
||||
{
|
||||
public DrawableBanana(Banana h)
|
||||
: base(h)
|
||||
protected override FruitVisualRepresentation GetVisualRepresentation(int indexInBeatmap) => FruitVisualRepresentation.Banana;
|
||||
|
||||
public DrawableBanana()
|
||||
: this(null)
|
||||
{
|
||||
}
|
||||
|
||||
private Color4? colour;
|
||||
|
||||
protected override Color4 GetComboColour(IReadOnlyList<Color4> comboColours)
|
||||
public DrawableBanana([CanBeNull] Banana h)
|
||||
: base(h)
|
||||
{
|
||||
// override any external colour changes with banananana
|
||||
return colour ??= getBananaColour();
|
||||
}
|
||||
|
||||
protected override void UpdateInitialTransforms()
|
||||
@ -46,20 +44,5 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
||||
if (Samples != null)
|
||||
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.
|
||||
// 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.Containers;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.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;
|
||||
|
||||
public DrawableBananaShower(BananaShower s, Func<CatchHitObject, DrawableHitObject<CatchHitObject>> createDrawableRepresentation = null)
|
||||
public DrawableBananaShower()
|
||||
: this(null)
|
||||
{
|
||||
}
|
||||
|
||||
public DrawableBananaShower([CanBeNull] BananaShower s)
|
||||
: base(s)
|
||||
{
|
||||
this.createDrawableRepresentation = createDrawableRepresentation;
|
||||
RelativeSizeAxes = Axes.X;
|
||||
Origin = Anchor.BottomLeft;
|
||||
X = 0;
|
||||
|
||||
AddInternal(bananaContainer = new Container { RelativeSizeAxes = Axes.Both });
|
||||
}
|
||||
@ -34,18 +35,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
||||
protected override void ClearNestedHitObjects()
|
||||
{
|
||||
base.ClearNestedHitObjects();
|
||||
bananaContainer.Clear();
|
||||
}
|
||||
|
||||
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);
|
||||
bananaContainer.Clear(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,77 +2,42 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using osu.Framework.Allocation;
|
||||
using JetBrains.Annotations;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Rulesets.Catch.UI;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
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>
|
||||
{
|
||||
protected override double InitialLifetimeOffset => HitObject.TimePreempt;
|
||||
public readonly Bindable<float> XBindable = new Bindable<float>();
|
||||
|
||||
public virtual bool StaysOnPlate => HitObject.CanBePlated;
|
||||
protected override double InitialLifetimeOffset => HitObject.TimePreempt;
|
||||
|
||||
public float DisplayRadius => DrawSize.X / 2 * Scale.X * HitObject.Scale;
|
||||
|
||||
protected override float SamplePlaybackPosition => HitObject.X / CatchPlayfield.WIDTH;
|
||||
|
||||
protected DrawableCatchHitObject(CatchHitObject hitObject)
|
||||
protected DrawableCatchHitObject([CanBeNull] CatchHitObject hitObject)
|
||||
: base(hitObject)
|
||||
{
|
||||
X = hitObject.X;
|
||||
Anchor = Anchor.BottomLeft;
|
||||
}
|
||||
|
||||
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;
|
||||
|
@ -1,18 +1,25 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using JetBrains.Annotations;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Rulesets.Catch.Objects.Drawables.Pieces;
|
||||
using osu.Game.Skinning;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
||||
{
|
||||
public class DrawableDroplet : PalpableDrawableCatchHitObject<Droplet>
|
||||
public class DrawableDroplet : DrawablePalpableCatchHitObject
|
||||
{
|
||||
public override bool StaysOnPlate => false;
|
||||
|
||||
public DrawableDroplet(Droplet h)
|
||||
public DrawableDroplet()
|
||||
: this(null)
|
||||
{
|
||||
}
|
||||
|
||||
public DrawableDroplet([CanBeNull] CatchHitObject h)
|
||||
: base(h)
|
||||
{
|
||||
}
|
||||
@ -20,7 +27,17 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
||||
[BackgroundDependencyLoader]
|
||||
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()
|
||||
|
@ -2,15 +2,27 @@
|
||||
// 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.Utils;
|
||||
using osu.Game.Rulesets.Catch.Objects.Drawables.Pieces;
|
||||
using osu.Game.Skinning;
|
||||
|
||||
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)
|
||||
{
|
||||
}
|
||||
@ -18,10 +30,26 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
ScaleContainer.Child = new SkinnableDrawable(
|
||||
new CatchSkinComponent(getComponent(HitObject.VisualRepresentation)), _ => new FruitPiece());
|
||||
|
||||
ScaleContainer.Rotation = (float)(RNG.NextDouble() - 0.5f) * 40;
|
||||
|
||||
IndexInBeatmap.BindValueChanged(change =>
|
||||
{
|
||||
VisualRepresentation.Value = GetVisualRepresentation(change.NewValue);
|
||||
}, true);
|
||||
|
||||
VisualRepresentation.BindValueChanged(_ => updatePiece());
|
||||
HyperDash.BindValueChanged(_ => updatePiece(), true);
|
||||
}
|
||||
|
||||
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)
|
||||
@ -48,4 +76,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.
|
||||
// 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.Containers;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osuTK;
|
||||
|
||||
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;
|
||||
|
||||
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)
|
||||
{
|
||||
this.createDrawableRepresentation = createDrawableRepresentation;
|
||||
RelativeSizeAxes = Axes.X;
|
||||
Origin = Anchor.BottomLeft;
|
||||
X = 0;
|
||||
|
||||
AddInternal(dropletContainer = new Container { RelativeSizeAxes = Axes.Both, });
|
||||
}
|
||||
|
||||
protected override void AddNestedHitObject(DrawableHitObject hitObject)
|
||||
{
|
||||
hitObject.Origin = Anchor.BottomCentre;
|
||||
|
||||
base.AddNestedHitObject(hitObject);
|
||||
dropletContainer.Add(hitObject);
|
||||
}
|
||||
@ -39,19 +35,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
||||
protected override void ClearNestedHitObjects()
|
||||
{
|
||||
base.ClearNestedHitObjects();
|
||||
dropletContainer.Clear();
|
||||
}
|
||||
|
||||
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)}.");
|
||||
dropletContainer.Clear(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,83 @@
|
||||
// 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;
|
||||
|
||||
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.
|
||||
// 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
|
||||
{
|
||||
public class DrawableTinyDroplet : DrawableDroplet
|
||||
{
|
||||
public DrawableTinyDroplet(TinyDroplet h)
|
||||
: base(h)
|
||||
protected override float ScaleFactor => base.ScaleFactor / 2;
|
||||
|
||||
public DrawableTinyDroplet()
|
||||
: this(null)
|
||||
{
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
public DrawableTinyDroplet([CanBeNull] TinyDroplet h)
|
||||
: 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.
|
||||
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Rulesets.Catch.Objects.Drawables.Pieces;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
||||
namespace osu.Game.Rulesets.Catch.Objects.Drawables.Pieces
|
||||
{
|
||||
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.
|
||||
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Rulesets.Catch.Objects.Drawables.Pieces;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
||||
namespace osu.Game.Rulesets.Catch.Objects.Drawables.Pieces
|
||||
{
|
||||
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.
|
||||
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Rulesets.Catch.Objects.Drawables.Pieces;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
||||
namespace osu.Game.Rulesets.Catch.Objects.Drawables.Pieces
|
||||
{
|
||||
public class PearPiece : PulpFormation
|
||||
{
|
@ -2,10 +2,9 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Rulesets.Catch.Objects.Drawables.Pieces;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
||||
namespace osu.Game.Rulesets.Catch.Objects.Drawables.Pieces
|
||||
{
|
||||
public class PineapplePiece : PulpFormation
|
||||
{
|
@ -10,7 +10,7 @@ using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
||||
namespace osu.Game.Rulesets.Catch.Objects.Drawables.Pieces
|
||||
{
|
||||
public abstract class PulpFormation : CompositeDrawable
|
||||
{
|
@ -2,10 +2,9 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Rulesets.Catch.Objects.Drawables.Pieces;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
||||
namespace osu.Game.Rulesets.Catch.Objects.Drawables.Pieces
|
||||
{
|
||||
public class RaspberryPiece : PulpFormation
|
||||
{
|
@ -50,12 +50,7 @@ namespace osu.Game.Rulesets.Catch.Objects
|
||||
{
|
||||
base.CreateNestedHitObjects(cancellationToken);
|
||||
|
||||
var dropletSamples = Samples.Select(s => new HitSampleInfo
|
||||
{
|
||||
Bank = s.Bank,
|
||||
Name = @"slidertick",
|
||||
Volume = s.Volume
|
||||
}).ToList();
|
||||
var dropletSamples = Samples.Select(s => s.With(@"slidertick")).ToList();
|
||||
|
||||
int nodeIndex = 0;
|
||||
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;
|
||||
double lastTime = 0;
|
||||
|
||||
void moveToNext(CatchHitObject h)
|
||||
void moveToNext(PalpableCatchHitObject h)
|
||||
{
|
||||
float positionChange = Math.Abs(lastPosition - h.X);
|
||||
double timeAvailable = h.StartTime - lastTime;
|
||||
@ -101,23 +101,16 @@ namespace osu.Game.Rulesets.Catch.Replays
|
||||
|
||||
foreach (var obj in Beatmap.HitObjects)
|
||||
{
|
||||
switch (obj)
|
||||
if (obj is PalpableCatchHitObject palpableObject)
|
||||
{
|
||||
case Fruit _:
|
||||
moveToNext(obj);
|
||||
break;
|
||||
moveToNext(palpableObject);
|
||||
}
|
||||
|
||||
foreach (var nestedObj in obj.NestedHitObjects.Cast<CatchHitObject>())
|
||||
{
|
||||
switch (nestedObj)
|
||||
if (nestedObj is PalpableCatchHitObject palpableNestedObject)
|
||||
{
|
||||
case Banana _:
|
||||
case TinyDroplet _:
|
||||
case Droplet _:
|
||||
case Fruit _:
|
||||
moveToNext(nestedObj);
|
||||
break;
|
||||
moveToNext(palpableNestedObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -19,7 +19,8 @@ namespace osu.Game.Rulesets.Catch.Skinning
|
||||
{
|
||||
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;
|
||||
|
||||
public LegacyFruitPiece(string lookupName)
|
||||
@ -31,9 +32,10 @@ namespace osu.Game.Rulesets.Catch.Skinning
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(DrawableHitObject drawableObject, ISkinSource skin)
|
||||
{
|
||||
DrawableCatchHitObject drawableCatchObject = (DrawableCatchHitObject)drawableObject;
|
||||
var drawableCatchObject = (DrawablePalpableCatchHitObject)drawableObject;
|
||||
|
||||
accentColour.BindTo(drawableCatchObject.AccentColour);
|
||||
hyperDash.BindTo(drawableCatchObject.HyperDash);
|
||||
|
||||
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,
|
||||
Origin = Anchor.Centre,
|
||||
@ -67,7 +69,7 @@ namespace osu.Game.Rulesets.Catch.Skinning
|
||||
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.
|
||||
|
||||
using System;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Beatmaps;
|
||||
@ -42,7 +43,6 @@ namespace osu.Game.Rulesets.Catch.UI
|
||||
|
||||
CatcherArea = new CatcherArea(difficulty)
|
||||
{
|
||||
CreateDrawableRepresentation = createDrawableRepresentation,
|
||||
ExplodingFruitTarget = explodingFruitContainer,
|
||||
Anchor = Anchor.BottomLeft,
|
||||
Origin = Anchor.TopLeft,
|
||||
@ -57,19 +57,33 @@ namespace osu.Game.Rulesets.Catch.UI
|
||||
};
|
||||
}
|
||||
|
||||
public bool CheckIfWeCanCatch(CatchHitObject obj) => CatcherArea.AttemptCatch(obj);
|
||||
|
||||
public override void Add(DrawableHitObject h)
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
h.OnNewResult += onNewResult;
|
||||
h.OnRevertResult += onRevertResult;
|
||||
|
||||
base.Add(h);
|
||||
|
||||
var fruit = (DrawableCatchHitObject)h;
|
||||
fruit.CheckPosition = CheckIfWeCanCatch;
|
||||
RegisterPool<Droplet, DrawableDroplet>(50);
|
||||
RegisterPool<TinyDroplet, DrawableTinyDroplet>(50);
|
||||
RegisterPool<Fruit, DrawableFruit>(100);
|
||||
RegisterPool<Banana, DrawableBanana>(100);
|
||||
RegisterPool<JuiceStream, DrawableJuiceStream>(10);
|
||||
RegisterPool<BananaShower, DrawableBananaShower>(2);
|
||||
}
|
||||
|
||||
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)
|
||||
=> CatcherArea.OnNewResult((DrawableCatchHitObject)judgedObject, result);
|
||||
|
||||
|
@ -9,6 +9,7 @@ using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Animations;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Pooling;
|
||||
using osu.Framework.Input.Bindings;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Beatmaps;
|
||||
@ -107,6 +108,9 @@ namespace osu.Game.Rulesets.Catch.UI
|
||||
private float hyperDashTargetPosition;
|
||||
private Bindable<bool> hitLighting;
|
||||
|
||||
private DrawablePool<HitExplosion> hitExplosionPool;
|
||||
private Container<HitExplosion> hitExplosionContainer;
|
||||
|
||||
public Catcher([NotNull] Container trailsTarget, BeatmapDifficulty difficulty = null)
|
||||
{
|
||||
this.trailsTarget = trailsTarget;
|
||||
@ -127,6 +131,7 @@ namespace osu.Game.Rulesets.Catch.UI
|
||||
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
hitExplosionPool = new DrawablePool<HitExplosion>(10),
|
||||
caughtFruitContainer,
|
||||
catcherIdle = new CatcherSprite(CatcherAnimationState.Idle)
|
||||
{
|
||||
@ -142,7 +147,12 @@ namespace osu.Game.Rulesets.Catch.UI
|
||||
{
|
||||
Anchor = Anchor.TopCentre,
|
||||
Alpha = 0,
|
||||
}
|
||||
},
|
||||
hitExplosionContainer = new Container<HitExplosion>
|
||||
{
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.BottomCentre,
|
||||
},
|
||||
};
|
||||
|
||||
trails = new CatcherTrailDisplay(this);
|
||||
@ -209,22 +219,22 @@ namespace osu.Game.Rulesets.Catch.UI
|
||||
|
||||
if (hitLighting.Value)
|
||||
{
|
||||
AddInternal(new HitExplosion(fruit)
|
||||
{
|
||||
X = fruit.X,
|
||||
Scale = new Vector2(fruit.HitObject.Scale)
|
||||
});
|
||||
HitExplosion hitExplosion = hitExplosionPool.Get();
|
||||
hitExplosion.X = fruit.X;
|
||||
hitExplosion.Scale = new Vector2(fruit.HitObject.Scale);
|
||||
hitExplosion.ObjectColour = fruit.AccentColour.Value;
|
||||
hitExplosionContainer.Add(hitExplosion);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Let the catcher attempt to catch a fruit.
|
||||
/// </summary>
|
||||
/// <param name="fruit">The fruit to catch.</param>
|
||||
/// <param name="hitObject">The fruit to catch.</param>
|
||||
/// <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;
|
||||
|
||||
var halfCatchWidth = catchWidth * 0.5f;
|
||||
|
@ -10,7 +10,6 @@ using osu.Game.Rulesets.Catch.Objects;
|
||||
using osu.Game.Rulesets.Catch.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Catch.Replays;
|
||||
using osu.Game.Rulesets.Judgements;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Rulesets.UI;
|
||||
using osuTK;
|
||||
@ -21,8 +20,6 @@ namespace osu.Game.Rulesets.Catch.UI
|
||||
{
|
||||
public const float CATCHER_SIZE = 106.75f;
|
||||
|
||||
public Func<CatchHitObject, DrawableHitObject<CatchHitObject>> CreateDrawableRepresentation;
|
||||
|
||||
public readonly Catcher MovableCatcher;
|
||||
private readonly CatchComboDisplay comboDisplay;
|
||||
|
||||
@ -51,7 +48,7 @@ namespace osu.Game.Rulesets.Catch.UI
|
||||
};
|
||||
}
|
||||
|
||||
public void OnNewResult(DrawableCatchHitObject fruit, JudgementResult result)
|
||||
public void OnNewResult(DrawableCatchHitObject hitObject, JudgementResult result)
|
||||
{
|
||||
if (!result.Type.IsScorable())
|
||||
return;
|
||||
@ -69,15 +66,15 @@ namespace osu.Game.Rulesets.Catch.UI
|
||||
lastPlateableFruit.OnLoadComplete += _ => action();
|
||||
}
|
||||
|
||||
if (result.IsHit && fruit.HitObject.CanBePlated)
|
||||
if (result.IsHit && hitObject is DrawablePalpableCatchHitObject fruit)
|
||||
{
|
||||
// create a new (cloned) fruit to stay on the plate. the original is faded out immediately.
|
||||
var caughtFruit = (DrawableCatchHitObject)CreateDrawableRepresentation?.Invoke(fruit.HitObject);
|
||||
var caughtFruit = createCaughtFruit(fruit);
|
||||
|
||||
if (caughtFruit == null) return;
|
||||
|
||||
caughtFruit.RelativePositionAxes = Axes.None;
|
||||
caughtFruit.Position = new Vector2(MovableCatcher.ToLocalSpace(fruit.ScreenSpaceDrawQuad.Centre).X - MovableCatcher.DrawSize.X / 2, 0);
|
||||
caughtFruit.Position = new Vector2(MovableCatcher.ToLocalSpace(hitObject.ScreenSpaceDrawQuad.Centre).X - MovableCatcher.DrawSize.X / 2, 0);
|
||||
caughtFruit.IsOnPlate = true;
|
||||
|
||||
caughtFruit.Anchor = Anchor.TopCentre;
|
||||
@ -93,7 +90,7 @@ namespace osu.Game.Rulesets.Catch.UI
|
||||
runAfterLoaded(() => MovableCatcher.Explode(caughtFruit));
|
||||
}
|
||||
|
||||
if (fruit.HitObject.LastInCombo)
|
||||
if (hitObject.HitObject.LastInCombo)
|
||||
{
|
||||
if (result.Judgement is CatchJudgement catchJudgement && catchJudgement.ShouldExplodeFor(result))
|
||||
runAfterLoaded(() => MovableCatcher.Explode());
|
||||
@ -101,7 +98,7 @@ namespace osu.Game.Rulesets.Catch.UI
|
||||
MovableCatcher.Drop();
|
||||
}
|
||||
|
||||
comboDisplay.OnNewResult(fruit, result);
|
||||
comboDisplay.OnNewResult(hitObject, result);
|
||||
}
|
||||
|
||||
public void OnRevertResult(DrawableCatchHitObject fruit, JudgementResult result)
|
||||
@ -127,5 +124,26 @@ namespace osu.Game.Rulesets.Catch.UI
|
||||
|
||||
comboDisplay.X = MovableCatcher.X;
|
||||
}
|
||||
|
||||
private DrawableCatchHitObject createCaughtFruit(DrawablePalpableCatchHitObject hitObject)
|
||||
{
|
||||
switch (hitObject.HitObject)
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -8,7 +8,6 @@ using osu.Game.Configuration;
|
||||
using osu.Game.Input.Handlers;
|
||||
using osu.Game.Replays;
|
||||
using osu.Game.Rulesets.Catch.Objects;
|
||||
using osu.Game.Rulesets.Catch.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Catch.Replays;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
@ -40,30 +39,6 @@ namespace osu.Game.Rulesets.Catch.UI
|
||||
|
||||
protected override PassThroughInputManager CreateInputManager() => new CatchInputManager(Ruleset.RulesetInfo);
|
||||
|
||||
public override DrawableHitObject<CatchHitObject> CreateDrawableRepresentation(CatchHitObject h)
|
||||
{
|
||||
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;
|
||||
}
|
||||
public override DrawableHitObject<CatchHitObject> CreateDrawableRepresentation(CatchHitObject h) => null;
|
||||
}
|
||||
}
|
||||
|
@ -5,35 +5,43 @@ using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Effects;
|
||||
using osu.Framework.Graphics.Pooling;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Rulesets.Catch.Objects.Drawables;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
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);
|
||||
Anchor = Anchor.TopCentre;
|
||||
Origin = Anchor.BottomCentre;
|
||||
|
||||
Color4 objectColour = fruit.AccentColour.Value;
|
||||
|
||||
// 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;
|
||||
|
||||
var colour = Interpolation.ValueAt(0.4f, objectColour, Color4.White, 0, 1);
|
||||
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
largeFaint = new CircularContainer
|
||||
@ -42,81 +50,84 @@ namespace osu.Game.Rulesets.Catch.UI
|
||||
Origin = Anchor.Centre,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Masking = true,
|
||||
// we want our size to be very small so the glow dominates it.
|
||||
Size = new Vector2(0.8f),
|
||||
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,
|
||||
Colour = Interpolation.ValueAt(0.1f, objectColour, Color4.White, 0, 1).Opacity(0.3f),
|
||||
Roundness = 160,
|
||||
Radius = 200,
|
||||
},
|
||||
},
|
||||
new CircularContainer
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Masking = true,
|
||||
Blending = BlendingParameters.Additive,
|
||||
EdgeEffect = new EdgeEffectParameters
|
||||
};
|
||||
|
||||
smallFaint.EdgeEffect = new EdgeEffectParameters
|
||||
{
|
||||
Type = EdgeEffectType.Glow,
|
||||
Colour = Interpolation.ValueAt(0.6f, objectColour, Color4.White, 0, 1),
|
||||
Roundness = 20,
|
||||
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
|
||||
};
|
||||
|
||||
directionalGlow1.EdgeEffect = directionalGlow2.EdgeEffect = new EdgeEffectParameters
|
||||
{
|
||||
Type = EdgeEffectType.Glow,
|
||||
Colour = colour,
|
||||
Colour = Interpolation.ValueAt(0.4f, objectColour, Color4.White, 0, 1),
|
||||
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()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
const double duration = 400;
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
public override SnapResult SnapScreenSpacePositionToValidPosition(Vector2 screenSpacePosition)
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
|
||||
public override float GetBeatSnapDistanceAt(double referenceTime)
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
|
@ -2,7 +2,7 @@
|
||||
<Import Project="..\osu.TestProject.props" />
|
||||
<ItemGroup Label="Package References">
|
||||
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.1" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.0" />
|
||||
<PackageReference Include="NUnit" Version="3.12.0" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="3.17.0" />
|
||||
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
|
||||
|
@ -78,9 +78,9 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
|
||||
|
||||
private double originalStartTime;
|
||||
|
||||
public override void UpdatePosition(SnapResult result)
|
||||
public override void UpdateTimeAndPosition(SnapResult result)
|
||||
{
|
||||
base.UpdatePosition(result);
|
||||
base.UpdateTimeAndPosition(result);
|
||||
|
||||
if (PlacementActive)
|
||||
{
|
||||
|
@ -48,9 +48,9 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
|
||||
return true;
|
||||
}
|
||||
|
||||
public override void UpdatePosition(SnapResult result)
|
||||
public override void UpdateTimeAndPosition(SnapResult result)
|
||||
{
|
||||
base.UpdatePosition(result);
|
||||
base.UpdateTimeAndPosition(result);
|
||||
|
||||
if (!PlacementActive)
|
||||
Column = result.Playfield as Column;
|
||||
|
@ -22,9 +22,9 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
|
||||
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)
|
||||
{
|
||||
|
@ -5,7 +5,7 @@ using osu.Game.Rulesets.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!";
|
||||
}
|
||||
|
@ -30,6 +30,8 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
||||
|
||||
public void UpdateResult() => base.UpdateResult(true);
|
||||
|
||||
protected override double MaximumJudgementOffset => base.MaximumJudgementOffset * release_window_lenience;
|
||||
|
||||
protected override void CheckForResult(bool userTriggered, double timeOffset)
|
||||
{
|
||||
Debug.Assert(HitObject.HitWindows != null);
|
||||
|
@ -97,7 +97,7 @@ namespace osu.Game.Rulesets.Mania.UI
|
||||
DrawableManiaHitObject maniaObject = (DrawableManiaHitObject)hitObject;
|
||||
maniaObject.CheckHittable = hitPolicy.IsHittable;
|
||||
|
||||
HitObjectContainer.Add(hitObject);
|
||||
base.Add(hitObject);
|
||||
}
|
||||
|
||||
public override bool Remove(DrawableHitObject h)
|
||||
|
@ -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();
|
||||
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)]
|
||||
@ -51,7 +52,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
var first = (OsuHitObject)objects.First();
|
||||
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("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("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));
|
||||
|
||||
@ -86,5 +87,64 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
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
|
||||
{
|
||||
public SnapResult SnapScreenSpacePositionToValidPosition(Vector2 screenSpacePosition) =>
|
||||
new SnapResult(screenSpacePosition, null);
|
||||
|
||||
public SnapResult SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition) => new SnapResult(screenSpacePosition, 0);
|
||||
|
||||
public float GetBeatSnapDistanceAt(double referenceTime) => (float)beat_length;
|
||||
|
@ -5,7 +5,7 @@ using osu.Game.Tests.Visual;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Tests.Mods
|
||||
{
|
||||
public class OsuModTestScene : ModTestScene
|
||||
public abstract class OsuModTestScene : ModTestScene
|
||||
{
|
||||
protected override Ruleset CreatePlayerRuleset() => new OsuRuleset();
|
||||
}
|
||||
|
@ -108,8 +108,8 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
|
||||
AddStep("change samples", () => slider.HitObject.Samples = new[]
|
||||
{
|
||||
new HitSampleInfo { Name = HitSampleInfo.HIT_CLAP },
|
||||
new HitSampleInfo { Name = HitSampleInfo.HIT_WHISTLE },
|
||||
new HitSampleInfo(HitSampleInfo.HIT_CLAP),
|
||||
new HitSampleInfo(HitSampleInfo.HIT_WHISTLE),
|
||||
});
|
||||
|
||||
AddAssert("head samples updated", () => assertSamples(slider.HitObject.HeadCircle));
|
||||
@ -136,15 +136,15 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
slider = (DrawableSlider)createSlider(repeats: 1);
|
||||
|
||||
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);
|
||||
});
|
||||
|
||||
AddStep("change samples", () => slider.HitObject.Samples = new[]
|
||||
{
|
||||
new HitSampleInfo { Name = HitSampleInfo.HIT_CLAP },
|
||||
new HitSampleInfo { Name = HitSampleInfo.HIT_WHISTLE },
|
||||
new HitSampleInfo(HitSampleInfo.HIT_CLAP),
|
||||
new HitSampleInfo(HitSampleInfo.HIT_WHISTLE),
|
||||
});
|
||||
|
||||
AddAssert("head samples not updated", () => assertSamples(slider.HitObject.HeadCircle));
|
||||
|
@ -2,7 +2,7 @@
|
||||
<Import Project="..\osu.TestProject.props" />
|
||||
<ItemGroup Label="Package References">
|
||||
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.1" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.0" />
|
||||
<PackageReference Include="NUnit" Version="3.12.0" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="3.17.0" />
|
||||
<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);
|
||||
}
|
||||
|
||||
public override void UpdatePosition(SnapResult result)
|
||||
public override void UpdateTimeAndPosition(SnapResult result)
|
||||
{
|
||||
base.UpdatePosition(result);
|
||||
base.UpdateTimeAndPosition(result);
|
||||
HitObject.Position = ToLocalSpace(result.ScreenSpacePosition);
|
||||
}
|
||||
}
|
||||
|
@ -44,6 +44,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
||||
private OsuColour colours { get; set; }
|
||||
|
||||
private IBindable<Vector2> sliderPosition;
|
||||
private IBindable<float> sliderScale;
|
||||
private IBindable<Vector2> controlPointPosition;
|
||||
|
||||
public PathControlPointPiece(Slider slider, PathControlPoint controlPoint)
|
||||
@ -69,13 +70,13 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Size = new Vector2(10),
|
||||
Size = new Vector2(20),
|
||||
},
|
||||
markerRing = new CircularContainer
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Size = new Vector2(14),
|
||||
Size = new Vector2(28),
|
||||
Masking = true,
|
||||
BorderThickness = 2,
|
||||
BorderColour = Color4.White,
|
||||
@ -102,6 +103,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
||||
controlPointPosition = ControlPoint.Position.GetBoundCopy();
|
||||
controlPointPosition.BindValueChanged(_ => updateMarkerDisplay());
|
||||
|
||||
sliderScale = slider.ScaleBindable.GetBoundCopy();
|
||||
sliderScale.BindValueChanged(_ => updateMarkerDisplay());
|
||||
|
||||
IsSelected.BindValueChanged(_ => updateMarkerDisplay());
|
||||
|
||||
updateMarkerDisplay();
|
||||
@ -143,6 +147,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
||||
|
||||
protected override bool OnClick(ClickEvent e) => RequestSelection != null;
|
||||
|
||||
private Vector2 dragStartPosition;
|
||||
|
||||
protected override bool OnDragStart(DragStartEvent e)
|
||||
{
|
||||
if (RequestSelection == null)
|
||||
@ -150,6 +156,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
||||
|
||||
if (e.Button == MouseButton.Left)
|
||||
{
|
||||
dragStartPosition = ControlPoint.Position.Value;
|
||||
changeHandler?.BeginChange();
|
||||
return true;
|
||||
}
|
||||
@ -174,7 +181,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
||||
slider.Path.ControlPoints[i].Position.Value -= movementDelta;
|
||||
}
|
||||
else
|
||||
ControlPoint.Position.Value += e.Delta;
|
||||
ControlPoint.Position.Value = dragStartPosition + (e.MousePosition - e.MouseDownPosition);
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
marker.Colour = colour;
|
||||
marker.Scale = new Vector2(slider.Scale);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -67,9 +67,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
||||
inputManager = GetContainingInputManager();
|
||||
}
|
||||
|
||||
public override void UpdatePosition(SnapResult result)
|
||||
public override void UpdateTimeAndPosition(SnapResult result)
|
||||
{
|
||||
base.UpdatePosition(result);
|
||||
base.UpdateTimeAndPosition(result);
|
||||
|
||||
switch (state)
|
||||
{
|
||||
|
@ -44,6 +44,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
||||
[Resolved(CanBeNull = true)]
|
||||
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)
|
||||
: base(slider)
|
||||
{
|
||||
@ -61,13 +64,13 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
||||
};
|
||||
}
|
||||
|
||||
private IBindable<int> pathVersion;
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
pathVersion = HitObject.Path.Version.GetBoundCopy();
|
||||
controlPoints.BindTo(HitObject.Path.ControlPoints);
|
||||
|
||||
pathVersion.BindTo(HitObject.Path.Version);
|
||||
pathVersion.BindValueChanged(_ => updatePath());
|
||||
|
||||
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)
|
||||
{
|
||||
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.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using osu.Framework.Graphics.Pooling;
|
||||
using System.Linq;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Beatmaps;
|
||||
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.UI;
|
||||
using osuTK;
|
||||
@ -26,8 +29,51 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
{
|
||||
protected override GameplayCursorContainer CreateCursor() => null;
|
||||
|
||||
protected override DrawablePool<TDrawable> CreatePool<TDrawable>(int initialSize, int? maximumSize = null)
|
||||
=> new DrawableOsuEditPool<TDrawable>(CheckHittable, OnHitObjectLoaded, initialSize, maximumSize);
|
||||
protected override void OnNewDrawableHitObject(DrawableHitObject d)
|
||||
{
|
||||
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))
|
||||
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.
|
||||
if (distanceSnapGrid == null)
|
||||
return base.SnapScreenSpacePositionToValidTime(screenSpacePosition);
|
||||
|
@ -5,7 +5,7 @@ using osu.Game.Rulesets.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!";
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Bindables;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
@ -47,6 +48,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
|
||||
});
|
||||
}
|
||||
|
||||
public double AnimationStartTime { get; set; }
|
||||
public Bindable<double> AnimationStartTime { get; } = new BindableDouble();
|
||||
}
|
||||
}
|
||||
|
@ -80,7 +80,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
|
||||
fp.Alpha = 0;
|
||||
fp.Scale = new Vector2(1.5f * end.Scale);
|
||||
|
||||
fp.AnimationStartTime = fadeInTime;
|
||||
fp.AnimationStartTime.Value = fadeInTime;
|
||||
|
||||
using (fp.BeginAbsoluteSequence(fadeInTime))
|
||||
{
|
||||
|
@ -19,7 +19,7 @@ using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
{
|
||||
public class DrawableHitCircle : DrawableOsuHitObject, IDrawableHitObjectWithProxiedApproach
|
||||
public class DrawableHitCircle : DrawableOsuHitObject
|
||||
{
|
||||
public OsuAction? HitAction => HitArea.HitAction;
|
||||
protected virtual OsuSkinComponents CirclePieceComponent => OsuSkinComponents.HitCircle;
|
||||
|
@ -9,9 +9,7 @@ using osu.Framework.Graphics;
|
||||
using osu.Game.Rulesets.Judgements;
|
||||
using osu.Game.Rulesets.Osu.Judgements;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Osu.UI;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osuTK;
|
||||
|
||||
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);
|
||||
PositionBindable.BindTo(HitObject.PositionBindable);
|
||||
StackHeightBindable.BindTo(HitObject.StackHeightBindable);
|
||||
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);
|
||||
PositionBindable.UnbindFrom(HitObject.PositionBindable);
|
||||
|
@ -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;
|
||||
});
|
||||
}
|
||||
}
|
@ -19,7 +19,7 @@ using osu.Game.Skinning;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
{
|
||||
public class DrawableSlider : DrawableOsuHitObject, IDrawableHitObjectWithProxiedApproach
|
||||
public class DrawableSlider : DrawableOsuHitObject
|
||||
{
|
||||
public new Slider HitObject => (Slider)base.HitObject;
|
||||
|
||||
@ -86,18 +86,18 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
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().
|
||||
pathVersion.Value = int.MaxValue;
|
||||
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);
|
||||
}
|
||||
@ -115,8 +115,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
|
||||
if (firstSample != null)
|
||||
{
|
||||
var clone = HitObject.SampleControlPoint.ApplyTo(firstSample);
|
||||
clone.Name = "sliderslide";
|
||||
var clone = HitObject.SampleControlPoint.ApplyTo(firstSample).With("sliderslide");
|
||||
|
||||
samplesContainer.Add(slidingSample = new PausableSkinnableSound(clone)
|
||||
{
|
||||
@ -255,7 +254,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
if (userTriggered || Time.Current < HitObject.EndTime)
|
||||
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()
|
||||
@ -294,14 +293,14 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
{
|
||||
case ArmedState.Hit:
|
||||
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;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
private class DefaultSliderBody : PlaySliderBody
|
||||
|
@ -4,7 +4,6 @@
|
||||
using System;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
|
||||
@ -36,9 +35,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
pathVersion.BindValueChanged(_ => updatePosition());
|
||||
}
|
||||
|
||||
protected override void OnFree(HitObject hitObject)
|
||||
protected override void OnFree()
|
||||
{
|
||||
base.OnFree(hitObject);
|
||||
base.OnFree();
|
||||
|
||||
pathVersion.UnbindFrom(drawableSlider.PathVersion);
|
||||
}
|
||||
|
@ -110,8 +110,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
|
||||
if (firstSample != null)
|
||||
{
|
||||
var clone = HitObject.SampleControlPoint.ApplyTo(firstSample);
|
||||
clone.Name = "spinnerspin";
|
||||
var clone = HitObject.SampleControlPoint.ApplyTo(firstSample).With("spinnerspin");
|
||||
|
||||
samplesContainer.Add(spinningSample = new PausableSkinnableSound(clone)
|
||||
{
|
||||
|
@ -1,6 +1,8 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
{
|
||||
public class DrawableSpinnerTick : DrawableOsuHitObject
|
||||
@ -17,6 +19,16 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
{
|
||||
}
|
||||
|
||||
private DrawableSpinner drawableSpinner;
|
||||
|
||||
protected override void OnParentReceived(DrawableHitObject parent)
|
||||
{
|
||||
base.OnParentReceived(parent);
|
||||
drawableSpinner = (DrawableSpinner)parent;
|
||||
}
|
||||
|
||||
protected override double MaximumJudgementOffset => drawableSpinner.HitObject.Duration;
|
||||
|
||||
/// <summary>
|
||||
/// Apply a judgement result.
|
||||
/// </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<int> indexInCurrentCombo = new Bindable<int>();
|
||||
|
||||
@ -50,7 +49,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
|
||||
{
|
||||
var drawableOsuObject = (DrawableOsuHitObject)drawableObject;
|
||||
|
||||
state.BindTo(drawableObject.State);
|
||||
accentColour.BindTo(drawableObject.AccentColour);
|
||||
indexInCurrentCombo.BindTo(drawableOsuObject.IndexInCurrentComboBindable);
|
||||
}
|
||||
@ -59,7 +57,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
state.BindValueChanged(updateState, true);
|
||||
accentColour.BindValueChanged(colour =>
|
||||
{
|
||||
explode.Colour = colour.NewValue;
|
||||
@ -68,15 +65,18 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
|
||||
}, 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))
|
||||
{
|
||||
glow.FadeOut(400);
|
||||
|
||||
switch (state.NewValue)
|
||||
switch (state)
|
||||
{
|
||||
case ArmedState.Hit:
|
||||
const double flash_in = 40;
|
||||
|
@ -248,7 +248,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -221,14 +221,7 @@ namespace osu.Game.Rulesets.Osu.Objects
|
||||
var sampleList = new List<HitSampleInfo>();
|
||||
|
||||
if (firstSample != null)
|
||||
{
|
||||
sampleList.Add(new HitSampleInfo
|
||||
{
|
||||
Bank = firstSample.Bank,
|
||||
Volume = firstSample.Volume,
|
||||
Name = @"slidertick",
|
||||
});
|
||||
}
|
||||
sampleList.Add(firstSample.With("slidertick"));
|
||||
|
||||
foreach (var tick in NestedHitObjects.OfType<SliderTick>())
|
||||
tick.Samples = sampleList;
|
||||
|
@ -11,7 +11,7 @@ namespace osu.Game.Rulesets.Osu.Objects
|
||||
{
|
||||
public SpinnerBonusTick()
|
||||
{
|
||||
Samples.Add(new HitSampleInfo { Name = "spinnerbonus" });
|
||||
Samples.Add(new HitSampleInfo("spinnerbonus"));
|
||||
}
|
||||
|
||||
public override Judgement CreateJudgement() => new OsuSpinnerBonusTickJudgement();
|
||||
|
@ -38,7 +38,6 @@ namespace osu.Game.Rulesets.Osu.Skinning
|
||||
|
||||
private SkinnableSpriteText hitCircleText;
|
||||
|
||||
private readonly IBindable<ArmedState> state = new Bindable<ArmedState>();
|
||||
private readonly Bindable<Color4> accentColour = new Bindable<Color4>();
|
||||
private readonly IBindable<int> indexInCurrentCombo = new Bindable<int>();
|
||||
|
||||
@ -113,7 +112,6 @@ namespace osu.Game.Rulesets.Osu.Skinning
|
||||
if (overlayAboveNumber)
|
||||
AddInternal(hitCircleOverlay.CreateProxy());
|
||||
|
||||
state.BindTo(drawableObject.State);
|
||||
accentColour.BindTo(drawableObject.AccentColour);
|
||||
indexInCurrentCombo.BindTo(drawableOsuObject.IndexInCurrentComboBindable);
|
||||
|
||||
@ -137,19 +135,21 @@ namespace osu.Game.Rulesets.Osu.Skinning
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
state.BindValueChanged(updateState, true);
|
||||
accentColour.BindValueChanged(colour => hitCircleSprite.Colour = LegacyColourCompatibility.DisallowZeroAlpha(colour.NewValue), true);
|
||||
if (hasNumber)
|
||||
indexInCurrentCombo.BindValueChanged(index => hitCircleText.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)
|
||||
{
|
||||
const double legacy_fade_duration = 240;
|
||||
|
||||
using (BeginAbsoluteSequence(drawableObject.HitStateUpdateTime, true))
|
||||
{
|
||||
switch (state.NewValue)
|
||||
switch (state)
|
||||
{
|
||||
case ArmedState.Hit:
|
||||
circleSprites.FadeOut(legacy_fade_duration, Easing.Out);
|
||||
|
@ -3,6 +3,7 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
@ -25,8 +26,6 @@ namespace osu.Game.Rulesets.Osu.UI
|
||||
{
|
||||
public class OsuPlayfield : Playfield
|
||||
{
|
||||
public readonly Func<DrawableHitObject, double, bool> CheckHittable;
|
||||
|
||||
private readonly PlayfieldBorder playfieldBorder;
|
||||
private readonly ProxyContainer approachCircles;
|
||||
private readonly ProxyContainer spinnerProxies;
|
||||
@ -56,7 +55,6 @@ namespace osu.Game.Rulesets.Osu.UI
|
||||
};
|
||||
|
||||
hitPolicy = new OrderedHitPolicy(HitObjectContainer);
|
||||
CheckHittable = hitPolicy.IsHittable;
|
||||
|
||||
var hitWindows = new OsuHitWindows();
|
||||
|
||||
@ -68,6 +66,29 @@ namespace osu.Game.Rulesets.Osu.UI
|
||||
NewResult += onNewResult;
|
||||
}
|
||||
|
||||
protected override void OnNewDrawableHitObject(DrawableHitObject drawable)
|
||||
{
|
||||
((DrawableOsuHitObject)drawable).CheckHittable = hitPolicy.IsHittable;
|
||||
|
||||
Debug.Assert(!drawable.IsLoaded, $"Already loaded {nameof(DrawableHitObject)} is added to {nameof(OsuPlayfield)}");
|
||||
drawable.OnLoadComplete += onDrawableHitObjectLoaded;
|
||||
}
|
||||
|
||||
private void onDrawableHitObjectLoaded(Drawable drawable)
|
||||
{
|
||||
// note: `Slider`'s `ProxiedLayer` is added when its nested `DrawableHitCircle` is loaded.
|
||||
switch (drawable)
|
||||
{
|
||||
case DrawableSpinner _:
|
||||
spinnerProxies.Add(drawable.CreateProxy());
|
||||
break;
|
||||
|
||||
case DrawableHitCircle hitCircle:
|
||||
approachCircles.Add(hitCircle.ProxiedLayer.CreateProxy());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void onJudgmentLoaded(DrawableOsuJudgement judgement)
|
||||
{
|
||||
judgementAboveHitObjectLayer.Add(judgement.GetProxyAboveHitObjectsContent());
|
||||
@ -78,28 +99,19 @@ namespace osu.Game.Rulesets.Osu.UI
|
||||
{
|
||||
config?.BindWith(OsuRulesetSetting.PlayfieldBorderStyle, playfieldBorder.PlayfieldBorderStyle);
|
||||
|
||||
registerPool<HitCircle, DrawableHitCircle>(10, 100);
|
||||
RegisterPool<HitCircle, DrawableHitCircle>(10, 100);
|
||||
|
||||
registerPool<Slider, DrawableSlider>(10, 100);
|
||||
registerPool<SliderHeadCircle, DrawableSliderHead>(10, 100);
|
||||
registerPool<SliderTailCircle, DrawableSliderTail>(10, 100);
|
||||
registerPool<SliderTick, DrawableSliderTick>(10, 100);
|
||||
registerPool<SliderRepeat, DrawableSliderRepeat>(5, 50);
|
||||
RegisterPool<Slider, DrawableSlider>(10, 100);
|
||||
RegisterPool<SliderHeadCircle, DrawableSliderHead>(10, 100);
|
||||
RegisterPool<SliderTailCircle, DrawableSliderTail>(10, 100);
|
||||
RegisterPool<SliderTick, DrawableSliderTick>(10, 100);
|
||||
RegisterPool<SliderRepeat, DrawableSliderRepeat>(5, 50);
|
||||
|
||||
registerPool<Spinner, DrawableSpinner>(2, 20);
|
||||
registerPool<SpinnerTick, DrawableSpinnerTick>(10, 100);
|
||||
registerPool<SpinnerBonusTick, DrawableSpinnerBonusTick>(10, 100);
|
||||
RegisterPool<Spinner, DrawableSpinner>(2, 20);
|
||||
RegisterPool<SpinnerTick, DrawableSpinnerTick>(10, 100);
|
||||
RegisterPool<SpinnerBonusTick, DrawableSpinnerBonusTick>(10, 100);
|
||||
}
|
||||
|
||||
private void registerPool<TObject, TDrawable>(int initialSize, int? maximumSize = null)
|
||||
where TObject : HitObject
|
||||
where TDrawable : DrawableHitObject, new()
|
||||
=> RegisterPool<TObject, TDrawable>(CreatePool<TDrawable>(initialSize, maximumSize));
|
||||
|
||||
protected virtual DrawablePool<TDrawable> CreatePool<TDrawable>(int initialSize, int? maximumSize = null)
|
||||
where TDrawable : DrawableHitObject, new()
|
||||
=> new DrawableOsuPool<TDrawable>(CheckHittable, OnHitObjectLoaded, initialSize, maximumSize);
|
||||
|
||||
protected override HitObjectLifetimeEntry CreateLifetimeEntry(HitObject hitObject) => new OsuHitObjectLifetimeEntry(hitObject);
|
||||
|
||||
protected override void OnHitObjectAdded(HitObject hitObject)
|
||||
@ -114,27 +126,6 @@ namespace osu.Game.Rulesets.Osu.UI
|
||||
followPoints.RemoveFollowPoints((OsuHitObject)hitObject);
|
||||
}
|
||||
|
||||
public void OnHitObjectLoaded(Drawable drawable)
|
||||
{
|
||||
switch (drawable)
|
||||
{
|
||||
case DrawableSliderHead _:
|
||||
case DrawableSliderTail _:
|
||||
case DrawableSliderTick _:
|
||||
case DrawableSliderRepeat _:
|
||||
case DrawableSpinnerTick _:
|
||||
break;
|
||||
|
||||
case DrawableSpinner _:
|
||||
spinnerProxies.Add(drawable.CreateProxy());
|
||||
break;
|
||||
|
||||
case IDrawableHitObjectWithProxiedApproach approach:
|
||||
approachCircles.Add(approach.ProxiedLayer.CreateProxy());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void onNewResult(DrawableHitObject judgedObject, JudgementResult result)
|
||||
{
|
||||
// Hitobjects that block future hits should miss previous hitobjects if they're hit out-of-order.
|
||||
@ -185,6 +176,8 @@ namespace osu.Game.Rulesets.Osu.UI
|
||||
public OsuHitObjectLifetimeEntry(HitObject hitObject)
|
||||
: base(hitObject)
|
||||
{
|
||||
// Prevent past objects in idles states from remaining alive as their end times are skipped in non-frame-stable contexts.
|
||||
LifetimeEnd = HitObject.GetEndTime() + HitObject.HitWindows.WindowFor(HitResult.Miss);
|
||||
}
|
||||
|
||||
protected override double InitialLifetimeOffset => ((OsuHitObject)HitObject).TimePreempt;
|
||||
|
@ -2,7 +2,7 @@
|
||||
<Import Project="..\osu.TestProject.props" />
|
||||
<ItemGroup Label="Package References">
|
||||
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.1" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.0" />
|
||||
<PackageReference Include="NUnit" Version="3.12.0" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="3.17.0" />
|
||||
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
|
||||
|
@ -43,10 +43,10 @@ namespace osu.Game.Rulesets.Taiko.Edit.Blueprints
|
||||
return false;
|
||||
}
|
||||
|
||||
public override void UpdatePosition(SnapResult result)
|
||||
public override void UpdateTimeAndPosition(SnapResult result)
|
||||
{
|
||||
piece.Position = ToLocalSpace(result.ScreenSpacePosition);
|
||||
base.UpdatePosition(result);
|
||||
base.UpdateTimeAndPosition(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -68,9 +68,9 @@ namespace osu.Game.Rulesets.Taiko.Edit.Blueprints
|
||||
EndPlacement(true);
|
||||
}
|
||||
|
||||
public override void UpdatePosition(SnapResult result)
|
||||
public override void UpdateTimeAndPosition(SnapResult result)
|
||||
{
|
||||
base.UpdatePosition(result);
|
||||
base.UpdateTimeAndPosition(result);
|
||||
|
||||
if (PlacementActive)
|
||||
{
|
||||
|
@ -7,6 +7,6 @@ namespace osu.Game.Rulesets.Taiko.Mods
|
||||
{
|
||||
public class TaikoModEasy : ModEasy
|
||||
{
|
||||
public override string Description => @"Beats move slower, less accuracy required, and three lives!";
|
||||
public override string Description => @"Beats move slower, and less accuracy required!";
|
||||
}
|
||||
}
|
||||
|
@ -28,6 +28,8 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
||||
Filled = HitObject.FirstTick
|
||||
});
|
||||
|
||||
protected override double MaximumJudgementOffset => HitObject.HitWindow;
|
||||
|
||||
protected override void CheckForResult(bool userTriggered, double timeOffset)
|
||||
{
|
||||
if (!userTriggered)
|
||||
|
@ -79,7 +79,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
||||
if (isRimType != rimSamples.Any())
|
||||
{
|
||||
if (isRimType)
|
||||
HitObject.Samples.Add(new HitSampleInfo { Name = HitSampleInfo.HIT_CLAP });
|
||||
HitObject.Samples.Add(new HitSampleInfo(HitSampleInfo.HIT_CLAP));
|
||||
else
|
||||
{
|
||||
foreach (var sample in rimSamples)
|
||||
@ -125,9 +125,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
||||
if (s.Name != HitSampleInfo.HIT_FINISH)
|
||||
continue;
|
||||
|
||||
var sClone = s.Clone();
|
||||
sClone.Name = HitSampleInfo.HIT_WHISTLE;
|
||||
corrected[i] = sClone;
|
||||
corrected[i] = s.With(HitSampleInfo.HIT_WHISTLE);
|
||||
}
|
||||
|
||||
return corrected;
|
||||
|
@ -169,7 +169,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
||||
if (isStrong.Value != strongSamples.Any())
|
||||
{
|
||||
if (isStrong.Value)
|
||||
HitObject.Samples.Add(new HitSampleInfo { Name = HitSampleInfo.HIT_FINISH });
|
||||
HitObject.Samples.Add(new HitSampleInfo(HitSampleInfo.HIT_FINISH));
|
||||
else
|
||||
{
|
||||
foreach (var sample in strongSamples)
|
||||
|
@ -1,6 +1,7 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
@ -37,7 +38,7 @@ namespace osu.Game.Rulesets.Taiko.UI
|
||||
private SkinnableDrawable mascot;
|
||||
|
||||
private ProxyContainer topLevelHitContainer;
|
||||
private ProxyContainer barlineContainer;
|
||||
private ScrollingHitObjectContainer barlineContainer;
|
||||
private Container rightArea;
|
||||
private Container leftArea;
|
||||
|
||||
@ -83,10 +84,7 @@ namespace osu.Game.Rulesets.Taiko.UI
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
barlineContainer = new ProxyContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
},
|
||||
barlineContainer = new ScrollingHitObjectContainer(),
|
||||
new Container
|
||||
{
|
||||
Name = "Hit objects",
|
||||
@ -159,18 +157,37 @@ namespace osu.Game.Rulesets.Taiko.UI
|
||||
|
||||
public override void Add(DrawableHitObject h)
|
||||
{
|
||||
h.OnNewResult += OnNewResult;
|
||||
base.Add(h);
|
||||
|
||||
switch (h)
|
||||
{
|
||||
case DrawableBarLine barline:
|
||||
barlineContainer.Add(barline.CreateProxy());
|
||||
barlineContainer.Add(barline);
|
||||
break;
|
||||
|
||||
case DrawableTaikoHitObject taikoObject:
|
||||
h.OnNewResult += OnNewResult;
|
||||
topLevelHitContainer.Add(taikoObject.CreateProxiedContent());
|
||||
base.Add(h);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new ArgumentException($"Unsupported {nameof(DrawableHitObject)} type");
|
||||
}
|
||||
}
|
||||
|
||||
public override bool Remove(DrawableHitObject h)
|
||||
{
|
||||
switch (h)
|
||||
{
|
||||
case DrawableBarLine barline:
|
||||
return barlineContainer.Remove(barline);
|
||||
|
||||
case DrawableTaikoHitObject _:
|
||||
h.OnNewResult -= OnNewResult;
|
||||
// todo: consider tidying of proxied content if required.
|
||||
return base.Remove(h);
|
||||
|
||||
default:
|
||||
throw new ArgumentException($"Unsupported {nameof(DrawableHitObject)} type");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2,7 +2,6 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Screens.Edit;
|
||||
@ -44,6 +43,36 @@ namespace osu.Game.Tests.Editing
|
||||
Assert.That(stateChangedFired, Is.EqualTo(2));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestApplyThenUndoThenApplySameChange()
|
||||
{
|
||||
var (handler, beatmap) = createChangeHandler();
|
||||
|
||||
Assert.That(handler.CanUndo.Value, Is.False);
|
||||
Assert.That(handler.CanRedo.Value, Is.False);
|
||||
|
||||
string originalHash = handler.CurrentStateHash;
|
||||
|
||||
addArbitraryChange(beatmap);
|
||||
handler.SaveState();
|
||||
|
||||
Assert.That(handler.CanUndo.Value, Is.True);
|
||||
Assert.That(handler.CanRedo.Value, Is.False);
|
||||
Assert.That(stateChangedFired, Is.EqualTo(1));
|
||||
|
||||
string hash = handler.CurrentStateHash;
|
||||
|
||||
// undo a change without saving
|
||||
handler.RestoreState(-1);
|
||||
|
||||
Assert.That(originalHash, Is.EqualTo(handler.CurrentStateHash));
|
||||
Assert.That(stateChangedFired, Is.EqualTo(2));
|
||||
|
||||
addArbitraryChange(beatmap);
|
||||
handler.SaveState();
|
||||
Assert.That(hash, Is.EqualTo(handler.CurrentStateHash));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestSaveSameStateDoesNotSave()
|
||||
{
|
||||
@ -139,7 +168,7 @@ namespace osu.Game.Tests.Editing
|
||||
|
||||
private void addArbitraryChange(EditorBeatmap beatmap)
|
||||
{
|
||||
beatmap.Add(new HitCircle { StartTime = RNG.Next(0, 100000) });
|
||||
beatmap.Add(new HitCircle { StartTime = 2760 });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -139,7 +139,7 @@ namespace osu.Game.Tests.Editing
|
||||
HitObjects =
|
||||
{
|
||||
(OsuHitObject)current.HitObjects[0],
|
||||
new HitCircle { StartTime = 2000, Samples = { new HitSampleInfo { Name = HitSampleInfo.HIT_FINISH } } },
|
||||
new HitCircle { StartTime = 2000, Samples = { new HitSampleInfo(HitSampleInfo.HIT_FINISH) } },
|
||||
(OsuHitObject)current.HitObjects[2],
|
||||
}
|
||||
};
|
||||
@ -268,12 +268,12 @@ namespace osu.Game.Tests.Editing
|
||||
HitObjects =
|
||||
{
|
||||
(OsuHitObject)current.HitObjects[0],
|
||||
new HitCircle { StartTime = 1000, Samples = { new HitSampleInfo { Name = HitSampleInfo.HIT_FINISH } } },
|
||||
new HitCircle { StartTime = 1000, Samples = { new HitSampleInfo(HitSampleInfo.HIT_FINISH) } },
|
||||
(OsuHitObject)current.HitObjects[2],
|
||||
(OsuHitObject)current.HitObjects[3],
|
||||
new HitCircle { StartTime = 2250, Samples = { new HitSampleInfo { Name = HitSampleInfo.HIT_WHISTLE } } },
|
||||
new HitCircle { StartTime = 2250, Samples = { new HitSampleInfo(HitSampleInfo.HIT_WHISTLE) } },
|
||||
(OsuHitObject)current.HitObjects[5],
|
||||
new HitCircle { StartTime = 3000, Samples = { new HitSampleInfo { Name = HitSampleInfo.HIT_CLAP } } },
|
||||
new HitCircle { StartTime = 3000, Samples = { new HitSampleInfo(HitSampleInfo.HIT_CLAP) } },
|
||||
(OsuHitObject)current.HitObjects[7],
|
||||
}
|
||||
};
|
||||
|
79
osu.Game.Tests/NonVisual/Skinning/LegacySkinAnimationTest.cs
Normal file
79
osu.Game.Tests/NonVisual/Skinning/LegacySkinAnimationTest.cs
Normal file
@ -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 System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio.Sample;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Animations;
|
||||
using osu.Framework.Graphics.OpenGL.Textures;
|
||||
using osu.Framework.Graphics.Textures;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Framework.Timing;
|
||||
using osu.Game.Audio;
|
||||
using osu.Game.Skinning;
|
||||
using osu.Game.Tests.Visual;
|
||||
|
||||
namespace osu.Game.Tests.NonVisual.Skinning
|
||||
{
|
||||
[HeadlessTest]
|
||||
public class LegacySkinAnimationTest : OsuTestScene
|
||||
{
|
||||
private const string animation_name = "animation";
|
||||
private const int frame_count = 6;
|
||||
|
||||
[Cached(typeof(IAnimationTimeReference))]
|
||||
private TestAnimationTimeReference animationTimeReference = new TestAnimationTimeReference();
|
||||
|
||||
private TextureAnimation animation;
|
||||
|
||||
[Test]
|
||||
public void TestAnimationTimeReferenceChange()
|
||||
{
|
||||
ISkin skin = new TestSkin();
|
||||
|
||||
AddStep("get animation", () => Add(animation = (TextureAnimation)skin.GetAnimation(animation_name, true, false)));
|
||||
AddAssert("frame count correct", () => animation.FrameCount == frame_count);
|
||||
assertPlaybackPosition(0);
|
||||
|
||||
AddStep("set start time to 1000", () => animationTimeReference.AnimationStartTime.Value = 1000);
|
||||
assertPlaybackPosition(-1000);
|
||||
|
||||
AddStep("set current time to 500", () => animationTimeReference.ManualClock.CurrentTime = 500);
|
||||
assertPlaybackPosition(-500);
|
||||
}
|
||||
|
||||
private void assertPlaybackPosition(double expectedPosition)
|
||||
=> AddAssert($"playback position is {expectedPosition}", () => animation.PlaybackPosition == expectedPosition);
|
||||
|
||||
private class TestSkin : ISkin
|
||||
{
|
||||
private static readonly string[] lookup_names = Enumerable.Range(0, frame_count).Select(frame => $"{animation_name}-{frame}").ToArray();
|
||||
|
||||
public Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT)
|
||||
{
|
||||
return lookup_names.Contains(componentName) ? Texture.WhitePixel : null;
|
||||
}
|
||||
|
||||
public Drawable GetDrawableComponent(ISkinComponent component) => throw new NotSupportedException();
|
||||
public SampleChannel GetSample(ISampleInfo sampleInfo) => throw new NotSupportedException();
|
||||
public IBindable<TValue> GetConfig<TLookup, TValue>(TLookup lookup) => throw new NotSupportedException();
|
||||
}
|
||||
|
||||
private class TestAnimationTimeReference : IAnimationTimeReference
|
||||
{
|
||||
public ManualClock ManualClock { get; }
|
||||
public IFrameBasedClock Clock { get; }
|
||||
public Bindable<double> AnimationStartTime { get; } = new BindableDouble();
|
||||
|
||||
public TestAnimationTimeReference()
|
||||
{
|
||||
ManualClock = new ManualClock();
|
||||
Clock = new FramedClock(ManualClock);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -318,7 +318,7 @@ namespace osu.Game.Tests.Visual.Background
|
||||
private class FadeAccessibleResults : ResultsScreen
|
||||
{
|
||||
public FadeAccessibleResults(ScoreInfo score)
|
||||
: base(score)
|
||||
: base(score, true)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -153,6 +153,9 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
|
||||
private class SnapProvider : IPositionSnapProvider
|
||||
{
|
||||
public SnapResult SnapScreenSpacePositionToValidPosition(Vector2 screenSpacePosition) =>
|
||||
new SnapResult(screenSpacePosition, null);
|
||||
|
||||
public SnapResult SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition) => new SnapResult(screenSpacePosition, 0);
|
||||
|
||||
public float GetBeatSnapDistanceAt(double referenceTime) => 10;
|
||||
|
@ -21,13 +21,13 @@ using osu.Game.Rulesets.Difficulty;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Objects.Legacy;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Rulesets.UI;
|
||||
using osu.Game.Rulesets.UI.Scrolling;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Gameplay
|
||||
{
|
||||
@ -46,6 +46,50 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
[SetUp]
|
||||
public void Setup() => Schedule(() => testClock.CurrentTime = 0);
|
||||
|
||||
[TestCase("pooled")]
|
||||
[TestCase("non-pooled")]
|
||||
public void TestHitObjectLifetime(string pooled)
|
||||
{
|
||||
var beatmap = createBeatmap(_ => pooled == "pooled" ? new TestPooledHitObject() : new TestHitObject());
|
||||
beatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = time_range });
|
||||
createTest(beatmap);
|
||||
|
||||
assertPosition(0, 0f);
|
||||
assertDead(3);
|
||||
|
||||
setTime(3 * time_range);
|
||||
assertPosition(3, 0f);
|
||||
assertDead(0);
|
||||
|
||||
setTime(0 * time_range);
|
||||
assertPosition(0, 0f);
|
||||
assertDead(3);
|
||||
}
|
||||
|
||||
[TestCase("pooled")]
|
||||
[TestCase("non-pooled")]
|
||||
public void TestNestedHitObject(string pooled)
|
||||
{
|
||||
var beatmap = createBeatmap(i =>
|
||||
{
|
||||
var h = pooled == "pooled" ? new TestPooledParentHitObject() : new TestParentHitObject();
|
||||
h.Duration = 300;
|
||||
h.ChildTimeOffset = i % 3 * 100;
|
||||
return h;
|
||||
});
|
||||
beatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = time_range });
|
||||
createTest(beatmap);
|
||||
|
||||
assertPosition(0, 0f);
|
||||
assertHeight(0);
|
||||
assertChildPosition(0);
|
||||
|
||||
setTime(5 * time_range);
|
||||
assertPosition(5, 0f);
|
||||
assertHeight(5);
|
||||
assertChildPosition(5);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestRelativeBeatLengthScaleSingleTimingPoint()
|
||||
{
|
||||
@ -147,8 +191,37 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
assertPosition(1, 1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a <see cref="DrawableTestHitObject" /> corresponding to the <paramref name="index"/>'th <see cref="TestHitObject"/>.
|
||||
/// When the hit object is not alive, `null` is returned.
|
||||
/// </summary>
|
||||
[CanBeNull]
|
||||
private DrawableTestHitObject getDrawableHitObject(int index)
|
||||
{
|
||||
var hitObject = drawableRuleset.Beatmap.HitObjects.ElementAt(index);
|
||||
return (DrawableTestHitObject)drawableRuleset.Playfield.HitObjectContainer.AliveObjects.FirstOrDefault(obj => obj.HitObject == hitObject);
|
||||
}
|
||||
|
||||
private float yScale => drawableRuleset.Playfield.HitObjectContainer.DrawHeight;
|
||||
|
||||
private void assertDead(int index) => AddAssert($"hitobject {index} is dead", () => getDrawableHitObject(index) == null);
|
||||
|
||||
private void assertHeight(int index) => AddAssert($"hitobject {index} height", () =>
|
||||
{
|
||||
var d = getDrawableHitObject(index);
|
||||
return d != null && Precision.AlmostEquals(d.DrawHeight, yScale * (float)(d.HitObject.Duration / time_range), 0.1f);
|
||||
});
|
||||
|
||||
private void assertChildPosition(int index) => AddAssert($"hitobject {index} child position", () =>
|
||||
{
|
||||
var d = getDrawableHitObject(index);
|
||||
return d is DrawableTestParentHitObject && Precision.AlmostEquals(
|
||||
d.NestedHitObjects.First().DrawPosition.Y,
|
||||
yScale * (float)((TestParentHitObject)d.HitObject).ChildTimeOffset / time_range, 0.1f);
|
||||
});
|
||||
|
||||
private void assertPosition(int index, float relativeY) => AddAssert($"hitobject {index} at {relativeY}",
|
||||
() => Precision.AlmostEquals(drawableRuleset.Playfield.AllHitObjects.ElementAt(index).DrawPosition.Y, drawableRuleset.Playfield.HitObjectContainer.DrawHeight * relativeY));
|
||||
() => Precision.AlmostEquals(getDrawableHitObject(index)?.DrawPosition.Y ?? -1, yScale * relativeY));
|
||||
|
||||
private void setTime(double time)
|
||||
{
|
||||
@ -160,12 +233,16 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
/// The hitobjects are spaced <see cref="time_range"/> milliseconds apart.
|
||||
/// </summary>
|
||||
/// <returns>The <see cref="IBeatmap"/>.</returns>
|
||||
private IBeatmap createBeatmap()
|
||||
private IBeatmap createBeatmap(Func<int, TestHitObject> createAction = null)
|
||||
{
|
||||
var beatmap = new Beatmap<HitObject> { BeatmapInfo = { Ruleset = new OsuRuleset().RulesetInfo } };
|
||||
var beatmap = new Beatmap<TestHitObject> { BeatmapInfo = { Ruleset = new OsuRuleset().RulesetInfo } };
|
||||
|
||||
for (int i = 0; i < 10; i++)
|
||||
beatmap.HitObjects.Add(new HitObject { StartTime = i * time_range });
|
||||
{
|
||||
var h = createAction?.Invoke(i) ?? new TestHitObject();
|
||||
h.StartTime = i * time_range;
|
||||
beatmap.HitObjects.Add(h);
|
||||
}
|
||||
|
||||
return beatmap;
|
||||
}
|
||||
@ -225,7 +302,21 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
TimeRange.Value = time_range;
|
||||
}
|
||||
|
||||
public override DrawableHitObject<TestHitObject> CreateDrawableRepresentation(TestHitObject h) => new DrawableTestHitObject(h);
|
||||
public override DrawableHitObject<TestHitObject> CreateDrawableRepresentation(TestHitObject h)
|
||||
{
|
||||
switch (h)
|
||||
{
|
||||
case TestPooledHitObject _:
|
||||
case TestPooledParentHitObject _:
|
||||
return null;
|
||||
|
||||
case TestParentHitObject p:
|
||||
return new DrawableTestParentHitObject(p);
|
||||
|
||||
default:
|
||||
return new DrawableTestHitObject(h);
|
||||
}
|
||||
}
|
||||
|
||||
protected override PassThroughInputManager CreateInputManager() => new PassThroughInputManager();
|
||||
|
||||
@ -265,6 +356,9 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
RegisterPool<TestPooledHitObject, DrawableTestPooledHitObject>(1);
|
||||
RegisterPool<TestPooledParentHitObject, DrawableTestPooledParentHitObject>(1);
|
||||
}
|
||||
}
|
||||
|
||||
@ -277,30 +371,46 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
|
||||
public override bool CanConvert() => true;
|
||||
|
||||
protected override IEnumerable<TestHitObject> ConvertHitObject(HitObject original, IBeatmap beatmap, CancellationToken cancellationToken)
|
||||
{
|
||||
yield return new TestHitObject
|
||||
{
|
||||
StartTime = original.StartTime,
|
||||
Duration = (original as IHasDuration)?.Duration ?? 100
|
||||
};
|
||||
}
|
||||
protected override IEnumerable<TestHitObject> ConvertHitObject(HitObject original, IBeatmap beatmap, CancellationToken cancellationToken) =>
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region HitObject
|
||||
|
||||
private class TestHitObject : ConvertHitObject, IHasDuration
|
||||
private class TestHitObject : HitObject, IHasDuration
|
||||
{
|
||||
public double EndTime => StartTime + Duration;
|
||||
|
||||
public double Duration { get; set; }
|
||||
public double Duration { get; set; } = 100;
|
||||
}
|
||||
|
||||
private class TestPooledHitObject : TestHitObject
|
||||
{
|
||||
}
|
||||
|
||||
private class TestParentHitObject : TestHitObject
|
||||
{
|
||||
public double ChildTimeOffset;
|
||||
|
||||
protected override void CreateNestedHitObjects(CancellationToken cancellationToken)
|
||||
{
|
||||
AddNested(new TestHitObject { StartTime = StartTime + ChildTimeOffset });
|
||||
}
|
||||
}
|
||||
|
||||
private class TestPooledParentHitObject : TestParentHitObject
|
||||
{
|
||||
protected override void CreateNestedHitObjects(CancellationToken cancellationToken)
|
||||
{
|
||||
AddNested(new TestPooledHitObject { StartTime = StartTime + ChildTimeOffset });
|
||||
}
|
||||
}
|
||||
|
||||
private class DrawableTestHitObject : DrawableHitObject<TestHitObject>
|
||||
{
|
||||
public DrawableTestHitObject(TestHitObject hitObject)
|
||||
public DrawableTestHitObject([CanBeNull] TestHitObject hitObject)
|
||||
: base(hitObject)
|
||||
{
|
||||
Anchor = Anchor.TopCentre;
|
||||
@ -324,6 +434,52 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected override void Update() => LifetimeEnd = HitObject.EndTime;
|
||||
}
|
||||
|
||||
private class DrawableTestPooledHitObject : DrawableTestHitObject
|
||||
{
|
||||
public DrawableTestPooledHitObject()
|
||||
: base(null)
|
||||
{
|
||||
InternalChildren[0].Colour = Color4.LightSkyBlue;
|
||||
InternalChildren[1].Colour = Color4.Blue;
|
||||
}
|
||||
}
|
||||
|
||||
private class DrawableTestParentHitObject : DrawableTestHitObject
|
||||
{
|
||||
private readonly Container<DrawableHitObject> container;
|
||||
|
||||
public DrawableTestParentHitObject([CanBeNull] TestHitObject hitObject)
|
||||
: base(hitObject)
|
||||
{
|
||||
InternalChildren[0].Colour = Color4.LightYellow;
|
||||
InternalChildren[1].Colour = Color4.Yellow;
|
||||
|
||||
AddInternal(container = new Container<DrawableHitObject>
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
});
|
||||
}
|
||||
|
||||
protected override DrawableHitObject CreateNestedHitObject(HitObject hitObject) =>
|
||||
new DrawableTestHitObject((TestHitObject)hitObject);
|
||||
|
||||
protected override void AddNestedHitObject(DrawableHitObject hitObject) => container.Add(hitObject);
|
||||
|
||||
protected override void ClearNestedHitObjects() => container.Clear(false);
|
||||
}
|
||||
|
||||
private class DrawableTestPooledParentHitObject : DrawableTestParentHitObject
|
||||
{
|
||||
public DrawableTestPooledParentHitObject()
|
||||
: base(null)
|
||||
{
|
||||
InternalChildren[0].Colour = Color4.LightSeaGreen;
|
||||
InternalChildren[1].Colour = Color4.Green;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user