mirror of
https://github.com/ppy/osu.git
synced 2025-02-09 14:02:56 +08:00
Merge branch 'master' into cognition
This commit is contained in:
commit
c690ecf482
@ -21,7 +21,7 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"ppy.localisationanalyser.tools": {
|
"ppy.localisationanalyser.tools": {
|
||||||
"version": "2022.607.0",
|
"version": "2022.809.0",
|
||||||
"commands": [
|
"commands": [
|
||||||
"localisation"
|
"localisation"
|
||||||
]
|
]
|
||||||
|
@ -53,3 +53,7 @@ dotnet_diagnostic.CA2225.severity = none
|
|||||||
|
|
||||||
# Banned APIs
|
# Banned APIs
|
||||||
dotnet_diagnostic.RS0030.severity = error
|
dotnet_diagnostic.RS0030.severity = error
|
||||||
|
|
||||||
|
# Temporarily disable analysing CanBeNull = true in NRT contexts due to mobile issues.
|
||||||
|
# See: https://github.com/ppy/osu/pull/19677
|
||||||
|
dotnet_diagnostic.OSUF001.severity = none
|
@ -77,5 +77,8 @@ namespace osu.Game.Rulesets.EmptyFreeform
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Leave this line intact. It will bake the correct version into the ruleset on each build/release.
|
||||||
|
public override string RulesetAPIVersionSupported => CURRENT_RULESET_API_VERSION;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -49,5 +49,8 @@ namespace osu.Game.Rulesets.Pippidon
|
|||||||
};
|
};
|
||||||
|
|
||||||
public override Drawable CreateIcon() => new PippidonRulesetIcon(this);
|
public override Drawable CreateIcon() => new PippidonRulesetIcon(this);
|
||||||
|
|
||||||
|
// Leave this line intact. It will bake the correct version into the ruleset on each build/release.
|
||||||
|
public override string RulesetAPIVersionSupported => CURRENT_RULESET_API_VERSION;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -54,5 +54,8 @@ namespace osu.Game.Rulesets.EmptyScrolling
|
|||||||
Text = ShortName[0].ToString(),
|
Text = ShortName[0].ToString(),
|
||||||
Font = OsuFont.Default.With(size: 18),
|
Font = OsuFont.Default.With(size: 18),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Leave this line intact. It will bake the correct version into the ruleset on each build/release.
|
||||||
|
public override string RulesetAPIVersionSupported => CURRENT_RULESET_API_VERSION;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,8 +21,11 @@ namespace osu.Game.Rulesets.Pippidon.Beatmaps
|
|||||||
public PippidonBeatmapConverter(IBeatmap beatmap, Ruleset ruleset)
|
public PippidonBeatmapConverter(IBeatmap beatmap, Ruleset ruleset)
|
||||||
: base(beatmap, ruleset)
|
: base(beatmap, ruleset)
|
||||||
{
|
{
|
||||||
minPosition = beatmap.HitObjects.Min(getUsablePosition);
|
if (beatmap.HitObjects.Any())
|
||||||
maxPosition = beatmap.HitObjects.Max(getUsablePosition);
|
{
|
||||||
|
minPosition = beatmap.HitObjects.Min(getUsablePosition);
|
||||||
|
maxPosition = beatmap.HitObjects.Max(getUsablePosition);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool CanConvert() => Beatmap.HitObjects.All(h => h is IHasXPosition && h is IHasYPosition);
|
public override bool CanConvert() => Beatmap.HitObjects.All(h => h is IHasXPosition && h is IHasYPosition);
|
||||||
|
@ -46,5 +46,8 @@ namespace osu.Game.Rulesets.Pippidon
|
|||||||
};
|
};
|
||||||
|
|
||||||
public override Drawable CreateIcon() => new PippidonRulesetIcon(this);
|
public override Drawable CreateIcon() => new PippidonRulesetIcon(this);
|
||||||
|
|
||||||
|
// Leave this line intact. It will bake the correct version into the ruleset on each build/release.
|
||||||
|
public override string RulesetAPIVersionSupported => CURRENT_RULESET_API_VERSION;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -51,11 +51,11 @@
|
|||||||
<Reference Include="Java.Interop" />
|
<Reference Include="Java.Interop" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.810.0" />
|
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.831.0" />
|
||||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2022.810.2" />
|
<PackageReference Include="ppy.osu.Framework.Android" Version="2022.901.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup Label="Transitive Dependencies">
|
<ItemGroup Label="Transitive Dependencies">
|
||||||
<!-- Realm needs to be directly referenced in all Xamarin projects, as it will not pull in its transitive dependencies otherwise. -->
|
<!-- Realm needs to be directly referenced in all Xamarin projects, as it will not pull in its transitive dependencies otherwise. -->
|
||||||
<PackageReference Include="Realm" Version="10.14.0" />
|
<PackageReference Include="Realm" Version="10.15.1" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
@ -76,7 +76,7 @@ namespace osu.Desktop.Security
|
|||||||
private void load(OsuColour colours)
|
private void load(OsuColour colours)
|
||||||
{
|
{
|
||||||
Icon = FontAwesome.Solid.ShieldAlt;
|
Icon = FontAwesome.Solid.ShieldAlt;
|
||||||
IconBackground.Colour = colours.YellowDark;
|
IconContent.Colour = colours.YellowDark;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,8 +6,6 @@ using System.Runtime.Versioning;
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Colour;
|
|
||||||
using osu.Framework.Graphics.Shapes;
|
|
||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
using osu.Framework.Logging;
|
using osu.Framework.Logging;
|
||||||
using osu.Game;
|
using osu.Game;
|
||||||
@ -15,7 +13,6 @@ using osu.Game.Graphics;
|
|||||||
using osu.Game.Overlays;
|
using osu.Game.Overlays;
|
||||||
using osu.Game.Overlays.Notifications;
|
using osu.Game.Overlays.Notifications;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using osuTK.Graphics;
|
|
||||||
using Squirrel;
|
using Squirrel;
|
||||||
using Squirrel.SimpleSplat;
|
using Squirrel.SimpleSplat;
|
||||||
|
|
||||||
@ -177,17 +174,11 @@ namespace osu.Desktop.Updater
|
|||||||
{
|
{
|
||||||
IconContent.AddRange(new Drawable[]
|
IconContent.AddRange(new Drawable[]
|
||||||
{
|
{
|
||||||
new Box
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Colour = ColourInfo.GradientVertical(colours.YellowDark, colours.Yellow)
|
|
||||||
},
|
|
||||||
new SpriteIcon
|
new SpriteIcon
|
||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
Icon = FontAwesome.Solid.Upload,
|
Icon = FontAwesome.Solid.Upload,
|
||||||
Colour = Color4.White,
|
|
||||||
Size = new Vector2(20),
|
Size = new Vector2(20),
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -70,10 +70,17 @@ namespace osu.Game.Rulesets.Catch.Tests.Editor
|
|||||||
[Cached]
|
[Cached]
|
||||||
private readonly BindableBeatDivisor beatDivisor;
|
private readonly BindableBeatDivisor beatDivisor;
|
||||||
|
|
||||||
|
protected override Container<Drawable> Content { get; } = new Container { RelativeSizeAxes = Axes.Both };
|
||||||
|
|
||||||
public EditorBeatmapDependencyContainer(IBeatmap beatmap, BindableBeatDivisor beatDivisor)
|
public EditorBeatmapDependencyContainer(IBeatmap beatmap, BindableBeatDivisor beatDivisor)
|
||||||
{
|
{
|
||||||
editorClock = new EditorClock(beatmap, beatDivisor);
|
|
||||||
this.beatDivisor = beatDivisor;
|
this.beatDivisor = beatDivisor;
|
||||||
|
|
||||||
|
InternalChildren = new Drawable[]
|
||||||
|
{
|
||||||
|
editorClock = new EditorClock(beatmap, beatDivisor),
|
||||||
|
Content,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,29 +3,30 @@
|
|||||||
|
|
||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
||||||
using osu.Game.Beatmaps;
|
using System;
|
||||||
using osu.Game.Graphics;
|
|
||||||
using osu.Game.Rulesets.Catch.Mods;
|
|
||||||
using osu.Game.Rulesets.Catch.UI;
|
|
||||||
using osu.Game.Rulesets.Mods;
|
|
||||||
using osu.Game.Rulesets.UI;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using osu.Framework.Extensions.EnumExtensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
using osu.Framework.Input.Bindings;
|
using osu.Framework.Input.Bindings;
|
||||||
using osu.Game.Rulesets.Catch.Replays;
|
using osu.Framework.Localisation;
|
||||||
using osu.Game.Rulesets.Replays.Types;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Beatmaps.Legacy;
|
using osu.Game.Beatmaps.Legacy;
|
||||||
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Rulesets.Catch.Beatmaps;
|
using osu.Game.Rulesets.Catch.Beatmaps;
|
||||||
using osu.Game.Rulesets.Catch.Difficulty;
|
using osu.Game.Rulesets.Catch.Difficulty;
|
||||||
using osu.Game.Rulesets.Catch.Scoring;
|
|
||||||
using osu.Game.Rulesets.Difficulty;
|
|
||||||
using osu.Game.Rulesets.Scoring;
|
|
||||||
using System;
|
|
||||||
using osu.Framework.Extensions.EnumExtensions;
|
|
||||||
using osu.Game.Rulesets.Catch.Edit;
|
using osu.Game.Rulesets.Catch.Edit;
|
||||||
|
using osu.Game.Rulesets.Catch.Mods;
|
||||||
|
using osu.Game.Rulesets.Catch.Replays;
|
||||||
|
using osu.Game.Rulesets.Catch.Scoring;
|
||||||
using osu.Game.Rulesets.Catch.Skinning.Legacy;
|
using osu.Game.Rulesets.Catch.Skinning.Legacy;
|
||||||
|
using osu.Game.Rulesets.Catch.UI;
|
||||||
|
using osu.Game.Rulesets.Difficulty;
|
||||||
using osu.Game.Rulesets.Edit;
|
using osu.Game.Rulesets.Edit;
|
||||||
|
using osu.Game.Rulesets.Mods;
|
||||||
|
using osu.Game.Rulesets.Replays.Types;
|
||||||
|
using osu.Game.Rulesets.Scoring;
|
||||||
|
using osu.Game.Rulesets.UI;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch
|
namespace osu.Game.Rulesets.Catch
|
||||||
@ -42,6 +43,8 @@ namespace osu.Game.Rulesets.Catch
|
|||||||
|
|
||||||
public const string SHORT_NAME = "fruits";
|
public const string SHORT_NAME = "fruits";
|
||||||
|
|
||||||
|
public override string RulesetAPIVersionSupported => CURRENT_RULESET_API_VERSION;
|
||||||
|
|
||||||
public override IEnumerable<KeyBinding> GetDefaultKeyBindings(int variant = 0) => new[]
|
public override IEnumerable<KeyBinding> GetDefaultKeyBindings(int variant = 0) => new[]
|
||||||
{
|
{
|
||||||
new KeyBinding(InputKey.Z, CatchAction.MoveLeft),
|
new KeyBinding(InputKey.Z, CatchAction.MoveLeft),
|
||||||
@ -162,7 +165,7 @@ namespace osu.Game.Rulesets.Catch
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string GetDisplayNameForHitResult(HitResult result)
|
public override LocalisableString GetDisplayNameForHitResult(HitResult result)
|
||||||
{
|
{
|
||||||
switch (result)
|
switch (result)
|
||||||
{
|
{
|
||||||
|
@ -56,7 +56,7 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components
|
|||||||
public void UpdateFrom(ScrollingHitObjectContainer hitObjectContainer, JuiceStream hitObject)
|
public void UpdateFrom(ScrollingHitObjectContainer hitObjectContainer, JuiceStream hitObject)
|
||||||
{
|
{
|
||||||
while (path.Vertices.Count < InternalChildren.Count)
|
while (path.Vertices.Count < InternalChildren.Count)
|
||||||
RemoveInternal(InternalChildren[^1]);
|
RemoveInternal(InternalChildren[^1], true);
|
||||||
|
|
||||||
while (InternalChildren.Count < path.Vertices.Count)
|
while (InternalChildren.Count < path.Vertices.Count)
|
||||||
AddInternal(new VertexPiece());
|
AddInternal(new VertexPiece());
|
||||||
|
@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components
|
|||||||
.Where(h => !(h is TinyDroplet)));
|
.Where(h => !(h is TinyDroplet)));
|
||||||
|
|
||||||
while (nestedHitObjects.Count < InternalChildren.Count)
|
while (nestedHitObjects.Count < InternalChildren.Count)
|
||||||
RemoveInternal(InternalChildren[^1]);
|
RemoveInternal(InternalChildren[^1], true);
|
||||||
|
|
||||||
while (InternalChildren.Count < nestedHitObjects.Count)
|
while (InternalChildren.Count < nestedHitObjects.Count)
|
||||||
AddInternal(new FruitOutline());
|
AddInternal(new FruitOutline());
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Localisation;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Mods
|
namespace osu.Game.Rulesets.Catch.Mods
|
||||||
{
|
{
|
||||||
public class CatchModEasy : ModEasyWithExtraLives
|
public class CatchModEasy : ModEasyWithExtraLives
|
||||||
{
|
{
|
||||||
public override string Description => @"Larger fruits, more forgiving HP drain, less accuracy required, and three lives!";
|
public override LocalisableString Description => @"Larger fruits, more forgiving HP drain, less accuracy required, and three lives!";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
using osu.Framework.Localisation;
|
||||||
using osu.Game.Rulesets.Catch.Objects;
|
using osu.Game.Rulesets.Catch.Objects;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.UI;
|
using osu.Game.Rulesets.UI;
|
||||||
@ -14,7 +15,7 @@ namespace osu.Game.Rulesets.Catch.Mods
|
|||||||
{
|
{
|
||||||
public override string Name => "Floating Fruits";
|
public override string Name => "Floating Fruits";
|
||||||
public override string Acronym => "FF";
|
public override string Acronym => "FF";
|
||||||
public override string Description => "The fruits are... floating?";
|
public override LocalisableString Description => "The fruits are... floating?";
|
||||||
public override double ScoreMultiplier => 1;
|
public override double ScoreMultiplier => 1;
|
||||||
public override IconUsage? Icon => FontAwesome.Solid.Cloud;
|
public override IconUsage? Icon => FontAwesome.Solid.Cloud;
|
||||||
|
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Localisation;
|
||||||
using osu.Game.Rulesets.Catch.Objects;
|
using osu.Game.Rulesets.Catch.Objects;
|
||||||
using osu.Game.Rulesets.Catch.Objects.Drawables;
|
using osu.Game.Rulesets.Catch.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.Catch.UI;
|
using osu.Game.Rulesets.Catch.UI;
|
||||||
@ -14,7 +15,7 @@ namespace osu.Game.Rulesets.Catch.Mods
|
|||||||
{
|
{
|
||||||
public class CatchModHidden : ModHidden, IApplicableToDrawableRuleset<CatchHitObject>
|
public class CatchModHidden : ModHidden, IApplicableToDrawableRuleset<CatchHitObject>
|
||||||
{
|
{
|
||||||
public override string Description => @"Play with fading fruits.";
|
public override LocalisableString Description => @"Play with fading fruits.";
|
||||||
public override double ScoreMultiplier => UsesDefaultConfiguration ? 1.06 : 1;
|
public override double ScoreMultiplier => UsesDefaultConfiguration ? 1.06 : 1;
|
||||||
|
|
||||||
private const double fade_out_offset_multiplier = 0.6;
|
private const double fade_out_offset_multiplier = 0.6;
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using osu.Framework.Localisation;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Rulesets.Catch.Beatmaps;
|
using osu.Game.Rulesets.Catch.Beatmaps;
|
||||||
using osu.Game.Rulesets.Catch.Objects;
|
using osu.Game.Rulesets.Catch.Objects;
|
||||||
@ -14,7 +15,7 @@ namespace osu.Game.Rulesets.Catch.Mods
|
|||||||
{
|
{
|
||||||
public class CatchModMirror : ModMirror, IApplicableToBeatmap
|
public class CatchModMirror : ModMirror, IApplicableToBeatmap
|
||||||
{
|
{
|
||||||
public override string Description => "Fruits are flipped horizontally.";
|
public override LocalisableString Description => "Fruits are flipped horizontally.";
|
||||||
|
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// <see cref="IApplicableToBeatmap"/> is used instead of <see cref="IApplicableToHitObject"/>,
|
/// <see cref="IApplicableToBeatmap"/> is used instead of <see cref="IApplicableToHitObject"/>,
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Localisation;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
using osu.Game.Configuration;
|
using osu.Game.Configuration;
|
||||||
@ -14,7 +15,7 @@ namespace osu.Game.Rulesets.Catch.Mods
|
|||||||
{
|
{
|
||||||
public class CatchModNoScope : ModNoScope, IUpdatableByPlayfield
|
public class CatchModNoScope : ModNoScope, IUpdatableByPlayfield
|
||||||
{
|
{
|
||||||
public override string Description => "Where's the catcher?";
|
public override LocalisableString Description => "Where's the catcher?";
|
||||||
|
|
||||||
[SettingSource(
|
[SettingSource(
|
||||||
"Hidden at combo",
|
"Hidden at combo",
|
||||||
|
@ -5,6 +5,7 @@ using osu.Framework.Graphics;
|
|||||||
using osu.Framework.Input;
|
using osu.Framework.Input;
|
||||||
using osu.Framework.Input.Bindings;
|
using osu.Framework.Input.Bindings;
|
||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
|
using osu.Framework.Localisation;
|
||||||
using osu.Game.Rulesets.Catch.Objects;
|
using osu.Game.Rulesets.Catch.Objects;
|
||||||
using osu.Game.Rulesets.Catch.UI;
|
using osu.Game.Rulesets.Catch.UI;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
@ -16,7 +17,7 @@ namespace osu.Game.Rulesets.Catch.Mods
|
|||||||
{
|
{
|
||||||
public class CatchModRelax : ModRelax, IApplicableToDrawableRuleset<CatchHitObject>, IApplicableToPlayer
|
public class CatchModRelax : ModRelax, IApplicableToDrawableRuleset<CatchHitObject>, IApplicableToPlayer
|
||||||
{
|
{
|
||||||
public override string Description => @"Use the mouse to control the catcher.";
|
public override LocalisableString Description => @"Use the mouse to control the catcher.";
|
||||||
|
|
||||||
private DrawableRuleset<CatchHitObject> drawableRuleset = null!;
|
private DrawableRuleset<CatchHitObject> drawableRuleset = null!;
|
||||||
|
|
||||||
|
@ -271,8 +271,8 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
SetHyperDashState();
|
SetHyperDashState();
|
||||||
}
|
}
|
||||||
|
|
||||||
caughtObjectContainer.RemoveAll(d => d.HitObject == drawableObject.HitObject);
|
caughtObjectContainer.RemoveAll(d => d.HitObject == drawableObject.HitObject, false);
|
||||||
droppedObjectTarget.RemoveAll(d => d.HitObject == drawableObject.HitObject);
|
droppedObjectTarget.RemoveAll(d => d.HitObject == drawableObject.HitObject, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -430,7 +430,7 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
{
|
{
|
||||||
var droppedObject = getDroppedObject(caughtObject);
|
var droppedObject = getDroppedObject(caughtObject);
|
||||||
|
|
||||||
caughtObjectContainer.Remove(caughtObject);
|
caughtObjectContainer.Remove(caughtObject, false);
|
||||||
|
|
||||||
droppedObjectTarget.Add(droppedObject);
|
droppedObjectTarget.Add(droppedObject);
|
||||||
|
|
||||||
|
@ -93,15 +93,15 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
switch (entry.Animation)
|
switch (entry.Animation)
|
||||||
{
|
{
|
||||||
case CatcherTrailAnimation.Dashing:
|
case CatcherTrailAnimation.Dashing:
|
||||||
dashTrails.Remove(drawable);
|
dashTrails.Remove(drawable, false);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case CatcherTrailAnimation.HyperDashing:
|
case CatcherTrailAnimation.HyperDashing:
|
||||||
hyperDashTrails.Remove(drawable);
|
hyperDashTrails.Remove(drawable, false);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case CatcherTrailAnimation.HyperDashAfterImage:
|
case CatcherTrailAnimation.HyperDashAfterImage:
|
||||||
hyperDashAfterImages.Remove(drawable);
|
hyperDashAfterImages.Remove(drawable, false);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,7 @@ using osu.Framework.Allocation;
|
|||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
using osu.Game.Database;
|
using osu.Game.Database;
|
||||||
using osu.Game.Overlays;
|
using osu.Game.Overlays;
|
||||||
using osu.Game.Rulesets.Edit;
|
using osu.Game.Rulesets.Edit;
|
||||||
@ -34,10 +35,14 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor
|
|||||||
{
|
{
|
||||||
AddStep("setup compose screen", () =>
|
AddStep("setup compose screen", () =>
|
||||||
{
|
{
|
||||||
var editorBeatmap = new EditorBeatmap(new ManiaBeatmap(new StageDefinition { Columns = 4 })
|
var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 4 })
|
||||||
{
|
{
|
||||||
BeatmapInfo = { Ruleset = new ManiaRuleset().RulesetInfo },
|
BeatmapInfo = { Ruleset = new ManiaRuleset().RulesetInfo },
|
||||||
});
|
};
|
||||||
|
|
||||||
|
beatmap.ControlPointInfo.Add(0, new TimingControlPoint());
|
||||||
|
|
||||||
|
var editorBeatmap = new EditorBeatmap(beatmap, new LegacyBeatmapSkin(beatmap.BeatmapInfo, null));
|
||||||
|
|
||||||
Beatmap.Value = CreateWorkingBeatmap(editorBeatmap.PlayableBeatmap);
|
Beatmap.Value = CreateWorkingBeatmap(editorBeatmap.PlayableBeatmap);
|
||||||
|
|
||||||
@ -50,7 +55,11 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor
|
|||||||
(typeof(IBeatSnapProvider), editorBeatmap),
|
(typeof(IBeatSnapProvider), editorBeatmap),
|
||||||
(typeof(OverlayColourProvider), new OverlayColourProvider(OverlayColourScheme.Green)),
|
(typeof(OverlayColourProvider), new OverlayColourProvider(OverlayColourScheme.Green)),
|
||||||
},
|
},
|
||||||
Child = new ComposeScreen { State = { Value = Visibility.Visible } },
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
editorBeatmap,
|
||||||
|
new ComposeScreen { State = { Value = Visibility.Visible } },
|
||||||
|
}
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -35,7 +35,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor
|
|||||||
public void Setup() => Schedule(() =>
|
public void Setup() => Schedule(() =>
|
||||||
{
|
{
|
||||||
BeatDivisor.Value = 8;
|
BeatDivisor.Value = 8;
|
||||||
Clock.Seek(0);
|
EditorClock.Seek(0);
|
||||||
|
|
||||||
Child = composer = new TestComposer { RelativeSizeAxes = Axes.Both };
|
Child = composer = new TestComposer { RelativeSizeAxes = Axes.Both };
|
||||||
});
|
});
|
||||||
@ -53,7 +53,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor
|
|||||||
{
|
{
|
||||||
lastObject = this.ChildrenOfType<DrawableHitObject>().Single(d => d.HitObject == composer.EditorBeatmap.HitObjects.Last());
|
lastObject = this.ChildrenOfType<DrawableHitObject>().Single(d => d.HitObject == composer.EditorBeatmap.HitObjects.Last());
|
||||||
originalTime = lastObject.HitObject.StartTime;
|
originalTime = lastObject.HitObject.StartTime;
|
||||||
Clock.Seek(composer.EditorBeatmap.HitObjects.Last().StartTime);
|
EditorClock.Seek(composer.EditorBeatmap.HitObjects.Last().StartTime);
|
||||||
});
|
});
|
||||||
|
|
||||||
AddStep("select all objects", () => composer.EditorBeatmap.SelectedHitObjects.AddRange(composer.EditorBeatmap.HitObjects));
|
AddStep("select all objects", () => composer.EditorBeatmap.SelectedHitObjects.AddRange(composer.EditorBeatmap.HitObjects));
|
||||||
@ -90,7 +90,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor
|
|||||||
{
|
{
|
||||||
lastObject = this.ChildrenOfType<DrawableHitObject>().Single(d => d.HitObject == composer.EditorBeatmap.HitObjects.Last());
|
lastObject = this.ChildrenOfType<DrawableHitObject>().Single(d => d.HitObject == composer.EditorBeatmap.HitObjects.Last());
|
||||||
originalTime = lastObject.HitObject.StartTime;
|
originalTime = lastObject.HitObject.StartTime;
|
||||||
Clock.Seek(composer.EditorBeatmap.HitObjects.Last().StartTime);
|
EditorClock.Seek(composer.EditorBeatmap.HitObjects.Last().StartTime);
|
||||||
});
|
});
|
||||||
|
|
||||||
AddStep("select all objects", () => composer.EditorBeatmap.SelectedHitObjects.AddRange(composer.EditorBeatmap.HitObjects));
|
AddStep("select all objects", () => composer.EditorBeatmap.SelectedHitObjects.AddRange(composer.EditorBeatmap.HitObjects));
|
||||||
@ -125,7 +125,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor
|
|||||||
AddStep("seek to last object", () =>
|
AddStep("seek to last object", () =>
|
||||||
{
|
{
|
||||||
lastObject = this.ChildrenOfType<DrawableHitObject>().Single(d => d.HitObject == composer.EditorBeatmap.HitObjects.Last());
|
lastObject = this.ChildrenOfType<DrawableHitObject>().Single(d => d.HitObject == composer.EditorBeatmap.HitObjects.Last());
|
||||||
Clock.Seek(composer.EditorBeatmap.HitObjects.Last().StartTime);
|
EditorClock.Seek(composer.EditorBeatmap.HitObjects.Last().StartTime);
|
||||||
});
|
});
|
||||||
|
|
||||||
AddStep("select all objects", () => composer.EditorBeatmap.SelectedHitObjects.AddRange(composer.EditorBeatmap.HitObjects));
|
AddStep("select all objects", () => composer.EditorBeatmap.SelectedHitObjects.AddRange(composer.EditorBeatmap.HitObjects));
|
||||||
|
@ -16,11 +16,11 @@ namespace osu.Game.Rulesets.Mania.Tests
|
|||||||
{
|
{
|
||||||
protected override string ResourceAssembly => "osu.Game.Rulesets.Mania";
|
protected override string ResourceAssembly => "osu.Game.Rulesets.Mania";
|
||||||
|
|
||||||
[TestCase(2.3449735700206298d, 242, "diffcalc-test")]
|
[TestCase(2.3493769750220914d, 242, "diffcalc-test")]
|
||||||
public void Test(double expectedStarRating, int expectedMaxCombo, string name)
|
public void Test(double expectedStarRating, int expectedMaxCombo, string name)
|
||||||
=> base.Test(expectedStarRating, expectedMaxCombo, name);
|
=> base.Test(expectedStarRating, expectedMaxCombo, name);
|
||||||
|
|
||||||
[TestCase(2.7879104989252959d, 242, "diffcalc-test")]
|
[TestCase(2.797245912537965d, 242, "diffcalc-test")]
|
||||||
public void TestClockRateAdjusted(double expectedStarRating, int expectedMaxCombo, string name)
|
public void TestClockRateAdjusted(double expectedStarRating, int expectedMaxCombo, string name)
|
||||||
=> Test(expectedStarRating, expectedMaxCombo, name, new ManiaModDoubleTime());
|
=> Test(expectedStarRating, expectedMaxCombo, name, new ManiaModDoubleTime());
|
||||||
|
|
||||||
|
@ -31,7 +31,7 @@ namespace osu.Game.Rulesets.Mania.Difficulty
|
|||||||
private readonly bool isForCurrentRuleset;
|
private readonly bool isForCurrentRuleset;
|
||||||
private readonly double originalOverallDifficulty;
|
private readonly double originalOverallDifficulty;
|
||||||
|
|
||||||
public override int Version => 20220701;
|
public override int Version => 20220902;
|
||||||
|
|
||||||
public ManiaDifficultyCalculator(IRulesetInfo ruleset, IWorkingBeatmap beatmap)
|
public ManiaDifficultyCalculator(IRulesetInfo ruleset, IWorkingBeatmap beatmap)
|
||||||
: base(ruleset, beatmap)
|
: base(ruleset, beatmap)
|
||||||
|
@ -21,7 +21,8 @@ namespace osu.Game.Rulesets.Mania.Difficulty.Skills
|
|||||||
protected override double SkillMultiplier => 1;
|
protected override double SkillMultiplier => 1;
|
||||||
protected override double StrainDecayBase => 1;
|
protected override double StrainDecayBase => 1;
|
||||||
|
|
||||||
private readonly double[] holdEndTimes;
|
private readonly double[] startTimes;
|
||||||
|
private readonly double[] endTimes;
|
||||||
private readonly double[] individualStrains;
|
private readonly double[] individualStrains;
|
||||||
|
|
||||||
private double individualStrain;
|
private double individualStrain;
|
||||||
@ -30,7 +31,8 @@ namespace osu.Game.Rulesets.Mania.Difficulty.Skills
|
|||||||
public Strain(Mod[] mods, int totalColumns)
|
public Strain(Mod[] mods, int totalColumns)
|
||||||
: base(mods)
|
: base(mods)
|
||||||
{
|
{
|
||||||
holdEndTimes = new double[totalColumns];
|
startTimes = new double[totalColumns];
|
||||||
|
endTimes = new double[totalColumns];
|
||||||
individualStrains = new double[totalColumns];
|
individualStrains = new double[totalColumns];
|
||||||
overallStrain = 1;
|
overallStrain = 1;
|
||||||
}
|
}
|
||||||
@ -38,32 +40,27 @@ namespace osu.Game.Rulesets.Mania.Difficulty.Skills
|
|||||||
protected override double StrainValueOf(DifficultyHitObject current)
|
protected override double StrainValueOf(DifficultyHitObject current)
|
||||||
{
|
{
|
||||||
var maniaCurrent = (ManiaDifficultyHitObject)current;
|
var maniaCurrent = (ManiaDifficultyHitObject)current;
|
||||||
|
double startTime = maniaCurrent.StartTime;
|
||||||
double endTime = maniaCurrent.EndTime;
|
double endTime = maniaCurrent.EndTime;
|
||||||
int column = maniaCurrent.BaseObject.Column;
|
int column = maniaCurrent.BaseObject.Column;
|
||||||
double closestEndTime = Math.Abs(endTime - maniaCurrent.LastObject.StartTime); // Lowest value we can assume with the current information
|
|
||||||
|
|
||||||
double holdFactor = 1.0; // Factor to all additional strains in case something else is held
|
|
||||||
double holdAddition = 0; // Addition to the current note in case it's a hold and has to be released awkwardly
|
|
||||||
bool isOverlapping = false;
|
bool isOverlapping = false;
|
||||||
|
|
||||||
// Fill up the holdEndTimes array
|
double closestEndTime = Math.Abs(endTime - startTime); // Lowest value we can assume with the current information
|
||||||
for (int i = 0; i < holdEndTimes.Length; ++i)
|
double holdFactor = 1.0; // Factor to all additional strains in case something else is held
|
||||||
|
double holdAddition = 0; // Addition to the current note in case it's a hold and has to be released awkwardly
|
||||||
|
|
||||||
|
for (int i = 0; i < endTimes.Length; ++i)
|
||||||
{
|
{
|
||||||
// The current note is overlapped if a previous note or end is overlapping the current note body
|
// The current note is overlapped if a previous note or end is overlapping the current note body
|
||||||
isOverlapping |= Precision.DefinitelyBigger(holdEndTimes[i], maniaCurrent.StartTime, 1) && Precision.DefinitelyBigger(endTime, holdEndTimes[i], 1);
|
isOverlapping |= Precision.DefinitelyBigger(endTimes[i], startTime, 1) && Precision.DefinitelyBigger(endTime, endTimes[i], 1);
|
||||||
|
|
||||||
// We give a slight bonus to everything if something is held meanwhile
|
// We give a slight bonus to everything if something is held meanwhile
|
||||||
if (Precision.DefinitelyBigger(holdEndTimes[i], endTime, 1))
|
if (Precision.DefinitelyBigger(endTimes[i], endTime, 1))
|
||||||
holdFactor = 1.25;
|
holdFactor = 1.25;
|
||||||
|
|
||||||
closestEndTime = Math.Min(closestEndTime, Math.Abs(endTime - holdEndTimes[i]));
|
closestEndTime = Math.Min(closestEndTime, Math.Abs(endTime - endTimes[i]));
|
||||||
|
|
||||||
// Decay individual strains
|
|
||||||
individualStrains[i] = applyDecay(individualStrains[i], current.DeltaTime, individual_decay_base);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
holdEndTimes[column] = endTime;
|
|
||||||
|
|
||||||
// The hold addition is given if there was an overlap, however it is only valid if there are no other note with a similar ending.
|
// The hold addition is given if there was an overlap, however it is only valid if there are no other note with a similar ending.
|
||||||
// Releasing multiple notes is just as easy as releasing 1. Nerfs the hold addition by half if the closest release is release_threshold away.
|
// Releasing multiple notes is just as easy as releasing 1. Nerfs the hold addition by half if the closest release is release_threshold away.
|
||||||
// holdAddition
|
// holdAddition
|
||||||
@ -77,12 +74,22 @@ namespace osu.Game.Rulesets.Mania.Difficulty.Skills
|
|||||||
if (isOverlapping)
|
if (isOverlapping)
|
||||||
holdAddition = 1 / (1 + Math.Exp(0.5 * (release_threshold - closestEndTime)));
|
holdAddition = 1 / (1 + Math.Exp(0.5 * (release_threshold - closestEndTime)));
|
||||||
|
|
||||||
// Increase individual strain in own column
|
// Decay and increase individualStrains in own column
|
||||||
|
individualStrains[column] = applyDecay(individualStrains[column], startTime - startTimes[column], individual_decay_base);
|
||||||
individualStrains[column] += 2.0 * holdFactor;
|
individualStrains[column] += 2.0 * holdFactor;
|
||||||
individualStrain = individualStrains[column];
|
|
||||||
|
|
||||||
overallStrain = applyDecay(overallStrain, current.DeltaTime, overall_decay_base) + (1 + holdAddition) * holdFactor;
|
// For notes at the same time (in a chord), the individualStrain should be the hardest individualStrain out of those columns
|
||||||
|
individualStrain = maniaCurrent.DeltaTime <= 1 ? Math.Max(individualStrain, individualStrains[column]) : individualStrains[column];
|
||||||
|
|
||||||
|
// Decay and increase overallStrain
|
||||||
|
overallStrain = applyDecay(overallStrain, current.DeltaTime, overall_decay_base);
|
||||||
|
overallStrain += (1 + holdAddition) * holdFactor;
|
||||||
|
|
||||||
|
// Update startTimes and endTimes arrays
|
||||||
|
startTimes[column] = startTime;
|
||||||
|
endTimes[column] = endTime;
|
||||||
|
|
||||||
|
// By subtracting CurrentStrain, this skill effectively only considers the maximum strain of any one hitobject within each strain section.
|
||||||
return individualStrain + overallStrain - CurrentStrain;
|
return individualStrain + overallStrain - CurrentStrain;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -43,6 +43,7 @@ namespace osu.Game.Rulesets.Mania.Edit.Setup
|
|||||||
private void updateBeatmap()
|
private void updateBeatmap()
|
||||||
{
|
{
|
||||||
Beatmap.BeatmapInfo.SpecialStyle = specialStyle.Current.Value;
|
Beatmap.BeatmapInfo.SpecialStyle = specialStyle.Current.Value;
|
||||||
|
Beatmap.SaveState();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,22 +4,17 @@
|
|||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using osu.Game.Beatmaps;
|
|
||||||
using osu.Game.Rulesets.Mania.Mods;
|
|
||||||
using osu.Game.Rulesets.Mania.UI;
|
|
||||||
using osu.Game.Rulesets.Mods;
|
|
||||||
using osu.Game.Rulesets.UI;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Extensions.EnumExtensions;
|
using osu.Framework.Extensions.EnumExtensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
using osu.Framework.Input.Bindings;
|
using osu.Framework.Input.Bindings;
|
||||||
using osu.Game.Graphics;
|
using osu.Framework.Localisation;
|
||||||
using osu.Game.Rulesets.Mania.Replays;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Rulesets.Replays.Types;
|
|
||||||
using osu.Game.Beatmaps.Legacy;
|
using osu.Game.Beatmaps.Legacy;
|
||||||
using osu.Game.Configuration;
|
using osu.Game.Configuration;
|
||||||
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Overlays.Settings;
|
using osu.Game.Overlays.Settings;
|
||||||
using osu.Game.Rulesets.Configuration;
|
using osu.Game.Rulesets.Configuration;
|
||||||
using osu.Game.Rulesets.Difficulty;
|
using osu.Game.Rulesets.Difficulty;
|
||||||
@ -30,13 +25,19 @@ using osu.Game.Rulesets.Mania.Configuration;
|
|||||||
using osu.Game.Rulesets.Mania.Difficulty;
|
using osu.Game.Rulesets.Mania.Difficulty;
|
||||||
using osu.Game.Rulesets.Mania.Edit;
|
using osu.Game.Rulesets.Mania.Edit;
|
||||||
using osu.Game.Rulesets.Mania.Edit.Setup;
|
using osu.Game.Rulesets.Mania.Edit.Setup;
|
||||||
|
using osu.Game.Rulesets.Mania.Mods;
|
||||||
|
using osu.Game.Rulesets.Mania.Replays;
|
||||||
using osu.Game.Rulesets.Mania.Scoring;
|
using osu.Game.Rulesets.Mania.Scoring;
|
||||||
using osu.Game.Rulesets.Mania.Skinning.Legacy;
|
using osu.Game.Rulesets.Mania.Skinning.Legacy;
|
||||||
|
using osu.Game.Rulesets.Mania.UI;
|
||||||
|
using osu.Game.Rulesets.Mods;
|
||||||
|
using osu.Game.Rulesets.Replays.Types;
|
||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Rulesets.UI;
|
||||||
using osu.Game.Scoring;
|
using osu.Game.Scoring;
|
||||||
using osu.Game.Screens.Edit.Setup;
|
using osu.Game.Screens.Edit.Setup;
|
||||||
using osu.Game.Screens.Ranking.Statistics;
|
using osu.Game.Screens.Ranking.Statistics;
|
||||||
|
using osu.Game.Skinning;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania
|
namespace osu.Game.Rulesets.Mania
|
||||||
{
|
{
|
||||||
@ -59,6 +60,8 @@ namespace osu.Game.Rulesets.Mania
|
|||||||
|
|
||||||
public const string SHORT_NAME = "mania";
|
public const string SHORT_NAME = "mania";
|
||||||
|
|
||||||
|
public override string RulesetAPIVersionSupported => CURRENT_RULESET_API_VERSION;
|
||||||
|
|
||||||
public override HitObjectComposer CreateHitObjectComposer() => new ManiaHitObjectComposer(this);
|
public override HitObjectComposer CreateHitObjectComposer() => new ManiaHitObjectComposer(this);
|
||||||
|
|
||||||
public override ISkin CreateLegacySkinProvider(ISkin skin, IBeatmap beatmap) => new ManiaLegacySkinTransformer(skin, beatmap);
|
public override ISkin CreateLegacySkinProvider(ISkin skin, IBeatmap beatmap) => new ManiaLegacySkinTransformer(skin, beatmap);
|
||||||
@ -311,7 +314,7 @@ namespace osu.Game.Rulesets.Mania
|
|||||||
return Array.Empty<KeyBinding>();
|
return Array.Empty<KeyBinding>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string GetVariantName(int variant)
|
public override LocalisableString GetVariantName(int variant)
|
||||||
{
|
{
|
||||||
switch (getPlayfieldType(variant))
|
switch (getPlayfieldType(variant))
|
||||||
{
|
{
|
||||||
@ -356,7 +359,7 @@ namespace osu.Game.Rulesets.Mania
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string GetDisplayNameForHitResult(HitResult result)
|
public override LocalisableString GetDisplayNameForHitResult(HitResult result)
|
||||||
{
|
{
|
||||||
switch (result)
|
switch (result)
|
||||||
{
|
{
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
using osu.Framework.Localisation;
|
||||||
using osu.Game.Configuration;
|
using osu.Game.Configuration;
|
||||||
using osu.Game.Rulesets.Mania.Objects;
|
using osu.Game.Rulesets.Mania.Objects;
|
||||||
using osu.Game.Rulesets.Mania.UI;
|
using osu.Game.Rulesets.Mania.UI;
|
||||||
@ -16,9 +17,9 @@ namespace osu.Game.Rulesets.Mania.Mods
|
|||||||
|
|
||||||
public override string Acronym => "CS";
|
public override string Acronym => "CS";
|
||||||
|
|
||||||
public override double ScoreMultiplier => 1;
|
public override double ScoreMultiplier => 0.9;
|
||||||
|
|
||||||
public override string Description => "No more tricky speed changes!";
|
public override LocalisableString Description => "No more tricky speed changes!";
|
||||||
|
|
||||||
public override IconUsage? Icon => FontAwesome.Solid.Equals;
|
public override IconUsage? Icon => FontAwesome.Solid.Equals;
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Localisation;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Rulesets.Mania.Beatmaps;
|
using osu.Game.Rulesets.Mania.Beatmaps;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
@ -11,7 +12,7 @@ namespace osu.Game.Rulesets.Mania.Mods
|
|||||||
{
|
{
|
||||||
public override string Name => "Dual Stages";
|
public override string Name => "Dual Stages";
|
||||||
public override string Acronym => "DS";
|
public override string Acronym => "DS";
|
||||||
public override string Description => @"Double the stages, double the fun!";
|
public override LocalisableString Description => @"Double the stages, double the fun!";
|
||||||
public override ModType Type => ModType.Conversion;
|
public override ModType Type => ModType.Conversion;
|
||||||
public override double ScoreMultiplier => 1;
|
public override double ScoreMultiplier => 1;
|
||||||
|
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Localisation;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.Mods
|
namespace osu.Game.Rulesets.Mania.Mods
|
||||||
{
|
{
|
||||||
public class ManiaModEasy : ModEasyWithExtraLives
|
public class ManiaModEasy : ModEasyWithExtraLives
|
||||||
{
|
{
|
||||||
public override string Description => @"More forgiving HP drain, less accuracy required, and three lives!";
|
public override LocalisableString Description => @"More forgiving HP drain, less accuracy required, and three lives!";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using osu.Framework.Localisation;
|
||||||
using osu.Game.Rulesets.Mania.UI;
|
using osu.Game.Rulesets.Mania.UI;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.Mods
|
namespace osu.Game.Rulesets.Mania.Mods
|
||||||
@ -11,7 +12,7 @@ namespace osu.Game.Rulesets.Mania.Mods
|
|||||||
{
|
{
|
||||||
public override string Name => "Fade In";
|
public override string Name => "Fade In";
|
||||||
public override string Acronym => "FI";
|
public override string Acronym => "FI";
|
||||||
public override string Description => @"Keys appear out of nowhere!";
|
public override LocalisableString Description => @"Keys appear out of nowhere!";
|
||||||
public override double ScoreMultiplier => 1;
|
public override double ScoreMultiplier => 1;
|
||||||
|
|
||||||
public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ManiaModHidden)).ToArray();
|
public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ManiaModHidden)).ToArray();
|
||||||
|
@ -3,13 +3,14 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using osu.Framework.Localisation;
|
||||||
using osu.Game.Rulesets.Mania.UI;
|
using osu.Game.Rulesets.Mania.UI;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.Mods
|
namespace osu.Game.Rulesets.Mania.Mods
|
||||||
{
|
{
|
||||||
public class ManiaModHidden : ManiaModPlayfieldCover
|
public class ManiaModHidden : ManiaModPlayfieldCover
|
||||||
{
|
{
|
||||||
public override string Description => @"Keys fade out before you hit them!";
|
public override LocalisableString Description => @"Keys fade out before you hit them!";
|
||||||
public override double ScoreMultiplier => 1;
|
public override double ScoreMultiplier => 1;
|
||||||
|
|
||||||
public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ManiaModFadeIn)).ToArray();
|
public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ManiaModFadeIn)).ToArray();
|
||||||
|
@ -8,6 +8,7 @@ using osu.Game.Rulesets.Mania.Objects;
|
|||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using osu.Framework.Localisation;
|
||||||
using osu.Game.Rulesets.Mania.Beatmaps;
|
using osu.Game.Rulesets.Mania.Beatmaps;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.Mods
|
namespace osu.Game.Rulesets.Mania.Mods
|
||||||
@ -20,7 +21,7 @@ namespace osu.Game.Rulesets.Mania.Mods
|
|||||||
|
|
||||||
public override double ScoreMultiplier => 1;
|
public override double ScoreMultiplier => 1;
|
||||||
|
|
||||||
public override string Description => @"Replaces all hold notes with normal notes.";
|
public override LocalisableString Description => @"Replaces all hold notes with normal notes.";
|
||||||
|
|
||||||
public override IconUsage? Icon => FontAwesome.Solid.DotCircle;
|
public override IconUsage? Icon => FontAwesome.Solid.DotCircle;
|
||||||
|
|
||||||
|
@ -5,6 +5,7 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
using osu.Framework.Localisation;
|
||||||
using osu.Game.Audio;
|
using osu.Game.Audio;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Rulesets.Mania.Beatmaps;
|
using osu.Game.Rulesets.Mania.Beatmaps;
|
||||||
@ -20,7 +21,7 @@ namespace osu.Game.Rulesets.Mania.Mods
|
|||||||
public override string Acronym => "IN";
|
public override string Acronym => "IN";
|
||||||
public override double ScoreMultiplier => 1;
|
public override double ScoreMultiplier => 1;
|
||||||
|
|
||||||
public override string Description => "Hold the keys. To the beat.";
|
public override LocalisableString Description => "Hold the keys. To the beat.";
|
||||||
|
|
||||||
public override IconUsage? Icon => FontAwesome.Solid.YinYang;
|
public override IconUsage? Icon => FontAwesome.Solid.YinYang;
|
||||||
|
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Localisation;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.Mods
|
namespace osu.Game.Rulesets.Mania.Mods
|
||||||
{
|
{
|
||||||
public class ManiaModKey1 : ManiaKeyMod
|
public class ManiaModKey1 : ManiaKeyMod
|
||||||
@ -8,6 +10,6 @@ namespace osu.Game.Rulesets.Mania.Mods
|
|||||||
public override int KeyCount => 1;
|
public override int KeyCount => 1;
|
||||||
public override string Name => "One Key";
|
public override string Name => "One Key";
|
||||||
public override string Acronym => "1K";
|
public override string Acronym => "1K";
|
||||||
public override string Description => @"Play with one key.";
|
public override LocalisableString Description => @"Play with one key.";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Localisation;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.Mods
|
namespace osu.Game.Rulesets.Mania.Mods
|
||||||
{
|
{
|
||||||
public class ManiaModKey10 : ManiaKeyMod
|
public class ManiaModKey10 : ManiaKeyMod
|
||||||
@ -8,6 +10,6 @@ namespace osu.Game.Rulesets.Mania.Mods
|
|||||||
public override int KeyCount => 10;
|
public override int KeyCount => 10;
|
||||||
public override string Name => "Ten Keys";
|
public override string Name => "Ten Keys";
|
||||||
public override string Acronym => "10K";
|
public override string Acronym => "10K";
|
||||||
public override string Description => @"Play with ten keys.";
|
public override LocalisableString Description => @"Play with ten keys.";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Localisation;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.Mods
|
namespace osu.Game.Rulesets.Mania.Mods
|
||||||
{
|
{
|
||||||
public class ManiaModKey2 : ManiaKeyMod
|
public class ManiaModKey2 : ManiaKeyMod
|
||||||
@ -8,6 +10,6 @@ namespace osu.Game.Rulesets.Mania.Mods
|
|||||||
public override int KeyCount => 2;
|
public override int KeyCount => 2;
|
||||||
public override string Name => "Two Keys";
|
public override string Name => "Two Keys";
|
||||||
public override string Acronym => "2K";
|
public override string Acronym => "2K";
|
||||||
public override string Description => @"Play with two keys.";
|
public override LocalisableString Description => @"Play with two keys.";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Localisation;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.Mods
|
namespace osu.Game.Rulesets.Mania.Mods
|
||||||
{
|
{
|
||||||
public class ManiaModKey3 : ManiaKeyMod
|
public class ManiaModKey3 : ManiaKeyMod
|
||||||
@ -8,6 +10,6 @@ namespace osu.Game.Rulesets.Mania.Mods
|
|||||||
public override int KeyCount => 3;
|
public override int KeyCount => 3;
|
||||||
public override string Name => "Three Keys";
|
public override string Name => "Three Keys";
|
||||||
public override string Acronym => "3K";
|
public override string Acronym => "3K";
|
||||||
public override string Description => @"Play with three keys.";
|
public override LocalisableString Description => @"Play with three keys.";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Localisation;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.Mods
|
namespace osu.Game.Rulesets.Mania.Mods
|
||||||
{
|
{
|
||||||
public class ManiaModKey4 : ManiaKeyMod
|
public class ManiaModKey4 : ManiaKeyMod
|
||||||
@ -8,6 +10,6 @@ namespace osu.Game.Rulesets.Mania.Mods
|
|||||||
public override int KeyCount => 4;
|
public override int KeyCount => 4;
|
||||||
public override string Name => "Four Keys";
|
public override string Name => "Four Keys";
|
||||||
public override string Acronym => "4K";
|
public override string Acronym => "4K";
|
||||||
public override string Description => @"Play with four keys.";
|
public override LocalisableString Description => @"Play with four keys.";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Localisation;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.Mods
|
namespace osu.Game.Rulesets.Mania.Mods
|
||||||
{
|
{
|
||||||
public class ManiaModKey5 : ManiaKeyMod
|
public class ManiaModKey5 : ManiaKeyMod
|
||||||
@ -8,6 +10,6 @@ namespace osu.Game.Rulesets.Mania.Mods
|
|||||||
public override int KeyCount => 5;
|
public override int KeyCount => 5;
|
||||||
public override string Name => "Five Keys";
|
public override string Name => "Five Keys";
|
||||||
public override string Acronym => "5K";
|
public override string Acronym => "5K";
|
||||||
public override string Description => @"Play with five keys.";
|
public override LocalisableString Description => @"Play with five keys.";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Localisation;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.Mods
|
namespace osu.Game.Rulesets.Mania.Mods
|
||||||
{
|
{
|
||||||
public class ManiaModKey6 : ManiaKeyMod
|
public class ManiaModKey6 : ManiaKeyMod
|
||||||
@ -8,6 +10,6 @@ namespace osu.Game.Rulesets.Mania.Mods
|
|||||||
public override int KeyCount => 6;
|
public override int KeyCount => 6;
|
||||||
public override string Name => "Six Keys";
|
public override string Name => "Six Keys";
|
||||||
public override string Acronym => "6K";
|
public override string Acronym => "6K";
|
||||||
public override string Description => @"Play with six keys.";
|
public override LocalisableString Description => @"Play with six keys.";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Localisation;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.Mods
|
namespace osu.Game.Rulesets.Mania.Mods
|
||||||
{
|
{
|
||||||
public class ManiaModKey7 : ManiaKeyMod
|
public class ManiaModKey7 : ManiaKeyMod
|
||||||
@ -8,6 +10,6 @@ namespace osu.Game.Rulesets.Mania.Mods
|
|||||||
public override int KeyCount => 7;
|
public override int KeyCount => 7;
|
||||||
public override string Name => "Seven Keys";
|
public override string Name => "Seven Keys";
|
||||||
public override string Acronym => "7K";
|
public override string Acronym => "7K";
|
||||||
public override string Description => @"Play with seven keys.";
|
public override LocalisableString Description => @"Play with seven keys.";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Localisation;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.Mods
|
namespace osu.Game.Rulesets.Mania.Mods
|
||||||
{
|
{
|
||||||
public class ManiaModKey8 : ManiaKeyMod
|
public class ManiaModKey8 : ManiaKeyMod
|
||||||
@ -8,6 +10,6 @@ namespace osu.Game.Rulesets.Mania.Mods
|
|||||||
public override int KeyCount => 8;
|
public override int KeyCount => 8;
|
||||||
public override string Name => "Eight Keys";
|
public override string Name => "Eight Keys";
|
||||||
public override string Acronym => "8K";
|
public override string Acronym => "8K";
|
||||||
public override string Description => @"Play with eight keys.";
|
public override LocalisableString Description => @"Play with eight keys.";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Localisation;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.Mods
|
namespace osu.Game.Rulesets.Mania.Mods
|
||||||
{
|
{
|
||||||
public class ManiaModKey9 : ManiaKeyMod
|
public class ManiaModKey9 : ManiaKeyMod
|
||||||
@ -8,6 +10,6 @@ namespace osu.Game.Rulesets.Mania.Mods
|
|||||||
public override int KeyCount => 9;
|
public override int KeyCount => 9;
|
||||||
public override string Name => "Nine Keys";
|
public override string Name => "Nine Keys";
|
||||||
public override string Acronym => "9K";
|
public override string Acronym => "9K";
|
||||||
public override string Description => @"Play with nine keys.";
|
public override LocalisableString Description => @"Play with nine keys.";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@ using osu.Framework.Extensions.IEnumerableExtensions;
|
|||||||
using osu.Game.Rulesets.Mania.Objects;
|
using osu.Game.Rulesets.Mania.Objects;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using osu.Framework.Localisation;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Rulesets.Mania.Beatmaps;
|
using osu.Game.Rulesets.Mania.Beatmaps;
|
||||||
|
|
||||||
@ -12,7 +13,7 @@ namespace osu.Game.Rulesets.Mania.Mods
|
|||||||
{
|
{
|
||||||
public class ManiaModMirror : ModMirror, IApplicableToBeatmap
|
public class ManiaModMirror : ModMirror, IApplicableToBeatmap
|
||||||
{
|
{
|
||||||
public override string Description => "Notes are flipped horizontally.";
|
public override LocalisableString Description => "Notes are flipped horizontally.";
|
||||||
|
|
||||||
public void ApplyToBeatmap(IBeatmap beatmap)
|
public void ApplyToBeatmap(IBeatmap beatmap)
|
||||||
{
|
{
|
||||||
|
@ -31,7 +31,7 @@ namespace osu.Game.Rulesets.Mania.Mods
|
|||||||
HitObjectContainer hoc = column.HitObjectArea.HitObjectContainer;
|
HitObjectContainer hoc = column.HitObjectArea.HitObjectContainer;
|
||||||
Container hocParent = (Container)hoc.Parent;
|
Container hocParent = (Container)hoc.Parent;
|
||||||
|
|
||||||
hocParent.Remove(hoc);
|
hocParent.Remove(hoc, false);
|
||||||
hocParent.Add(new PlayfieldCoveringWrapper(hoc).With(c =>
|
hocParent.Add(new PlayfieldCoveringWrapper(hoc).With(c =>
|
||||||
{
|
{
|
||||||
c.RelativeSizeAxes = Axes.Both;
|
c.RelativeSizeAxes = Axes.Both;
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||||
|
using osu.Framework.Localisation;
|
||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Rulesets.Mania.Beatmaps;
|
using osu.Game.Rulesets.Mania.Beatmaps;
|
||||||
@ -14,7 +15,7 @@ namespace osu.Game.Rulesets.Mania.Mods
|
|||||||
{
|
{
|
||||||
public class ManiaModRandom : ModRandom, IApplicableToBeatmap
|
public class ManiaModRandom : ModRandom, IApplicableToBeatmap
|
||||||
{
|
{
|
||||||
public override string Description => @"Shuffle around the keys!";
|
public override LocalisableString Description => @"Shuffle around the keys!";
|
||||||
|
|
||||||
public void ApplyToBeatmap(IBeatmap beatmap)
|
public void ApplyToBeatmap(IBeatmap beatmap)
|
||||||
{
|
{
|
||||||
|
@ -155,7 +155,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
lightContainer.FadeOut(120)
|
lightContainer.FadeOut(120)
|
||||||
.OnComplete(d => Column.TopLevelContainer.Remove(d));
|
.OnComplete(d => Column.TopLevelContainer.Remove(d, false));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
299
osu.Game.Rulesets.Osu.Tests/Editor/TestSceneObjectMerging.cs
Normal file
299
osu.Game.Rulesets.Osu.Tests/Editor/TestSceneObjectMerging.cs
Normal file
@ -0,0 +1,299 @@
|
|||||||
|
// 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.Framework.Utils;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
|
using osu.Game.Rulesets.Osu.Edit;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
|
using osuTK;
|
||||||
|
using osuTK.Input;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||||
|
{
|
||||||
|
public class TestSceneObjectMerging : TestSceneOsuEditor
|
||||||
|
{
|
||||||
|
private OsuSelectionHandler selectionHandler => Editor.ChildrenOfType<OsuSelectionHandler>().First();
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestSimpleMerge()
|
||||||
|
{
|
||||||
|
HitCircle? circle1 = null;
|
||||||
|
HitCircle? circle2 = null;
|
||||||
|
|
||||||
|
AddStep("select first two circles", () =>
|
||||||
|
{
|
||||||
|
circle1 = (HitCircle)EditorBeatmap.HitObjects.First(h => h is HitCircle);
|
||||||
|
circle2 = (HitCircle)EditorBeatmap.HitObjects.First(h => h is HitCircle && h != circle1);
|
||||||
|
EditorClock.Seek(circle1.StartTime);
|
||||||
|
EditorBeatmap.SelectedHitObjects.Add(circle1);
|
||||||
|
EditorBeatmap.SelectedHitObjects.Add(circle2);
|
||||||
|
});
|
||||||
|
|
||||||
|
moveMouseToHitObject(1);
|
||||||
|
AddAssert("merge option available", () => selectionHandler.ContextMenuItems.Any(o => o.Text.Value == "Merge selection"));
|
||||||
|
|
||||||
|
mergeSelection();
|
||||||
|
|
||||||
|
AddAssert("slider created", () => circle1 is not null && circle2 is not null && sliderCreatedFor(
|
||||||
|
(pos: circle1.Position, pathType: PathType.Linear),
|
||||||
|
(pos: circle2.Position, pathType: null)));
|
||||||
|
|
||||||
|
AddStep("undo", () => Editor.Undo());
|
||||||
|
AddAssert("merged objects restored", () => circle1 is not null && circle2 is not null && objectsRestored(circle1, circle2));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestMergeCircleSlider()
|
||||||
|
{
|
||||||
|
HitCircle? circle1 = null;
|
||||||
|
Slider? slider = null;
|
||||||
|
HitCircle? circle2 = null;
|
||||||
|
|
||||||
|
AddStep("select a circle, slider, circle", () =>
|
||||||
|
{
|
||||||
|
circle1 = (HitCircle)EditorBeatmap.HitObjects.First(h => h is HitCircle);
|
||||||
|
slider = (Slider)EditorBeatmap.HitObjects.First(h => h is Slider && h.StartTime > circle1.StartTime);
|
||||||
|
circle2 = (HitCircle)EditorBeatmap.HitObjects.First(h => h is HitCircle && h.StartTime > slider.StartTime);
|
||||||
|
EditorClock.Seek(circle1.StartTime);
|
||||||
|
EditorBeatmap.SelectedHitObjects.Add(circle1);
|
||||||
|
EditorBeatmap.SelectedHitObjects.Add(slider);
|
||||||
|
EditorBeatmap.SelectedHitObjects.Add(circle2);
|
||||||
|
});
|
||||||
|
|
||||||
|
mergeSelection();
|
||||||
|
|
||||||
|
AddAssert("slider created", () =>
|
||||||
|
{
|
||||||
|
if (circle1 is null || circle2 is null || slider is null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
var controlPoints = slider.Path.ControlPoints;
|
||||||
|
(Vector2, PathType?)[] args = new (Vector2, PathType?)[controlPoints.Count + 2];
|
||||||
|
args[0] = (circle1.Position, PathType.Linear);
|
||||||
|
|
||||||
|
for (int i = 0; i < controlPoints.Count; i++)
|
||||||
|
{
|
||||||
|
args[i + 1] = (controlPoints[i].Position + slider.Position, i == controlPoints.Count - 1 ? PathType.Linear : controlPoints[i].Type);
|
||||||
|
}
|
||||||
|
|
||||||
|
args[^1] = (circle2.Position, null);
|
||||||
|
return sliderCreatedFor(args);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddAssert("samples exist", sliderSampleExist);
|
||||||
|
|
||||||
|
AddStep("undo", () => Editor.Undo());
|
||||||
|
AddAssert("merged objects restored", () => circle1 is not null && circle2 is not null && slider is not null && objectsRestored(circle1, slider, circle2));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestMergeSliderSlider()
|
||||||
|
{
|
||||||
|
Slider? slider1 = null;
|
||||||
|
SliderPath? slider1Path = null;
|
||||||
|
Slider? slider2 = null;
|
||||||
|
|
||||||
|
AddStep("select two sliders", () =>
|
||||||
|
{
|
||||||
|
slider1 = (Slider)EditorBeatmap.HitObjects.First(h => h is Slider);
|
||||||
|
slider1Path = new SliderPath(slider1.Path.ControlPoints.Select(p => new PathControlPoint(p.Position, p.Type)).ToArray(), slider1.Path.ExpectedDistance.Value);
|
||||||
|
slider2 = (Slider)EditorBeatmap.HitObjects.First(h => h is Slider && h.StartTime > slider1.StartTime);
|
||||||
|
EditorClock.Seek(slider1.StartTime);
|
||||||
|
EditorBeatmap.SelectedHitObjects.Add(slider1);
|
||||||
|
EditorBeatmap.SelectedHitObjects.Add(slider2);
|
||||||
|
});
|
||||||
|
|
||||||
|
mergeSelection();
|
||||||
|
|
||||||
|
AddAssert("slider created", () =>
|
||||||
|
{
|
||||||
|
if (slider1 is null || slider2 is null || slider1Path is null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
var controlPoints1 = slider1Path.ControlPoints;
|
||||||
|
var controlPoints2 = slider2.Path.ControlPoints;
|
||||||
|
(Vector2, PathType?)[] args = new (Vector2, PathType?)[controlPoints1.Count + controlPoints2.Count - 1];
|
||||||
|
|
||||||
|
for (int i = 0; i < controlPoints1.Count - 1; i++)
|
||||||
|
{
|
||||||
|
args[i] = (controlPoints1[i].Position + slider1.Position, controlPoints1[i].Type);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < controlPoints2.Count; i++)
|
||||||
|
{
|
||||||
|
args[i + controlPoints1.Count - 1] = (controlPoints2[i].Position + controlPoints1[^1].Position + slider1.Position, controlPoints2[i].Type);
|
||||||
|
}
|
||||||
|
|
||||||
|
return sliderCreatedFor(args);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddAssert("samples exist", sliderSampleExist);
|
||||||
|
|
||||||
|
AddAssert("merged slider matches first slider", () =>
|
||||||
|
{
|
||||||
|
var mergedSlider = (Slider)EditorBeatmap.SelectedHitObjects.First();
|
||||||
|
return slider1 is not null && mergedSlider.HeadCircle.Samples.SequenceEqual(slider1.HeadCircle.Samples)
|
||||||
|
&& mergedSlider.TailCircle.Samples.SequenceEqual(slider1.TailCircle.Samples)
|
||||||
|
&& mergedSlider.Samples.SequenceEqual(slider1.Samples)
|
||||||
|
&& mergedSlider.SampleControlPoint.IsRedundant(slider1.SampleControlPoint);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddAssert("slider end is at same completion for last slider", () =>
|
||||||
|
{
|
||||||
|
if (slider1Path is null || slider2 is null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
var mergedSlider = (Slider)EditorBeatmap.SelectedHitObjects.First();
|
||||||
|
return Precision.AlmostEquals(mergedSlider.Path.Distance, slider1Path.CalculatedDistance + slider2.Path.Distance);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestNonMerge()
|
||||||
|
{
|
||||||
|
HitCircle? circle1 = null;
|
||||||
|
HitCircle? circle2 = null;
|
||||||
|
Spinner? spinner = null;
|
||||||
|
|
||||||
|
AddStep("select first two circles and spinner", () =>
|
||||||
|
{
|
||||||
|
circle1 = (HitCircle)EditorBeatmap.HitObjects.First(h => h is HitCircle);
|
||||||
|
circle2 = (HitCircle)EditorBeatmap.HitObjects.First(h => h is HitCircle && h != circle1);
|
||||||
|
spinner = (Spinner)EditorBeatmap.HitObjects.First(h => h is Spinner);
|
||||||
|
EditorClock.Seek(spinner.StartTime);
|
||||||
|
EditorBeatmap.SelectedHitObjects.Add(circle1);
|
||||||
|
EditorBeatmap.SelectedHitObjects.Add(circle2);
|
||||||
|
EditorBeatmap.SelectedHitObjects.Add(spinner);
|
||||||
|
});
|
||||||
|
|
||||||
|
mergeSelection();
|
||||||
|
|
||||||
|
AddAssert("slider created", () => circle1 is not null && circle2 is not null && sliderCreatedFor(
|
||||||
|
(pos: circle1.Position, pathType: PathType.Linear),
|
||||||
|
(pos: circle2.Position, pathType: null)));
|
||||||
|
|
||||||
|
AddAssert("samples exist", sliderSampleExist);
|
||||||
|
|
||||||
|
AddAssert("spinner not merged", () => EditorBeatmap.HitObjects.Contains(spinner));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestIllegalMerge()
|
||||||
|
{
|
||||||
|
HitCircle? circle1 = null;
|
||||||
|
HitCircle? circle2 = null;
|
||||||
|
|
||||||
|
AddStep("add two circles on the same position", () =>
|
||||||
|
{
|
||||||
|
circle1 = new HitCircle();
|
||||||
|
circle2 = new HitCircle { Position = circle1.Position + Vector2.UnitX };
|
||||||
|
EditorClock.Seek(0);
|
||||||
|
EditorBeatmap.Add(circle1);
|
||||||
|
EditorBeatmap.Add(circle2);
|
||||||
|
EditorBeatmap.SelectedHitObjects.Add(circle1);
|
||||||
|
EditorBeatmap.SelectedHitObjects.Add(circle2);
|
||||||
|
});
|
||||||
|
|
||||||
|
moveMouseToHitObject(1);
|
||||||
|
AddAssert("merge option not available", () => selectionHandler.ContextMenuItems.Length > 0 && selectionHandler.ContextMenuItems.All(o => o.Text.Value != "Merge selection"));
|
||||||
|
mergeSelection();
|
||||||
|
AddAssert("circles not merged", () => circle1 is not null && circle2 is not null
|
||||||
|
&& EditorBeatmap.HitObjects.Contains(circle1) && EditorBeatmap.HitObjects.Contains(circle2));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestSameStartTimeMerge()
|
||||||
|
{
|
||||||
|
HitCircle? circle1 = null;
|
||||||
|
HitCircle? circle2 = null;
|
||||||
|
|
||||||
|
AddStep("add two circles at the same time", () =>
|
||||||
|
{
|
||||||
|
circle1 = new HitCircle();
|
||||||
|
circle2 = new HitCircle { Position = circle1.Position + 100 * Vector2.UnitX };
|
||||||
|
EditorClock.Seek(0);
|
||||||
|
EditorBeatmap.Add(circle1);
|
||||||
|
EditorBeatmap.Add(circle2);
|
||||||
|
EditorBeatmap.SelectedHitObjects.Add(circle1);
|
||||||
|
EditorBeatmap.SelectedHitObjects.Add(circle2);
|
||||||
|
});
|
||||||
|
|
||||||
|
moveMouseToHitObject(1);
|
||||||
|
AddAssert("merge option available", () => selectionHandler.ContextMenuItems.Any(o => o.Text.Value == "Merge selection"));
|
||||||
|
|
||||||
|
mergeSelection();
|
||||||
|
|
||||||
|
AddAssert("slider created", () => circle1 is not null && circle2 is not null && sliderCreatedFor(
|
||||||
|
(pos: circle1.Position, pathType: PathType.Linear),
|
||||||
|
(pos: circle2.Position, pathType: null)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void mergeSelection()
|
||||||
|
{
|
||||||
|
AddStep("merge selection", () =>
|
||||||
|
{
|
||||||
|
InputManager.PressKey(Key.LControl);
|
||||||
|
InputManager.PressKey(Key.LShift);
|
||||||
|
InputManager.Key(Key.M);
|
||||||
|
InputManager.ReleaseKey(Key.LShift);
|
||||||
|
InputManager.ReleaseKey(Key.LControl);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool sliderCreatedFor(params (Vector2 pos, PathType? pathType)[] expectedControlPoints)
|
||||||
|
{
|
||||||
|
if (EditorBeatmap.SelectedHitObjects.Count != 1)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
var mergedSlider = (Slider)EditorBeatmap.SelectedHitObjects.First();
|
||||||
|
int i = 0;
|
||||||
|
|
||||||
|
foreach ((Vector2 pos, PathType? pathType) in expectedControlPoints)
|
||||||
|
{
|
||||||
|
var controlPoint = mergedSlider.Path.ControlPoints[i++];
|
||||||
|
|
||||||
|
if (!Precision.AlmostEquals(controlPoint.Position + mergedSlider.Position, pos) || controlPoint.Type != pathType)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool objectsRestored(params HitObject[] objects)
|
||||||
|
{
|
||||||
|
foreach (var hitObject in objects)
|
||||||
|
{
|
||||||
|
if (EditorBeatmap.HitObjects.Contains(hitObject))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool sliderSampleExist()
|
||||||
|
{
|
||||||
|
if (EditorBeatmap.SelectedHitObjects.Count != 1)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
var mergedSlider = (Slider)EditorBeatmap.SelectedHitObjects.First();
|
||||||
|
|
||||||
|
return mergedSlider.Samples[0] is not null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void moveMouseToHitObject(int index)
|
||||||
|
{
|
||||||
|
AddStep($"hover mouse over hit object {index}", () =>
|
||||||
|
{
|
||||||
|
if (EditorBeatmap.HitObjects.Count <= index)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Vector2 position = ((OsuHitObject)EditorBeatmap.HitObjects[index]).Position;
|
||||||
|
InputManager.MoveMouseTo(selectionHandler.ToScreenSpace(position));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -59,10 +59,9 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
editorClock = new EditorClock(editorBeatmap);
|
|
||||||
|
|
||||||
base.Content.Children = new Drawable[]
|
base.Content.Children = new Drawable[]
|
||||||
{
|
{
|
||||||
|
editorClock = new EditorClock(editorBeatmap),
|
||||||
snapProvider,
|
snapProvider,
|
||||||
Content
|
Content
|
||||||
};
|
};
|
||||||
|
@ -182,7 +182,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
|||||||
{
|
{
|
||||||
AddStep($"click context menu item \"{contextMenuText}\"", () =>
|
AddStep($"click context menu item \"{contextMenuText}\"", () =>
|
||||||
{
|
{
|
||||||
MenuItem item = visualiser.ContextMenuItems[1].Items.FirstOrDefault(menuItem => menuItem.Text.Value == contextMenuText);
|
MenuItem item = visualiser.ContextMenuItems.FirstOrDefault(menuItem => menuItem.Text.Value == "Curve type")?.Items.FirstOrDefault(menuItem => menuItem.Text.Value == contextMenuText);
|
||||||
|
|
||||||
item?.Action?.Value();
|
item?.Action?.Value();
|
||||||
});
|
});
|
||||||
|
@ -12,6 +12,7 @@ using osu.Game.Beatmaps;
|
|||||||
using osu.Game.Beatmaps.ControlPoints;
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
using osu.Game.Input.Bindings;
|
using osu.Game.Input.Bindings;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
using osu.Game.Rulesets.Osu.Edit;
|
using osu.Game.Rulesets.Osu.Edit;
|
||||||
using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components;
|
using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components;
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
@ -55,9 +56,9 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
|||||||
{
|
{
|
||||||
ControlPoints =
|
ControlPoints =
|
||||||
{
|
{
|
||||||
new PathControlPoint(Vector2.Zero),
|
new PathControlPoint(Vector2.Zero, PathType.PerfectCurve),
|
||||||
new PathControlPoint(OsuPlayfield.BASE_SIZE * 2 / 5),
|
new PathControlPoint(new Vector2(136, 205)),
|
||||||
new PathControlPoint(OsuPlayfield.BASE_SIZE * 3 / 5)
|
new PathControlPoint(new Vector2(-4, 226))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
@ -99,8 +100,8 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
|||||||
AddStep("move mouse to new point location", () =>
|
AddStep("move mouse to new point location", () =>
|
||||||
{
|
{
|
||||||
var firstPiece = this.ChildrenOfType<PathControlPointPiece>().Single(piece => piece.ControlPoint == slider.Path.ControlPoints[0]);
|
var firstPiece = this.ChildrenOfType<PathControlPointPiece>().Single(piece => piece.ControlPoint == slider.Path.ControlPoints[0]);
|
||||||
var secondPiece = this.ChildrenOfType<PathControlPointPiece>().Single(piece => piece.ControlPoint == slider.Path.ControlPoints[1]);
|
var pos = slider.Path.PositionAt(0.25d) + slider.Position;
|
||||||
InputManager.MoveMouseTo((firstPiece.ScreenSpaceDrawQuad.Centre + secondPiece.ScreenSpaceDrawQuad.Centre) / 2);
|
InputManager.MoveMouseTo(firstPiece.Parent.ToScreenSpace(pos));
|
||||||
});
|
});
|
||||||
AddStep("move slider end", () =>
|
AddStep("move slider end", () =>
|
||||||
{
|
{
|
||||||
@ -175,6 +176,23 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
|||||||
assertSliderSnapped(false);
|
assertSliderSnapped(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestRotatingSliderRetainsPerfectControlPointType()
|
||||||
|
{
|
||||||
|
OsuSelectionHandler selectionHandler;
|
||||||
|
|
||||||
|
AddAssert("first control point perfect", () => slider.Path.ControlPoints[0].Type == PathType.PerfectCurve);
|
||||||
|
|
||||||
|
AddStep("select slider", () => EditorBeatmap.SelectedHitObjects.Add(slider));
|
||||||
|
AddStep("rotate 90 degrees ccw", () =>
|
||||||
|
{
|
||||||
|
selectionHandler = this.ChildrenOfType<OsuSelectionHandler>().Single();
|
||||||
|
selectionHandler.HandleRotation(-90);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddAssert("first control point still perfect", () => slider.Path.ControlPoints[0].Type == PathType.PerfectCurve);
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestFlippingSliderDoesNotSnap()
|
public void TestFlippingSliderDoesNotSnap()
|
||||||
{
|
{
|
||||||
@ -200,6 +218,23 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
|||||||
assertSliderSnapped(false);
|
assertSliderSnapped(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestFlippingSliderRetainsPerfectControlPointType()
|
||||||
|
{
|
||||||
|
OsuSelectionHandler selectionHandler;
|
||||||
|
|
||||||
|
AddAssert("first control point perfect", () => slider.Path.ControlPoints[0].Type == PathType.PerfectCurve);
|
||||||
|
|
||||||
|
AddStep("select slider", () => EditorBeatmap.SelectedHitObjects.Add(slider));
|
||||||
|
AddStep("flip slider horizontally", () =>
|
||||||
|
{
|
||||||
|
selectionHandler = this.ChildrenOfType<OsuSelectionHandler>().Single();
|
||||||
|
selectionHandler.OnPressed(new KeyBindingPressEvent<GlobalAction>(InputManager.CurrentState, GlobalAction.EditorFlipVertically));
|
||||||
|
});
|
||||||
|
|
||||||
|
AddAssert("first control point still perfect", () => slider.Path.ControlPoints[0].Type == PathType.PerfectCurve);
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestReversingSliderDoesNotSnap()
|
public void TestReversingSliderDoesNotSnap()
|
||||||
{
|
{
|
||||||
|
255
osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSplitting.cs
Normal file
255
osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSplitting.cs
Normal file
@ -0,0 +1,255 @@
|
|||||||
|
// 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.Graphics.UserInterface;
|
||||||
|
using osu.Framework.Testing;
|
||||||
|
using osu.Framework.Utils;
|
||||||
|
using osu.Game.Audio;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
|
using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
|
using osu.Game.Screens.Edit.Compose.Components;
|
||||||
|
using osu.Game.Tests.Beatmaps;
|
||||||
|
using osu.Game.Tests.Visual;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||||
|
{
|
||||||
|
public class TestSceneSliderSplitting : EditorTestScene
|
||||||
|
{
|
||||||
|
protected override Ruleset CreateEditorRuleset() => new OsuRuleset();
|
||||||
|
|
||||||
|
protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new TestBeatmap(ruleset, false);
|
||||||
|
|
||||||
|
private ComposeBlueprintContainer blueprintContainer
|
||||||
|
=> Editor.ChildrenOfType<ComposeBlueprintContainer>().First();
|
||||||
|
|
||||||
|
private Slider? slider;
|
||||||
|
private PathControlPointVisualiser? visualiser;
|
||||||
|
|
||||||
|
private const double split_gap = 100;
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestBasicSplit()
|
||||||
|
{
|
||||||
|
double endTime = 0;
|
||||||
|
|
||||||
|
AddStep("add slider", () =>
|
||||||
|
{
|
||||||
|
slider = new Slider
|
||||||
|
{
|
||||||
|
Position = new Vector2(0, 50),
|
||||||
|
Path = new SliderPath(new[]
|
||||||
|
{
|
||||||
|
new PathControlPoint(Vector2.Zero, PathType.PerfectCurve),
|
||||||
|
new PathControlPoint(new Vector2(150, 150)),
|
||||||
|
new PathControlPoint(new Vector2(300, 0), PathType.PerfectCurve),
|
||||||
|
new PathControlPoint(new Vector2(400, 0)),
|
||||||
|
new PathControlPoint(new Vector2(400, 150))
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
EditorBeatmap.Add(slider);
|
||||||
|
|
||||||
|
endTime = slider.EndTime;
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("select added slider", () =>
|
||||||
|
{
|
||||||
|
EditorBeatmap.SelectedHitObjects.Add(slider);
|
||||||
|
visualiser = blueprintContainer.SelectionBlueprints.First(o => o.Item == slider).ChildrenOfType<PathControlPointVisualiser>().First();
|
||||||
|
});
|
||||||
|
|
||||||
|
moveMouseToControlPoint(2);
|
||||||
|
AddStep("select control point", () =>
|
||||||
|
{
|
||||||
|
if (visualiser is not null) visualiser.Pieces[2].IsSelected.Value = true;
|
||||||
|
});
|
||||||
|
addContextMenuItemStep("Split control point");
|
||||||
|
|
||||||
|
AddAssert("slider split", () => slider is not null && EditorBeatmap.HitObjects.Count == 2 &&
|
||||||
|
sliderCreatedFor((Slider)EditorBeatmap.HitObjects[0], 0, EditorBeatmap.HitObjects[1].StartTime - split_gap,
|
||||||
|
(new Vector2(0, 50), PathType.PerfectCurve),
|
||||||
|
(new Vector2(150, 200), null),
|
||||||
|
(new Vector2(300, 50), null)
|
||||||
|
) && sliderCreatedFor((Slider)EditorBeatmap.HitObjects[1], slider.StartTime, endTime + split_gap,
|
||||||
|
(new Vector2(300, 50), PathType.PerfectCurve),
|
||||||
|
(new Vector2(400, 50), null),
|
||||||
|
(new Vector2(400, 200), null)
|
||||||
|
));
|
||||||
|
|
||||||
|
AddStep("undo", () => Editor.Undo());
|
||||||
|
AddAssert("original slider restored", () => EditorBeatmap.HitObjects.Count == 1 && sliderCreatedFor((Slider)EditorBeatmap.HitObjects[0], 0, endTime,
|
||||||
|
(new Vector2(0, 50), PathType.PerfectCurve),
|
||||||
|
(new Vector2(150, 200), null),
|
||||||
|
(new Vector2(300, 50), PathType.PerfectCurve),
|
||||||
|
(new Vector2(400, 50), null),
|
||||||
|
(new Vector2(400, 200), null)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestDoubleSplit()
|
||||||
|
{
|
||||||
|
double endTime = 0;
|
||||||
|
|
||||||
|
AddStep("add slider", () =>
|
||||||
|
{
|
||||||
|
slider = new Slider
|
||||||
|
{
|
||||||
|
Position = new Vector2(0, 50),
|
||||||
|
Path = new SliderPath(new[]
|
||||||
|
{
|
||||||
|
new PathControlPoint(Vector2.Zero, PathType.PerfectCurve),
|
||||||
|
new PathControlPoint(new Vector2(150, 150)),
|
||||||
|
new PathControlPoint(new Vector2(300, 0), PathType.Bezier),
|
||||||
|
new PathControlPoint(new Vector2(400, 0)),
|
||||||
|
new PathControlPoint(new Vector2(400, 150), PathType.Catmull),
|
||||||
|
new PathControlPoint(new Vector2(300, 200)),
|
||||||
|
new PathControlPoint(new Vector2(400, 250))
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
EditorBeatmap.Add(slider);
|
||||||
|
|
||||||
|
endTime = slider.EndTime;
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("select added slider", () =>
|
||||||
|
{
|
||||||
|
EditorBeatmap.SelectedHitObjects.Add(slider);
|
||||||
|
visualiser = blueprintContainer.SelectionBlueprints.First(o => o.Item == slider).ChildrenOfType<PathControlPointVisualiser>().First();
|
||||||
|
});
|
||||||
|
|
||||||
|
moveMouseToControlPoint(2);
|
||||||
|
AddStep("select first control point", () =>
|
||||||
|
{
|
||||||
|
if (visualiser is not null) visualiser.Pieces[2].IsSelected.Value = true;
|
||||||
|
});
|
||||||
|
moveMouseToControlPoint(4);
|
||||||
|
AddStep("select second control point", () =>
|
||||||
|
{
|
||||||
|
if (visualiser is not null) visualiser.Pieces[4].IsSelected.Value = true;
|
||||||
|
});
|
||||||
|
addContextMenuItemStep("Split 2 control points");
|
||||||
|
|
||||||
|
AddAssert("slider split", () => slider is not null && EditorBeatmap.HitObjects.Count == 3 &&
|
||||||
|
sliderCreatedFor((Slider)EditorBeatmap.HitObjects[0], 0, EditorBeatmap.HitObjects[1].StartTime - split_gap,
|
||||||
|
(new Vector2(0, 50), PathType.PerfectCurve),
|
||||||
|
(new Vector2(150, 200), null),
|
||||||
|
(new Vector2(300, 50), null)
|
||||||
|
) && sliderCreatedFor((Slider)EditorBeatmap.HitObjects[1], EditorBeatmap.HitObjects[0].GetEndTime() + split_gap, slider.StartTime - split_gap,
|
||||||
|
(new Vector2(300, 50), PathType.Bezier),
|
||||||
|
(new Vector2(400, 50), null),
|
||||||
|
(new Vector2(400, 200), null)
|
||||||
|
) && sliderCreatedFor((Slider)EditorBeatmap.HitObjects[2], EditorBeatmap.HitObjects[1].GetEndTime() + split_gap, endTime + split_gap * 2,
|
||||||
|
(new Vector2(400, 200), PathType.Catmull),
|
||||||
|
(new Vector2(300, 250), null),
|
||||||
|
(new Vector2(400, 300), null)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestSplitRetainsHitsounds()
|
||||||
|
{
|
||||||
|
HitSampleInfo? sample = null;
|
||||||
|
|
||||||
|
AddStep("add slider", () =>
|
||||||
|
{
|
||||||
|
slider = new Slider
|
||||||
|
{
|
||||||
|
Position = new Vector2(0, 50),
|
||||||
|
LegacyLastTickOffset = 36, // This is necessary for undo to retain the sample control point
|
||||||
|
Path = new SliderPath(new[]
|
||||||
|
{
|
||||||
|
new PathControlPoint(Vector2.Zero, PathType.PerfectCurve),
|
||||||
|
new PathControlPoint(new Vector2(150, 150)),
|
||||||
|
new PathControlPoint(new Vector2(300, 0), PathType.PerfectCurve),
|
||||||
|
new PathControlPoint(new Vector2(400, 0)),
|
||||||
|
new PathControlPoint(new Vector2(400, 150))
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
EditorBeatmap.Add(slider);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("add hitsounds", () =>
|
||||||
|
{
|
||||||
|
if (slider is null) return;
|
||||||
|
|
||||||
|
slider.SampleControlPoint.SampleBank = "soft";
|
||||||
|
slider.SampleControlPoint.SampleVolume = 70;
|
||||||
|
sample = new HitSampleInfo("hitwhistle");
|
||||||
|
slider.Samples.Add(sample);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("select added slider", () =>
|
||||||
|
{
|
||||||
|
EditorBeatmap.SelectedHitObjects.Add(slider);
|
||||||
|
visualiser = blueprintContainer.SelectionBlueprints.First(o => o.Item == slider).ChildrenOfType<PathControlPointVisualiser>().First();
|
||||||
|
});
|
||||||
|
|
||||||
|
moveMouseToControlPoint(2);
|
||||||
|
AddStep("select control point", () =>
|
||||||
|
{
|
||||||
|
if (visualiser is not null) visualiser.Pieces[2].IsSelected.Value = true;
|
||||||
|
});
|
||||||
|
addContextMenuItemStep("Split control point");
|
||||||
|
AddAssert("sliders have hitsounds", hasHitsounds);
|
||||||
|
|
||||||
|
AddStep("select first slider", () => EditorBeatmap.SelectedHitObjects.Add(EditorBeatmap.HitObjects[0]));
|
||||||
|
AddStep("remove first slider", () => EditorBeatmap.RemoveAt(0));
|
||||||
|
AddStep("undo", () => Editor.Undo());
|
||||||
|
AddAssert("sliders have hitsounds", hasHitsounds);
|
||||||
|
|
||||||
|
bool hasHitsounds() => sample is not null &&
|
||||||
|
EditorBeatmap.HitObjects.All(o => o.SampleControlPoint.SampleBank == "soft" &&
|
||||||
|
o.SampleControlPoint.SampleVolume == 70 &&
|
||||||
|
o.Samples.Contains(sample));
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool sliderCreatedFor(Slider s, double startTime, double endTime, params (Vector2 pos, PathType? pathType)[] expectedControlPoints)
|
||||||
|
{
|
||||||
|
if (!Precision.AlmostEquals(s.StartTime, startTime, 1) || !Precision.AlmostEquals(s.EndTime, endTime, 1)) return false;
|
||||||
|
|
||||||
|
int i = 0;
|
||||||
|
|
||||||
|
foreach ((Vector2 pos, PathType? pathType) in expectedControlPoints)
|
||||||
|
{
|
||||||
|
var controlPoint = s.Path.ControlPoints[i++];
|
||||||
|
|
||||||
|
if (!Precision.AlmostEquals(controlPoint.Position + s.Position, pos) || controlPoint.Type != pathType)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void moveMouseToControlPoint(int index)
|
||||||
|
{
|
||||||
|
AddStep($"move mouse to control point {index}", () =>
|
||||||
|
{
|
||||||
|
if (slider is null || visualiser is null) return;
|
||||||
|
|
||||||
|
Vector2 position = slider.Path.ControlPoints[index].Position + slider.Position;
|
||||||
|
InputManager.MoveMouseTo(visualiser.Pieces[0].Parent.ToScreenSpace(position));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addContextMenuItemStep(string contextMenuText)
|
||||||
|
{
|
||||||
|
AddStep($"click context menu item \"{contextMenuText}\"", () =>
|
||||||
|
{
|
||||||
|
if (visualiser is null) return;
|
||||||
|
|
||||||
|
MenuItem? item = visualiser.ContextMenuItems.FirstOrDefault(menuItem => menuItem.Text.Value == contextMenuText);
|
||||||
|
|
||||||
|
item?.Action?.Value();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods
|
|||||||
{
|
{
|
||||||
Mod = mod,
|
Mod = mod,
|
||||||
PassCondition = () => Player.ScoreProcessor.JudgedHits >= 2 &&
|
PassCondition = () => Player.ScoreProcessor.JudgedHits >= 2 &&
|
||||||
Precision.AlmostEquals(Player.GameplayClockContainer.GameplayClock.Rate, mod.SpeedChange.Value)
|
Precision.AlmostEquals(Player.GameplayClockContainer.Rate, mod.SpeedChange.Value)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -195,7 +195,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
{
|
{
|
||||||
var drawableObject = getFunc.Invoke();
|
var drawableObject = getFunc.Invoke();
|
||||||
|
|
||||||
hitObjectContainer.Remove(drawableObject);
|
hitObjectContainer.Remove(drawableObject, false);
|
||||||
followPointRenderer.RemoveFollowPoints(drawableObject.HitObject);
|
followPointRenderer.RemoveFollowPoints(drawableObject.HitObject);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -212,7 +212,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
else
|
else
|
||||||
targetTime = getObject(hitObjectContainer.Count - 1).HitObject.StartTime + 1;
|
targetTime = getObject(hitObjectContainer.Count - 1).HitObject.StartTime + 1;
|
||||||
|
|
||||||
hitObjectContainer.Remove(toReorder);
|
hitObjectContainer.Remove(toReorder, false);
|
||||||
toReorder.HitObject.StartTime = targetTime;
|
toReorder.HitObject.StartTime = targetTime;
|
||||||
hitObjectContainer.Add(toReorder);
|
hitObjectContainer.Add(toReorder);
|
||||||
});
|
});
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
@ -12,7 +10,6 @@ using osu.Framework.Audio;
|
|||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
using osu.Framework.Timing;
|
using osu.Framework.Timing;
|
||||||
using osu.Framework.Utils;
|
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Replays;
|
using osu.Game.Replays;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
@ -36,16 +33,16 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
private const double spinner_duration = 6000;
|
private const double spinner_duration = 6000;
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private AudioManager audioManager { get; set; }
|
private AudioManager audioManager { get; set; } = null!;
|
||||||
|
|
||||||
protected override bool Autoplay => true;
|
protected override bool Autoplay => true;
|
||||||
|
|
||||||
protected override TestPlayer CreatePlayer(Ruleset ruleset) => new ScoreExposedPlayer();
|
protected override TestPlayer CreatePlayer(Ruleset ruleset) => new ScoreExposedPlayer();
|
||||||
|
|
||||||
protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null)
|
protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard? storyboard = null)
|
||||||
=> new ClockBackedTestWorkingBeatmap(beatmap, storyboard, new FramedClock(new ManualClock { Rate = 1 }), audioManager);
|
=> new ClockBackedTestWorkingBeatmap(beatmap, storyboard, new FramedClock(new ManualClock { Rate = 1 }), audioManager);
|
||||||
|
|
||||||
private DrawableSpinner drawableSpinner;
|
private DrawableSpinner drawableSpinner = null!;
|
||||||
private SpriteIcon spinnerSymbol => drawableSpinner.ChildrenOfType<SpriteIcon>().Single();
|
private SpriteIcon spinnerSymbol => drawableSpinner.ChildrenOfType<SpriteIcon>().Single();
|
||||||
|
|
||||||
[SetUpSteps]
|
[SetUpSteps]
|
||||||
@ -67,12 +64,12 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
{
|
{
|
||||||
trackerRotationTolerance = Math.Abs(drawableSpinner.RotationTracker.Rotation * 0.1f);
|
trackerRotationTolerance = Math.Abs(drawableSpinner.RotationTracker.Rotation * 0.1f);
|
||||||
});
|
});
|
||||||
AddAssert("is disc rotation not almost 0", () => !Precision.AlmostEquals(drawableSpinner.RotationTracker.Rotation, 0, 100));
|
AddAssert("is disc rotation not almost 0", () => drawableSpinner.RotationTracker.Rotation, () => Is.Not.EqualTo(0).Within(100));
|
||||||
AddAssert("is disc rotation absolute not almost 0", () => !Precision.AlmostEquals(drawableSpinner.Result.RateAdjustedRotation, 0, 100));
|
AddAssert("is disc rotation absolute not almost 0", () => drawableSpinner.Result.RateAdjustedRotation, () => Is.Not.EqualTo(0).Within(100));
|
||||||
|
|
||||||
addSeekStep(0);
|
addSeekStep(0);
|
||||||
AddAssert("is disc rotation almost 0", () => Precision.AlmostEquals(drawableSpinner.RotationTracker.Rotation, 0, trackerRotationTolerance));
|
AddAssert("is disc rotation almost 0", () => drawableSpinner.RotationTracker.Rotation, () => Is.EqualTo(0).Within(trackerRotationTolerance));
|
||||||
AddAssert("is disc rotation absolute almost 0", () => Precision.AlmostEquals(drawableSpinner.Result.RateAdjustedRotation, 0, 100));
|
AddAssert("is disc rotation absolute almost 0", () => drawableSpinner.Result.RateAdjustedRotation, () => Is.EqualTo(0).Within(100));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -100,20 +97,20 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
// we want to make sure that the rotation at time 2500 is in the same direction as at time 5000, but about half-way in.
|
// we want to make sure that the rotation at time 2500 is in the same direction as at time 5000, but about half-way in.
|
||||||
// due to the exponential damping applied we're allowing a larger margin of error of about 10%
|
// due to the exponential damping applied we're allowing a larger margin of error of about 10%
|
||||||
// (5% relative to the final rotation value, but we're half-way through the spin).
|
// (5% relative to the final rotation value, but we're half-way through the spin).
|
||||||
() => Precision.AlmostEquals(drawableSpinner.RotationTracker.Rotation, finalTrackerRotation / 2, trackerRotationTolerance));
|
() => drawableSpinner.RotationTracker.Rotation, () => Is.EqualTo(finalTrackerRotation / 2).Within(trackerRotationTolerance));
|
||||||
AddAssert("symbol rotation rewound",
|
AddAssert("symbol rotation rewound",
|
||||||
() => Precision.AlmostEquals(spinnerSymbol.Rotation, finalSpinnerSymbolRotation / 2, spinnerSymbolRotationTolerance));
|
() => spinnerSymbol.Rotation, () => Is.EqualTo(finalSpinnerSymbolRotation / 2).Within(spinnerSymbolRotationTolerance));
|
||||||
AddAssert("is cumulative rotation rewound",
|
AddAssert("is cumulative rotation rewound",
|
||||||
// cumulative rotation is not damped, so we're treating it as the "ground truth" and allowing a comparatively smaller margin of error.
|
// cumulative rotation is not damped, so we're treating it as the "ground truth" and allowing a comparatively smaller margin of error.
|
||||||
() => Precision.AlmostEquals(drawableSpinner.Result.RateAdjustedRotation, finalCumulativeTrackerRotation / 2, 100));
|
() => drawableSpinner.Result.RateAdjustedRotation, () => Is.EqualTo(finalCumulativeTrackerRotation / 2).Within(100));
|
||||||
|
|
||||||
addSeekStep(spinner_start_time + 5000);
|
addSeekStep(spinner_start_time + 5000);
|
||||||
AddAssert("is disc rotation almost same",
|
AddAssert("is disc rotation almost same",
|
||||||
() => Precision.AlmostEquals(drawableSpinner.RotationTracker.Rotation, finalTrackerRotation, trackerRotationTolerance));
|
() => drawableSpinner.RotationTracker.Rotation, () => Is.EqualTo(finalTrackerRotation).Within(trackerRotationTolerance));
|
||||||
AddAssert("is symbol rotation almost same",
|
AddAssert("is symbol rotation almost same",
|
||||||
() => Precision.AlmostEquals(spinnerSymbol.Rotation, finalSpinnerSymbolRotation, spinnerSymbolRotationTolerance));
|
() => spinnerSymbol.Rotation, () => Is.EqualTo(finalSpinnerSymbolRotation).Within(spinnerSymbolRotationTolerance));
|
||||||
AddAssert("is cumulative rotation almost same",
|
AddAssert("is cumulative rotation almost same",
|
||||||
() => Precision.AlmostEquals(drawableSpinner.Result.RateAdjustedRotation, finalCumulativeTrackerRotation, 100));
|
() => drawableSpinner.Result.RateAdjustedRotation, () => Is.EqualTo(finalCumulativeTrackerRotation).Within(100));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -177,10 +174,10 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
AddStep("retrieve spm", () => estimatedSpm = drawableSpinner.SpinsPerMinute.Value);
|
AddStep("retrieve spm", () => estimatedSpm = drawableSpinner.SpinsPerMinute.Value);
|
||||||
|
|
||||||
addSeekStep(2000);
|
addSeekStep(2000);
|
||||||
AddAssert("spm still valid", () => Precision.AlmostEquals(drawableSpinner.SpinsPerMinute.Value, estimatedSpm, 1.0));
|
AddAssert("spm still valid", () => drawableSpinner.SpinsPerMinute.Value, () => Is.EqualTo(estimatedSpm).Within(1.0));
|
||||||
|
|
||||||
addSeekStep(1000);
|
addSeekStep(1000);
|
||||||
AddAssert("spm still valid", () => Precision.AlmostEquals(drawableSpinner.SpinsPerMinute.Value, estimatedSpm, 1.0));
|
AddAssert("spm still valid", () => drawableSpinner.SpinsPerMinute.Value, () => Is.EqualTo(estimatedSpm).Within(1.0));
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestCase(0.5)]
|
[TestCase(0.5)]
|
||||||
@ -202,14 +199,14 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
AddStep("adjust track rate", () => ((MasterGameplayClockContainer)Player.GameplayClockContainer).UserPlaybackRate.Value = rate);
|
AddStep("adjust track rate", () => ((MasterGameplayClockContainer)Player.GameplayClockContainer).UserPlaybackRate.Value = rate);
|
||||||
|
|
||||||
addSeekStep(1000);
|
addSeekStep(1000);
|
||||||
AddAssert("progress almost same", () => Precision.AlmostEquals(expectedProgress, drawableSpinner.Progress, 0.05));
|
AddAssert("progress almost same", () => drawableSpinner.Progress, () => Is.EqualTo(expectedProgress).Within(0.05));
|
||||||
AddAssert("spm almost same", () => Precision.AlmostEquals(expectedSpm, drawableSpinner.SpinsPerMinute.Value, 2.0));
|
AddAssert("spm almost same", () => drawableSpinner.SpinsPerMinute.Value, () => Is.EqualTo(expectedSpm).Within(2.0));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addSeekStep(double time)
|
private void addSeekStep(double time)
|
||||||
{
|
{
|
||||||
AddStep($"seek to {time}", () => Player.GameplayClockContainer.Seek(time));
|
AddStep($"seek to {time}", () => Player.GameplayClockContainer.Seek(time));
|
||||||
AddUntilStep("wait for seek to finish", () => Precision.AlmostEquals(time, Player.DrawableRuleset.FrameStableClock.CurrentTime, 100));
|
AddUntilStep("wait for seek to finish", () => Player.DrawableRuleset.FrameStableClock.CurrentTime, () => Is.EqualTo(time).Within(100));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void transformReplay(Func<Replay, Replay> replayTransformation) => AddStep("set replay", () =>
|
private void transformReplay(Func<Replay, Replay> replayTransformation) => AddStep("set replay", () =>
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
<Import Project="..\osu.TestProject.props" />
|
<Import Project="..\osu.TestProject.props" />
|
||||||
<ItemGroup Label="Package References">
|
<ItemGroup Label="Package References">
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
|
||||||
<PackageReference Include="Moq" Version="4.18.1" />
|
<PackageReference Include="Moq" Version="4.18.2" />
|
||||||
<PackageReference Include="NUnit" Version="3.13.3" />
|
<PackageReference Include="NUnit" Version="3.13.3" />
|
||||||
<PackageReference Include="NUnit3TestAdapter" Version="4.1.0" />
|
<PackageReference Include="NUnit3TestAdapter" Version="4.1.0" />
|
||||||
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
|
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
|
||||||
|
@ -18,11 +18,14 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
|
|||||||
private const double min_velocity = 0.5;
|
private const double min_velocity = 0.5;
|
||||||
private const double slider_multiplier = 1.3;
|
private const double slider_multiplier = 1.3;
|
||||||
|
|
||||||
|
private const double min_angle_multiplier = 0.2;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Evaluates the difficulty of memorising and hitting an object, based on:
|
/// Evaluates the difficulty of memorising and hitting an object, based on:
|
||||||
/// <list type="bullet">
|
/// <list type="bullet">
|
||||||
/// <item><description>distance between a number of previous objects and the current object,</description></item>
|
/// <item><description>distance between a number of previous objects and the current object,</description></item>
|
||||||
/// <item><description>the visual opacity of the current object,</description></item>
|
/// <item><description>the visual opacity of the current object,</description></item>
|
||||||
|
/// <item><description>the angle made by the current object,</description></item>
|
||||||
/// <item><description>length and speed of the current object (for sliders),</description></item>
|
/// <item><description>length and speed of the current object (for sliders),</description></item>
|
||||||
/// <item><description>and whether the hidden mod is enabled.</description></item>
|
/// <item><description>and whether the hidden mod is enabled.</description></item>
|
||||||
/// </list>
|
/// </list>
|
||||||
@ -43,6 +46,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
|
|||||||
|
|
||||||
OsuDifficultyHitObject lastObj = osuCurrent;
|
OsuDifficultyHitObject lastObj = osuCurrent;
|
||||||
|
|
||||||
|
double angleRepeatCount = 0.0;
|
||||||
|
|
||||||
// This is iterating backwards in time from the current object.
|
// This is iterating backwards in time from the current object.
|
||||||
for (int i = 0; i < Math.Min(current.Index, 10); i++)
|
for (int i = 0; i < Math.Min(current.Index, 10); i++)
|
||||||
{
|
{
|
||||||
@ -66,6 +71,13 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
|
|||||||
double opacityBonus = 1.0 + max_opacity_bonus * (1.0 - osuCurrent.OpacityAt(currentHitObject.StartTime, hidden));
|
double opacityBonus = 1.0 + max_opacity_bonus * (1.0 - osuCurrent.OpacityAt(currentHitObject.StartTime, hidden));
|
||||||
|
|
||||||
result += stackNerf * opacityBonus * scalingFactor * jumpDistance / cumulativeStrainTime;
|
result += stackNerf * opacityBonus * scalingFactor * jumpDistance / cumulativeStrainTime;
|
||||||
|
|
||||||
|
if (currentObj.Angle != null && osuCurrent.Angle != null)
|
||||||
|
{
|
||||||
|
// Objects further back in time should count less for the nerf.
|
||||||
|
if (Math.Abs(currentObj.Angle.Value - osuCurrent.Angle.Value) < 0.02)
|
||||||
|
angleRepeatCount += Math.Max(1.0 - 0.1 * i, 0.0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
lastObj = currentObj;
|
lastObj = currentObj;
|
||||||
@ -77,6 +89,9 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
|
|||||||
if (hidden)
|
if (hidden)
|
||||||
result *= 1.0 + hidden_bonus;
|
result *= 1.0 + hidden_bonus;
|
||||||
|
|
||||||
|
// Nerf patterns with repeated angles.
|
||||||
|
result *= min_angle_multiplier + (1.0 - min_angle_multiplier) / (angleRepeatCount + 1.0);
|
||||||
|
|
||||||
double sliderBonus = 0.0;
|
double sliderBonus = 0.0;
|
||||||
|
|
||||||
if (osuCurrent.BaseObject is Slider osuSlider)
|
if (osuCurrent.BaseObject is Slider osuSlider)
|
||||||
|
@ -25,7 +25,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
|||||||
private const double difficulty_multiplier = 0.0675;
|
private const double difficulty_multiplier = 0.0675;
|
||||||
private double hitWindowGreat;
|
private double hitWindowGreat;
|
||||||
|
|
||||||
public override int Version => 20220701;
|
public override int Version => 20220902;
|
||||||
|
|
||||||
public OsuDifficultyCalculator(IRulesetInfo ruleset, IWorkingBeatmap beatmap)
|
public OsuDifficultyCalculator(IRulesetInfo ruleset, IWorkingBeatmap beatmap)
|
||||||
: base(ruleset, beatmap)
|
: base(ruleset, beatmap)
|
||||||
|
@ -267,8 +267,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
|||||||
comboBasedMissCount = fullComboThreshold / Math.Max(1.0, scoreMaxCombo);
|
comboBasedMissCount = fullComboThreshold / Math.Max(1.0, scoreMaxCombo);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clamp miss count since it's derived from combo and can be higher than total hits and that breaks some calculations
|
// Clamp miss count to maximum amount of possible breaks
|
||||||
comboBasedMissCount = Math.Min(comboBasedMissCount, totalHits);
|
comboBasedMissCount = Math.Min(comboBasedMissCount, countOk + countMeh + countMiss);
|
||||||
|
|
||||||
return Math.Max(countMiss, comboBasedMissCount);
|
return Math.Max(countMiss, comboBasedMissCount);
|
||||||
}
|
}
|
||||||
|
@ -26,7 +26,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
|
|||||||
hasHiddenMod = mods.Any(m => m is OsuModHidden);
|
hasHiddenMod = mods.Any(m => m is OsuModHidden);
|
||||||
}
|
}
|
||||||
|
|
||||||
private double skillMultiplier => 0.05;
|
private double skillMultiplier => 0.052;
|
||||||
private double strainDecayBase => 0.15;
|
private double strainDecayBase => 0.15;
|
||||||
|
|
||||||
private double currentStrain;
|
private double currentStrain;
|
||||||
|
@ -64,7 +64,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
|
|||||||
if (maxStrain == 0)
|
if (maxStrain == 0)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
return objectStrains.Aggregate((total, next) => total + (1.0 / (1.0 + Math.Exp(-(next / maxStrain * 12.0 - 6.0)))));
|
return objectStrains.Sum(strain => 1.0 / (1.0 + Math.Exp(-(strain / maxStrain * 12.0 - 6.0))));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -43,6 +43,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
|||||||
private InputManager inputManager;
|
private InputManager inputManager;
|
||||||
|
|
||||||
public Action<List<PathControlPoint>> RemoveControlPointsRequested;
|
public Action<List<PathControlPoint>> RemoveControlPointsRequested;
|
||||||
|
public Action<List<PathControlPoint>> SplitControlPointsRequested;
|
||||||
|
|
||||||
[Resolved(CanBeNull = true)]
|
[Resolved(CanBeNull = true)]
|
||||||
private IDistanceSnapProvider snapProvider { get; set; }
|
private IDistanceSnapProvider snapProvider { get; set; }
|
||||||
@ -104,6 +105,29 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private bool splitSelected()
|
||||||
|
{
|
||||||
|
List<PathControlPoint> controlPointsToSplitAt = Pieces.Where(p => p.IsSelected.Value && isSplittable(p)).Select(p => p.ControlPoint).ToList();
|
||||||
|
|
||||||
|
// Ensure that there are any points to be split
|
||||||
|
if (controlPointsToSplitAt.Count == 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
changeHandler?.BeginChange();
|
||||||
|
SplitControlPointsRequested?.Invoke(controlPointsToSplitAt);
|
||||||
|
changeHandler?.EndChange();
|
||||||
|
|
||||||
|
// Since pieces are re-used, they will not point to the deleted control points while remaining selected
|
||||||
|
foreach (var piece in Pieces)
|
||||||
|
piece.IsSelected.Value = false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool isSplittable(PathControlPointPiece p) =>
|
||||||
|
// A slider can only be split on control points which connect two different slider segments.
|
||||||
|
p.ControlPoint.Type.HasValue && p != Pieces.FirstOrDefault() && p != Pieces.LastOrDefault();
|
||||||
|
|
||||||
private void onControlPointsChanged(object sender, NotifyCollectionChangedEventArgs e)
|
private void onControlPointsChanged(object sender, NotifyCollectionChangedEventArgs e)
|
||||||
{
|
{
|
||||||
switch (e.Action)
|
switch (e.Action)
|
||||||
@ -142,8 +166,10 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
|||||||
case NotifyCollectionChangedAction.Remove:
|
case NotifyCollectionChangedAction.Remove:
|
||||||
foreach (var point in e.OldItems.Cast<PathControlPoint>())
|
foreach (var point in e.OldItems.Cast<PathControlPoint>())
|
||||||
{
|
{
|
||||||
Pieces.RemoveAll(p => p.ControlPoint == point);
|
foreach (var piece in Pieces.Where(p => p.ControlPoint == point).ToArray())
|
||||||
Connections.RemoveAll(c => c.ControlPoint == point);
|
piece.RemoveAndDisposeImmediately();
|
||||||
|
foreach (var connection in Connections.Where(c => c.ControlPoint == point).ToArray())
|
||||||
|
connection.RemoveAndDisposeImmediately();
|
||||||
}
|
}
|
||||||
|
|
||||||
// If removing before the end of the path,
|
// If removing before the end of the path,
|
||||||
@ -322,25 +348,42 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
|||||||
if (count == 0)
|
if (count == 0)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
List<MenuItem> items = new List<MenuItem>();
|
var splittablePieces = selectedPieces.Where(isSplittable).ToList();
|
||||||
|
int splittableCount = splittablePieces.Count;
|
||||||
|
|
||||||
|
List<MenuItem> curveTypeItems = new List<MenuItem>();
|
||||||
|
|
||||||
if (!selectedPieces.Contains(Pieces[0]))
|
if (!selectedPieces.Contains(Pieces[0]))
|
||||||
items.Add(createMenuItemForPathType(null));
|
curveTypeItems.Add(createMenuItemForPathType(null));
|
||||||
|
|
||||||
// todo: hide/disable items which aren't valid for selected points
|
// todo: hide/disable items which aren't valid for selected points
|
||||||
items.Add(createMenuItemForPathType(PathType.Linear));
|
curveTypeItems.Add(createMenuItemForPathType(PathType.Linear));
|
||||||
items.Add(createMenuItemForPathType(PathType.PerfectCurve));
|
curveTypeItems.Add(createMenuItemForPathType(PathType.PerfectCurve));
|
||||||
items.Add(createMenuItemForPathType(PathType.Bezier));
|
curveTypeItems.Add(createMenuItemForPathType(PathType.Bezier));
|
||||||
items.Add(createMenuItemForPathType(PathType.Catmull));
|
curveTypeItems.Add(createMenuItemForPathType(PathType.Catmull));
|
||||||
|
|
||||||
return new MenuItem[]
|
var menuItems = new List<MenuItem>
|
||||||
{
|
{
|
||||||
new OsuMenuItem($"Delete {"control point".ToQuantity(count, count > 1 ? ShowQuantityAs.Numeric : ShowQuantityAs.None)}", MenuItemType.Destructive, () => DeleteSelected()),
|
|
||||||
new OsuMenuItem("Curve type")
|
new OsuMenuItem("Curve type")
|
||||||
{
|
{
|
||||||
Items = items
|
Items = curveTypeItems
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (splittableCount > 0)
|
||||||
|
{
|
||||||
|
menuItems.Add(new OsuMenuItem($"Split {"control point".ToQuantity(splittableCount, splittableCount > 1 ? ShowQuantityAs.Numeric : ShowQuantityAs.None)}",
|
||||||
|
MenuItemType.Destructive,
|
||||||
|
() => splitSelected()));
|
||||||
|
}
|
||||||
|
|
||||||
|
menuItems.Add(
|
||||||
|
new OsuMenuItem($"Delete {"control point".ToQuantity(count, count > 1 ? ShowQuantityAs.Numeric : ShowQuantityAs.None)}",
|
||||||
|
MenuItemType.Destructive,
|
||||||
|
() => DeleteSelected())
|
||||||
|
);
|
||||||
|
|
||||||
|
return menuItems.ToArray();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -89,6 +89,10 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
|
|
||||||
HitObject.DifficultyControlPoint = nearestDifficultyPoint ?? new DifficultyControlPoint();
|
HitObject.DifficultyControlPoint = nearestDifficultyPoint ?? new DifficultyControlPoint();
|
||||||
HitObject.Position = ToLocalSpace(result.ScreenSpacePosition);
|
HitObject.Position = ToLocalSpace(result.ScreenSpacePosition);
|
||||||
|
|
||||||
|
// Replacing the DifficultyControlPoint above doesn't trigger any kind of invalidation.
|
||||||
|
// Without re-applying defaults, velocity won't be updated.
|
||||||
|
ApplyDefaultsToHitObject();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case SliderPlacementState.Body:
|
case SliderPlacementState.Body:
|
||||||
|
@ -13,6 +13,7 @@ using osu.Framework.Graphics.Primitives;
|
|||||||
using osu.Framework.Graphics.UserInterface;
|
using osu.Framework.Graphics.UserInterface;
|
||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
|
using osu.Game.Audio;
|
||||||
using osu.Game.Beatmaps.ControlPoints;
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
using osu.Game.Graphics.UserInterface;
|
using osu.Game.Graphics.UserInterface;
|
||||||
using osu.Game.Rulesets.Edit;
|
using osu.Game.Rulesets.Edit;
|
||||||
@ -111,7 +112,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
{
|
{
|
||||||
AddInternal(ControlPointVisualiser = new PathControlPointVisualiser(HitObject, true)
|
AddInternal(ControlPointVisualiser = new PathControlPointVisualiser(HitObject, true)
|
||||||
{
|
{
|
||||||
RemoveControlPointsRequested = removeControlPoints
|
RemoveControlPointsRequested = removeControlPoints,
|
||||||
|
SplitControlPointsRequested = splitControlPoints
|
||||||
});
|
});
|
||||||
|
|
||||||
base.OnSelected();
|
base.OnSelected();
|
||||||
@ -249,15 +251,83 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
HitObject.Position += first;
|
HitObject.Position += first;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void splitControlPoints(List<PathControlPoint> controlPointsToSplitAt)
|
||||||
|
{
|
||||||
|
// Arbitrary gap in milliseconds to put between split slider pieces
|
||||||
|
const double split_gap = 100;
|
||||||
|
|
||||||
|
// Ensure that there are any points to be split
|
||||||
|
if (controlPointsToSplitAt.Count == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
editorBeatmap.SelectedHitObjects.Clear();
|
||||||
|
|
||||||
|
foreach (var splitPoint in controlPointsToSplitAt)
|
||||||
|
{
|
||||||
|
if (splitPoint == controlPoints[0] || splitPoint == controlPoints[^1] || splitPoint.Type is null)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// Split off the section of slider before this control point so the remaining control points to split are in the latter part of the slider.
|
||||||
|
int index = controlPoints.IndexOf(splitPoint);
|
||||||
|
|
||||||
|
if (index <= 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// Extract the split portion and remove from the original slider.
|
||||||
|
var splitControlPoints = controlPoints.Take(index + 1).ToList();
|
||||||
|
controlPoints.RemoveRange(0, index);
|
||||||
|
|
||||||
|
// Turn the control points which were split off into a new slider.
|
||||||
|
var samplePoint = (SampleControlPoint)HitObject.SampleControlPoint.DeepClone();
|
||||||
|
var difficultyPoint = (DifficultyControlPoint)HitObject.DifficultyControlPoint.DeepClone();
|
||||||
|
|
||||||
|
var newSlider = new Slider
|
||||||
|
{
|
||||||
|
StartTime = HitObject.StartTime,
|
||||||
|
Position = HitObject.Position + splitControlPoints[0].Position,
|
||||||
|
NewCombo = HitObject.NewCombo,
|
||||||
|
SampleControlPoint = samplePoint,
|
||||||
|
DifficultyControlPoint = difficultyPoint,
|
||||||
|
LegacyLastTickOffset = HitObject.LegacyLastTickOffset,
|
||||||
|
Samples = HitObject.Samples.Select(s => s.With()).ToList(),
|
||||||
|
RepeatCount = HitObject.RepeatCount,
|
||||||
|
NodeSamples = HitObject.NodeSamples.Select(n => (IList<HitSampleInfo>)n.Select(s => s.With()).ToList()).ToList(),
|
||||||
|
Path = new SliderPath(splitControlPoints.Select(o => new PathControlPoint(o.Position - splitControlPoints[0].Position, o == splitControlPoints[^1] ? null : o.Type)).ToArray())
|
||||||
|
};
|
||||||
|
|
||||||
|
// Increase the start time of the slider before adding the new slider so the new slider is immediately inserted at the correct index and internal state remains valid.
|
||||||
|
HitObject.StartTime += split_gap;
|
||||||
|
|
||||||
|
editorBeatmap.Add(newSlider);
|
||||||
|
|
||||||
|
HitObject.NewCombo = false;
|
||||||
|
HitObject.Path.ExpectedDistance.Value -= newSlider.Path.CalculatedDistance;
|
||||||
|
HitObject.StartTime += newSlider.SpanDuration;
|
||||||
|
|
||||||
|
// In case the remainder of the slider has no length left over, give it length anyways so we don't get a 0 length slider.
|
||||||
|
if (HitObject.Path.ExpectedDistance.Value <= Precision.DOUBLE_EPSILON)
|
||||||
|
{
|
||||||
|
HitObject.Path.ExpectedDistance.Value = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Once all required pieces have been split off, the original slider has the final split.
|
||||||
|
// As a final step, we must reset its control points to have an origin of (0,0).
|
||||||
|
Vector2 first = controlPoints[0].Position;
|
||||||
|
foreach (var c in controlPoints)
|
||||||
|
c.Position -= first;
|
||||||
|
HitObject.Position += first;
|
||||||
|
}
|
||||||
|
|
||||||
private void convertToStream()
|
private void convertToStream()
|
||||||
{
|
{
|
||||||
if (editorBeatmap == null || changeHandler == null || beatDivisor == null)
|
if (editorBeatmap == null || beatDivisor == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var timingPoint = editorBeatmap.ControlPointInfo.TimingPointAt(HitObject.StartTime);
|
var timingPoint = editorBeatmap.ControlPointInfo.TimingPointAt(HitObject.StartTime);
|
||||||
double streamSpacing = timingPoint.BeatLength / beatDivisor.Value;
|
double streamSpacing = timingPoint.BeatLength / beatDivisor.Value;
|
||||||
|
|
||||||
changeHandler.BeginChange();
|
changeHandler?.BeginChange();
|
||||||
|
|
||||||
int i = 0;
|
int i = 0;
|
||||||
double time = HitObject.StartTime;
|
double time = HitObject.StartTime;
|
||||||
@ -292,7 +362,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
|
|
||||||
editorBeatmap.Remove(HitObject);
|
editorBeatmap.Remove(HitObject);
|
||||||
|
|
||||||
changeHandler.EndChange();
|
changeHandler?.EndChange();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override MenuItem[] ContextMenuItems => new MenuItem[]
|
public override MenuItem[] ContextMenuItems => new MenuItem[]
|
||||||
|
@ -6,8 +6,11 @@ using System.Linq;
|
|||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Primitives;
|
using osu.Framework.Graphics.Primitives;
|
||||||
|
using osu.Framework.Graphics.UserInterface;
|
||||||
|
using osu.Framework.Input.Events;
|
||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
using osu.Game.Extensions;
|
using osu.Game.Extensions;
|
||||||
|
using osu.Game.Graphics.UserInterface;
|
||||||
using osu.Game.Rulesets.Edit;
|
using osu.Game.Rulesets.Edit;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.Objects.Types;
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
@ -15,6 +18,7 @@ using osu.Game.Rulesets.Osu.Objects;
|
|||||||
using osu.Game.Rulesets.Osu.UI;
|
using osu.Game.Rulesets.Osu.UI;
|
||||||
using osu.Game.Screens.Edit.Compose.Components;
|
using osu.Game.Screens.Edit.Compose.Components;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
using osuTK.Input;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Edit
|
namespace osu.Game.Rulesets.Osu.Edit
|
||||||
{
|
{
|
||||||
@ -53,6 +57,17 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
referencePathTypes = null;
|
referencePathTypes = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override bool OnKeyDown(KeyDownEvent e)
|
||||||
|
{
|
||||||
|
if (e.Key == Key.M && e.ControlPressed && e.ShiftPressed)
|
||||||
|
{
|
||||||
|
mergeSelection();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
public override bool HandleMovement(MoveSelectionEvent<HitObject> moveEvent)
|
public override bool HandleMovement(MoveSelectionEvent<HitObject> moveEvent)
|
||||||
{
|
{
|
||||||
var hitObjects = selectedMovableObjects;
|
var hitObjects = selectedMovableObjects;
|
||||||
@ -112,13 +127,16 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
{
|
{
|
||||||
didFlip = true;
|
didFlip = true;
|
||||||
|
|
||||||
foreach (var point in slider.Path.ControlPoints)
|
var controlPoints = slider.Path.ControlPoints.Select(p =>
|
||||||
{
|
new PathControlPoint(new Vector2(
|
||||||
point.Position = new Vector2(
|
(direction == Direction.Horizontal ? -1 : 1) * p.Position.X,
|
||||||
(direction == Direction.Horizontal ? -1 : 1) * point.Position.X,
|
(direction == Direction.Vertical ? -1 : 1) * p.Position.Y
|
||||||
(direction == Direction.Vertical ? -1 : 1) * point.Position.Y
|
), p.Type)).ToArray();
|
||||||
);
|
|
||||||
}
|
// Importantly, update as a single operation so automatic adjustment of control points to different
|
||||||
|
// curve types does not unexpectedly trigger and change the slider's shape.
|
||||||
|
slider.Path.ControlPoints.Clear();
|
||||||
|
slider.Path.ControlPoints.AddRange(controlPoints);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -168,8 +186,13 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
|
|
||||||
if (h is IHasPath path)
|
if (h is IHasPath path)
|
||||||
{
|
{
|
||||||
foreach (var point in path.Path.ControlPoints)
|
var controlPoints = path.Path.ControlPoints.Select(p =>
|
||||||
point.Position = RotatePointAroundOrigin(point.Position, Vector2.Zero, delta);
|
new PathControlPoint(RotatePointAroundOrigin(p.Position, Vector2.Zero, delta), p.Type)).ToArray();
|
||||||
|
|
||||||
|
// Importantly, update as a single operation so automatic adjustment of control points to different
|
||||||
|
// curve types does not unexpectedly trigger and change the slider's shape.
|
||||||
|
path.Path.ControlPoints.Clear();
|
||||||
|
path.Path.ControlPoints.AddRange(controlPoints);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -320,7 +343,115 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
/// All osu! hitobjects which can be moved/rotated/scaled.
|
/// All osu! hitobjects which can be moved/rotated/scaled.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private OsuHitObject[] selectedMovableObjects => SelectedItems.OfType<OsuHitObject>()
|
private OsuHitObject[] selectedMovableObjects => SelectedItems.OfType<OsuHitObject>()
|
||||||
.Where(h => !(h is Spinner))
|
.Where(h => h is not Spinner)
|
||||||
.ToArray();
|
.ToArray();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// All osu! hitobjects which can be merged.
|
||||||
|
/// </summary>
|
||||||
|
private OsuHitObject[] selectedMergeableObjects => SelectedItems.OfType<OsuHitObject>()
|
||||||
|
.Where(h => h is HitCircle or Slider)
|
||||||
|
.OrderBy(h => h.StartTime)
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
|
private void mergeSelection()
|
||||||
|
{
|
||||||
|
var mergeableObjects = selectedMergeableObjects;
|
||||||
|
|
||||||
|
if (!canMerge(mergeableObjects))
|
||||||
|
return;
|
||||||
|
|
||||||
|
EditorBeatmap.BeginChange();
|
||||||
|
|
||||||
|
// Have an initial slider object.
|
||||||
|
var firstHitObject = mergeableObjects[0];
|
||||||
|
var mergedHitObject = firstHitObject as Slider ?? new Slider
|
||||||
|
{
|
||||||
|
StartTime = firstHitObject.StartTime,
|
||||||
|
Position = firstHitObject.Position,
|
||||||
|
NewCombo = firstHitObject.NewCombo,
|
||||||
|
SampleControlPoint = firstHitObject.SampleControlPoint,
|
||||||
|
Samples = firstHitObject.Samples,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (mergedHitObject.Path.ControlPoints.Count == 0)
|
||||||
|
{
|
||||||
|
mergedHitObject.Path.ControlPoints.Add(new PathControlPoint(Vector2.Zero, PathType.Linear));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Merge all the selected hit objects into one slider path.
|
||||||
|
bool lastCircle = firstHitObject is HitCircle;
|
||||||
|
|
||||||
|
foreach (var selectedMergeableObject in mergeableObjects.Skip(1))
|
||||||
|
{
|
||||||
|
if (selectedMergeableObject is IHasPath hasPath)
|
||||||
|
{
|
||||||
|
var offset = lastCircle ? selectedMergeableObject.Position - mergedHitObject.Position : mergedHitObject.Path.ControlPoints[^1].Position;
|
||||||
|
float distanceToLastControlPoint = Vector2.Distance(mergedHitObject.Path.ControlPoints[^1].Position, offset);
|
||||||
|
|
||||||
|
// Calculate the distance required to travel to the expected distance of the merging slider.
|
||||||
|
mergedHitObject.Path.ExpectedDistance.Value = mergedHitObject.Path.CalculatedDistance + distanceToLastControlPoint + hasPath.Path.Distance;
|
||||||
|
|
||||||
|
// Remove the last control point if it sits exactly on the start of the next control point.
|
||||||
|
if (Precision.AlmostEquals(distanceToLastControlPoint, 0))
|
||||||
|
{
|
||||||
|
mergedHitObject.Path.ControlPoints.RemoveAt(mergedHitObject.Path.ControlPoints.Count - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
mergedHitObject.Path.ControlPoints.AddRange(hasPath.Path.ControlPoints.Select(o => new PathControlPoint(o.Position + offset, o.Type)));
|
||||||
|
lastCircle = false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Turn the last control point into a linear type if this is the first merging circle in a sequence, so the subsequent control points can be inherited path type.
|
||||||
|
if (!lastCircle)
|
||||||
|
{
|
||||||
|
mergedHitObject.Path.ControlPoints.Last().Type = PathType.Linear;
|
||||||
|
}
|
||||||
|
|
||||||
|
mergedHitObject.Path.ControlPoints.Add(new PathControlPoint(selectedMergeableObject.Position - mergedHitObject.Position));
|
||||||
|
mergedHitObject.Path.ExpectedDistance.Value = null;
|
||||||
|
lastCircle = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure only the merged hit object is in the beatmap.
|
||||||
|
if (firstHitObject is Slider)
|
||||||
|
{
|
||||||
|
foreach (var selectedMergeableObject in mergeableObjects.Skip(1))
|
||||||
|
{
|
||||||
|
EditorBeatmap.Remove(selectedMergeableObject);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
foreach (var selectedMergeableObject in mergeableObjects)
|
||||||
|
{
|
||||||
|
EditorBeatmap.Remove(selectedMergeableObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
EditorBeatmap.Add(mergedHitObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure the merged hitobject is selected.
|
||||||
|
SelectedItems.Clear();
|
||||||
|
SelectedItems.Add(mergedHitObject);
|
||||||
|
|
||||||
|
EditorBeatmap.EndChange();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override IEnumerable<MenuItem> GetContextMenuItemsForSelection(IEnumerable<SelectionBlueprint<HitObject>> selection)
|
||||||
|
{
|
||||||
|
foreach (var item in base.GetContextMenuItemsForSelection(selection))
|
||||||
|
yield return item;
|
||||||
|
|
||||||
|
if (canMerge(selectedMergeableObjects))
|
||||||
|
yield return new OsuMenuItem("Merge selection", MenuItemType.Destructive, mergeSelection);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool canMerge(IReadOnlyList<OsuHitObject> objects) =>
|
||||||
|
objects.Count > 1
|
||||||
|
&& (objects.Any(h => h is Slider)
|
||||||
|
|| objects.Zip(objects.Skip(1), (h1, h2) => Precision.DefinitelyBigger(Vector2.DistanceSquared(h1.Position, h2.Position), 1)).Any(x => x));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -49,6 +49,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Setup
|
|||||||
private void updateBeatmap()
|
private void updateBeatmap()
|
||||||
{
|
{
|
||||||
Beatmap.BeatmapInfo.StackLeniency = stackLeniency.Current.Value;
|
Beatmap.BeatmapInfo.StackLeniency = stackLeniency.Current.Value;
|
||||||
|
Beatmap.SaveState();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
using osu.Framework.Localisation;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Mods
|
namespace osu.Game.Rulesets.Osu.Mods
|
||||||
{
|
{
|
||||||
@ -11,7 +12,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
{
|
{
|
||||||
public override string Name => @"Alternate";
|
public override string Name => @"Alternate";
|
||||||
public override string Acronym => @"AL";
|
public override string Acronym => @"AL";
|
||||||
public override string Description => @"Don't use the same key twice in a row!";
|
public override LocalisableString Description => @"Don't use the same key twice in a row!";
|
||||||
public override IconUsage? Icon => FontAwesome.Solid.Keyboard;
|
public override IconUsage? Icon => FontAwesome.Solid.Keyboard;
|
||||||
public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModSingleTap) }).ToArray();
|
public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModSingleTap) }).ToArray();
|
||||||
|
|
||||||
|
@ -5,6 +5,7 @@ using System;
|
|||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
using osu.Framework.Localisation;
|
||||||
using osu.Game.Configuration;
|
using osu.Game.Configuration;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
@ -16,7 +17,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
{
|
{
|
||||||
public override string Name => "Approach Different";
|
public override string Name => "Approach Different";
|
||||||
public override string Acronym => "AD";
|
public override string Acronym => "AD";
|
||||||
public override string Description => "Never trust the approach circles...";
|
public override LocalisableString Description => "Never trust the approach circles...";
|
||||||
public override double ScoreMultiplier => 1;
|
public override double ScoreMultiplier => 1;
|
||||||
public override IconUsage? Icon { get; } = FontAwesome.Regular.Circle;
|
public override IconUsage? Icon { get; } = FontAwesome.Regular.Circle;
|
||||||
|
|
||||||
|
@ -6,6 +6,7 @@ using System.Collections.Generic;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
using osu.Framework.Input.StateChanges;
|
using osu.Framework.Input.StateChanges;
|
||||||
|
using osu.Framework.Localisation;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
@ -20,8 +21,8 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
public override string Acronym => "AP";
|
public override string Acronym => "AP";
|
||||||
public override IconUsage? Icon => OsuIcon.ModAutopilot;
|
public override IconUsage? Icon => OsuIcon.ModAutopilot;
|
||||||
public override ModType Type => ModType.Automation;
|
public override ModType Type => ModType.Automation;
|
||||||
public override string Description => @"Automatic cursor movement - just follow the rhythm.";
|
public override LocalisableString Description => @"Automatic cursor movement - just follow the rhythm.";
|
||||||
public override double ScoreMultiplier => 1;
|
public override double ScoreMultiplier => 0.1;
|
||||||
public override Type[] IncompatibleMods => new[] { typeof(OsuModSpunOut), typeof(ModRelax), typeof(ModFailCondition), typeof(ModNoFail), typeof(ModAutoplay), typeof(OsuModMagnetised), typeof(OsuModRepel) };
|
public override Type[] IncompatibleMods => new[] { typeof(OsuModSpunOut), typeof(ModRelax), typeof(ModFailCondition), typeof(ModNoFail), typeof(ModAutoplay), typeof(OsuModMagnetised), typeof(OsuModRepel) };
|
||||||
|
|
||||||
public bool PerformFail() => false;
|
public bool PerformFail() => false;
|
||||||
@ -30,8 +31,6 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
|
|
||||||
private OsuInputManager inputManager = null!;
|
private OsuInputManager inputManager = null!;
|
||||||
|
|
||||||
private IFrameStableClock gameplayClock = null!;
|
|
||||||
|
|
||||||
private List<OsuReplayFrame> replayFrames = null!;
|
private List<OsuReplayFrame> replayFrames = null!;
|
||||||
|
|
||||||
private int currentFrame;
|
private int currentFrame;
|
||||||
@ -40,7 +39,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
{
|
{
|
||||||
if (currentFrame == replayFrames.Count - 1) return;
|
if (currentFrame == replayFrames.Count - 1) return;
|
||||||
|
|
||||||
double time = gameplayClock.CurrentTime;
|
double time = playfield.Clock.CurrentTime;
|
||||||
|
|
||||||
// Very naive implementation of autopilot based on proximity to replay frames.
|
// Very naive implementation of autopilot based on proximity to replay frames.
|
||||||
// TODO: this needs to be based on user interactions to better match stable (pausing until judgement is registered).
|
// TODO: this needs to be based on user interactions to better match stable (pausing until judgement is registered).
|
||||||
@ -55,8 +54,6 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
|
|
||||||
public void ApplyToDrawableRuleset(DrawableRuleset<OsuHitObject> drawableRuleset)
|
public void ApplyToDrawableRuleset(DrawableRuleset<OsuHitObject> drawableRuleset)
|
||||||
{
|
{
|
||||||
gameplayClock = drawableRuleset.FrameStableClock;
|
|
||||||
|
|
||||||
// Grab the input manager to disable the user's cursor, and for future use
|
// Grab the input manager to disable the user's cursor, and for future use
|
||||||
inputManager = (OsuInputManager)drawableRuleset.KeyBindingInputManager;
|
inputManager = (OsuInputManager)drawableRuleset.KeyBindingInputManager;
|
||||||
inputManager.AllowUserCursorMovement = false;
|
inputManager.AllowUserCursorMovement = false;
|
||||||
|
@ -8,6 +8,7 @@ using osu.Framework.Graphics.Containers;
|
|||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
using osu.Framework.Graphics.Textures;
|
using osu.Framework.Graphics.Textures;
|
||||||
|
using osu.Framework.Localisation;
|
||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
@ -22,7 +23,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
public class OsuModBlinds : Mod, IApplicableToDrawableRuleset<OsuHitObject>, IApplicableToHealthProcessor
|
public class OsuModBlinds : Mod, IApplicableToDrawableRuleset<OsuHitObject>, IApplicableToHealthProcessor
|
||||||
{
|
{
|
||||||
public override string Name => "Blinds";
|
public override string Name => "Blinds";
|
||||||
public override string Description => "Play with blinds on your screen.";
|
public override LocalisableString Description => "Play with blinds on your screen.";
|
||||||
public override string Acronym => "BL";
|
public override string Acronym => "BL";
|
||||||
|
|
||||||
public override IconUsage? Icon => FontAwesome.Solid.Adjust;
|
public override IconUsage? Icon => FontAwesome.Solid.Adjust;
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
using osu.Framework.Localisation;
|
||||||
using osu.Game.Configuration;
|
using osu.Game.Configuration;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Mods
|
namespace osu.Game.Rulesets.Osu.Mods
|
||||||
@ -15,7 +16,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
|
|
||||||
public override IconUsage? Icon => FontAwesome.Solid.CompressArrowsAlt;
|
public override IconUsage? Icon => FontAwesome.Solid.CompressArrowsAlt;
|
||||||
|
|
||||||
public override string Description => "Hit them at the right size!";
|
public override LocalisableString Description => "Hit them at the right size!";
|
||||||
|
|
||||||
[SettingSource("Starting Size", "The initial size multiplier applied to all objects.")]
|
[SettingSource("Starting Size", "The initial size multiplier applied to all objects.")]
|
||||||
public override BindableNumber<float> StartScale { get; } = new BindableFloat
|
public override BindableNumber<float> StartScale { get; } = new BindableFloat
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Localisation;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Mods
|
namespace osu.Game.Rulesets.Osu.Mods
|
||||||
{
|
{
|
||||||
public class OsuModEasy : ModEasyWithExtraLives
|
public class OsuModEasy : ModEasyWithExtraLives
|
||||||
{
|
{
|
||||||
public override string Description => @"Larger circles, more forgiving HP drain, less accuracy required, and three lives!";
|
public override LocalisableString Description => @"Larger circles, more forgiving HP drain, less accuracy required, and three lives!";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
using osu.Framework.Localisation;
|
||||||
using osu.Game.Configuration;
|
using osu.Game.Configuration;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Mods
|
namespace osu.Game.Rulesets.Osu.Mods
|
||||||
@ -15,7 +16,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
|
|
||||||
public override IconUsage? Icon => FontAwesome.Solid.ArrowsAltV;
|
public override IconUsage? Icon => FontAwesome.Solid.ArrowsAltV;
|
||||||
|
|
||||||
public override string Description => "Hit them at the right size!";
|
public override LocalisableString Description => "Hit them at the right size!";
|
||||||
|
|
||||||
[SettingSource("Starting Size", "The initial size multiplier applied to all objects.")]
|
[SettingSource("Starting Size", "The initial size multiplier applied to all objects.")]
|
||||||
public override BindableNumber<float> StartScale { get; } = new BindableFloat
|
public override BindableNumber<float> StartScale { get; } = new BindableFloat
|
||||||
|
@ -6,6 +6,7 @@ using System.Diagnostics;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Localisation;
|
||||||
using osu.Game.Configuration;
|
using osu.Game.Configuration;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
@ -22,7 +23,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
[SettingSource("Only fade approach circles", "The main object body will not fade when enabled.")]
|
[SettingSource("Only fade approach circles", "The main object body will not fade when enabled.")]
|
||||||
public Bindable<bool> OnlyFadeApproachCircles { get; } = new BindableBool();
|
public Bindable<bool> OnlyFadeApproachCircles { get; } = new BindableBool();
|
||||||
|
|
||||||
public override string Description => @"Play with no approach circles and fading circles/sliders.";
|
public override LocalisableString Description => @"Play with no approach circles and fading circles/sliders.";
|
||||||
public override double ScoreMultiplier => UsesDefaultConfiguration ? 1.06 : 1;
|
public override double ScoreMultiplier => UsesDefaultConfiguration ? 1.06 : 1;
|
||||||
|
|
||||||
public override Type[] IncompatibleMods => new[] { typeof(IRequiresApproachCircles), typeof(OsuModSpinIn) };
|
public override Type[] IncompatibleMods => new[] { typeof(IRequiresApproachCircles), typeof(OsuModSpinIn) };
|
||||||
|
@ -4,6 +4,8 @@
|
|||||||
using System;
|
using System;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
using osu.Framework.Localisation;
|
||||||
|
using osu.Framework.Timing;
|
||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
using osu.Game.Configuration;
|
using osu.Game.Configuration;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
@ -22,12 +24,10 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
public override string Acronym => "MG";
|
public override string Acronym => "MG";
|
||||||
public override IconUsage? Icon => FontAwesome.Solid.Magnet;
|
public override IconUsage? Icon => FontAwesome.Solid.Magnet;
|
||||||
public override ModType Type => ModType.Fun;
|
public override ModType Type => ModType.Fun;
|
||||||
public override string Description => "No need to chase the circles – your cursor is a magnet!";
|
public override LocalisableString Description => "No need to chase the circles – your cursor is a magnet!";
|
||||||
public override double ScoreMultiplier => 1;
|
public override double ScoreMultiplier => 0.5;
|
||||||
public override Type[] IncompatibleMods => new[] { typeof(OsuModAutopilot), typeof(OsuModWiggle), typeof(OsuModTransform), typeof(ModAutoplay), typeof(OsuModRelax), typeof(OsuModRepel) };
|
public override Type[] IncompatibleMods => new[] { typeof(OsuModAutopilot), typeof(OsuModWiggle), typeof(OsuModTransform), typeof(ModAutoplay), typeof(OsuModRelax), typeof(OsuModRepel) };
|
||||||
|
|
||||||
private IFrameStableClock gameplayClock = null!;
|
|
||||||
|
|
||||||
[SettingSource("Attraction strength", "How strong the pull is.", 0)]
|
[SettingSource("Attraction strength", "How strong the pull is.", 0)]
|
||||||
public BindableFloat AttractionStrength { get; } = new BindableFloat(0.5f)
|
public BindableFloat AttractionStrength { get; } = new BindableFloat(0.5f)
|
||||||
{
|
{
|
||||||
@ -38,8 +38,6 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
|
|
||||||
public void ApplyToDrawableRuleset(DrawableRuleset<OsuHitObject> drawableRuleset)
|
public void ApplyToDrawableRuleset(DrawableRuleset<OsuHitObject> drawableRuleset)
|
||||||
{
|
{
|
||||||
gameplayClock = drawableRuleset.FrameStableClock;
|
|
||||||
|
|
||||||
// Hide judgment displays and follow points as they won't make any sense.
|
// Hide judgment displays and follow points as they won't make any sense.
|
||||||
// Judgements can potentially be turned on in a future where they display at a position relative to their drawable counterpart.
|
// Judgements can potentially be turned on in a future where they display at a position relative to their drawable counterpart.
|
||||||
drawableRuleset.Playfield.DisplayJudgements.Value = false;
|
drawableRuleset.Playfield.DisplayJudgements.Value = false;
|
||||||
@ -55,27 +53,27 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
switch (drawable)
|
switch (drawable)
|
||||||
{
|
{
|
||||||
case DrawableHitCircle circle:
|
case DrawableHitCircle circle:
|
||||||
easeTo(circle, cursorPos);
|
easeTo(playfield.Clock, circle, cursorPos);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case DrawableSlider slider:
|
case DrawableSlider slider:
|
||||||
|
|
||||||
if (!slider.HeadCircle.Result.HasResult)
|
if (!slider.HeadCircle.Result.HasResult)
|
||||||
easeTo(slider, cursorPos);
|
easeTo(playfield.Clock, slider, cursorPos);
|
||||||
else
|
else
|
||||||
easeTo(slider, cursorPos - slider.Ball.DrawPosition);
|
easeTo(playfield.Clock, slider, cursorPos - slider.Ball.DrawPosition);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void easeTo(DrawableHitObject hitObject, Vector2 destination)
|
private void easeTo(IFrameBasedClock clock, DrawableHitObject hitObject, Vector2 destination)
|
||||||
{
|
{
|
||||||
double dampLength = Interpolation.Lerp(3000, 40, AttractionStrength.Value);
|
double dampLength = Interpolation.Lerp(3000, 40, AttractionStrength.Value);
|
||||||
|
|
||||||
float x = (float)Interpolation.DampContinuously(hitObject.X, destination.X, dampLength, gameplayClock.ElapsedFrameTime);
|
float x = (float)Interpolation.DampContinuously(hitObject.X, destination.X, dampLength, clock.ElapsedFrameTime);
|
||||||
float y = (float)Interpolation.DampContinuously(hitObject.Y, destination.Y, dampLength, gameplayClock.ElapsedFrameTime);
|
float y = (float)Interpolation.DampContinuously(hitObject.Y, destination.Y, dampLength, clock.ElapsedFrameTime);
|
||||||
|
|
||||||
hitObject.Position = new Vector2(x, y);
|
hitObject.Position = new Vector2(x, y);
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Localisation;
|
||||||
using osu.Game.Configuration;
|
using osu.Game.Configuration;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
@ -13,7 +14,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
{
|
{
|
||||||
public class OsuModMirror : ModMirror, IApplicableToHitObject
|
public class OsuModMirror : ModMirror, IApplicableToHitObject
|
||||||
{
|
{
|
||||||
public override string Description => "Flip objects on the chosen axes.";
|
public override LocalisableString Description => "Flip objects on the chosen axes.";
|
||||||
public override Type[] IncompatibleMods => new[] { typeof(ModHardRock) };
|
public override Type[] IncompatibleMods => new[] { typeof(ModHardRock) };
|
||||||
|
|
||||||
[SettingSource("Mirrored axes", "Choose which axes objects are mirrored over.")]
|
[SettingSource("Mirrored axes", "Choose which axes objects are mirrored over.")]
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Localisation;
|
||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Configuration;
|
using osu.Game.Configuration;
|
||||||
@ -17,7 +18,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
{
|
{
|
||||||
public class OsuModNoScope : ModNoScope, IUpdatableByPlayfield, IApplicableToBeatmap
|
public class OsuModNoScope : ModNoScope, IUpdatableByPlayfield, IApplicableToBeatmap
|
||||||
{
|
{
|
||||||
public override string Description => "Where's the cursor?";
|
public override LocalisableString Description => "Where's the cursor?";
|
||||||
|
|
||||||
private PeriodTracker spinnerPeriods = null!;
|
private PeriodTracker spinnerPeriods = null!;
|
||||||
|
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using osu.Framework.Localisation;
|
||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
@ -18,7 +19,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class OsuModRandom : ModRandom, IApplicableToBeatmap
|
public class OsuModRandom : ModRandom, IApplicableToBeatmap
|
||||||
{
|
{
|
||||||
public override string Description => "It never gets boring!";
|
public override LocalisableString Description => "It never gets boring!";
|
||||||
|
|
||||||
public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(OsuModTarget)).ToArray();
|
public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(OsuModTarget)).ToArray();
|
||||||
|
|
||||||
|
@ -5,6 +5,7 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using osu.Framework.Localisation;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.Objects.Types;
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
@ -18,7 +19,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
{
|
{
|
||||||
public class OsuModRelax : ModRelax, IUpdatableByPlayfield, IApplicableToDrawableRuleset<OsuHitObject>, IApplicableToPlayer
|
public class OsuModRelax : ModRelax, IUpdatableByPlayfield, IApplicableToDrawableRuleset<OsuHitObject>, IApplicableToPlayer
|
||||||
{
|
{
|
||||||
public override string Description => @"You don't need to click. Give your clicking/tapping fingers a break from the heat of things.";
|
public override LocalisableString Description => @"You don't need to click. Give your clicking/tapping fingers a break from the heat of things.";
|
||||||
public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModAutopilot), typeof(OsuModMagnetised), typeof(OsuModAlternate), typeof(OsuModSingleTap) }).ToArray();
|
public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModAutopilot), typeof(OsuModMagnetised), typeof(OsuModAlternate), typeof(OsuModSingleTap) }).ToArray();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -2,8 +2,9 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Diagnostics;
|
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Localisation;
|
||||||
|
using osu.Framework.Timing;
|
||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
using osu.Game.Configuration;
|
using osu.Game.Configuration;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
@ -22,12 +23,10 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
public override string Name => "Repel";
|
public override string Name => "Repel";
|
||||||
public override string Acronym => "RP";
|
public override string Acronym => "RP";
|
||||||
public override ModType Type => ModType.Fun;
|
public override ModType Type => ModType.Fun;
|
||||||
public override string Description => "Hit objects run away!";
|
public override LocalisableString Description => "Hit objects run away!";
|
||||||
public override double ScoreMultiplier => 1;
|
public override double ScoreMultiplier => 1;
|
||||||
public override Type[] IncompatibleMods => new[] { typeof(OsuModAutopilot), typeof(OsuModWiggle), typeof(OsuModTransform), typeof(ModAutoplay), typeof(OsuModMagnetised) };
|
public override Type[] IncompatibleMods => new[] { typeof(OsuModAutopilot), typeof(OsuModWiggle), typeof(OsuModTransform), typeof(ModAutoplay), typeof(OsuModMagnetised) };
|
||||||
|
|
||||||
private IFrameStableClock? gameplayClock;
|
|
||||||
|
|
||||||
[SettingSource("Repulsion strength", "How strong the repulsion is.", 0)]
|
[SettingSource("Repulsion strength", "How strong the repulsion is.", 0)]
|
||||||
public BindableFloat RepulsionStrength { get; } = new BindableFloat(0.5f)
|
public BindableFloat RepulsionStrength { get; } = new BindableFloat(0.5f)
|
||||||
{
|
{
|
||||||
@ -38,8 +37,6 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
|
|
||||||
public void ApplyToDrawableRuleset(DrawableRuleset<OsuHitObject> drawableRuleset)
|
public void ApplyToDrawableRuleset(DrawableRuleset<OsuHitObject> drawableRuleset)
|
||||||
{
|
{
|
||||||
gameplayClock = drawableRuleset.FrameStableClock;
|
|
||||||
|
|
||||||
// Hide judgment displays and follow points as they won't make any sense.
|
// Hide judgment displays and follow points as they won't make any sense.
|
||||||
// Judgements can potentially be turned on in a future where they display at a position relative to their drawable counterpart.
|
// Judgements can potentially be turned on in a future where they display at a position relative to their drawable counterpart.
|
||||||
drawableRuleset.Playfield.DisplayJudgements.Value = false;
|
drawableRuleset.Playfield.DisplayJudgements.Value = false;
|
||||||
@ -68,29 +65,27 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
switch (drawable)
|
switch (drawable)
|
||||||
{
|
{
|
||||||
case DrawableHitCircle circle:
|
case DrawableHitCircle circle:
|
||||||
easeTo(circle, destination, cursorPos);
|
easeTo(playfield.Clock, circle, destination, cursorPos);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case DrawableSlider slider:
|
case DrawableSlider slider:
|
||||||
|
|
||||||
if (!slider.HeadCircle.Result.HasResult)
|
if (!slider.HeadCircle.Result.HasResult)
|
||||||
easeTo(slider, destination, cursorPos);
|
easeTo(playfield.Clock, slider, destination, cursorPos);
|
||||||
else
|
else
|
||||||
easeTo(slider, destination - slider.Ball.DrawPosition, cursorPos);
|
easeTo(playfield.Clock, slider, destination - slider.Ball.DrawPosition, cursorPos);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void easeTo(DrawableHitObject hitObject, Vector2 destination, Vector2 cursorPos)
|
private void easeTo(IFrameBasedClock clock, DrawableHitObject hitObject, Vector2 destination, Vector2 cursorPos)
|
||||||
{
|
{
|
||||||
Debug.Assert(gameplayClock != null);
|
|
||||||
|
|
||||||
double dampLength = Vector2.Distance(hitObject.Position, cursorPos) / (0.04 * RepulsionStrength.Value + 0.04);
|
double dampLength = Vector2.Distance(hitObject.Position, cursorPos) / (0.04 * RepulsionStrength.Value + 0.04);
|
||||||
|
|
||||||
float x = (float)Interpolation.DampContinuously(hitObject.X, destination.X, dampLength, gameplayClock.ElapsedFrameTime);
|
float x = (float)Interpolation.DampContinuously(hitObject.X, destination.X, dampLength, clock.ElapsedFrameTime);
|
||||||
float y = (float)Interpolation.DampContinuously(hitObject.Y, destination.Y, dampLength, gameplayClock.ElapsedFrameTime);
|
float y = (float)Interpolation.DampContinuously(hitObject.Y, destination.Y, dampLength, clock.ElapsedFrameTime);
|
||||||
|
|
||||||
hitObject.Position = new Vector2(x, y);
|
hitObject.Position = new Vector2(x, y);
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using osu.Framework.Localisation;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Mods
|
namespace osu.Game.Rulesets.Osu.Mods
|
||||||
{
|
{
|
||||||
@ -10,7 +11,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
{
|
{
|
||||||
public override string Name => @"Single Tap";
|
public override string Name => @"Single Tap";
|
||||||
public override string Acronym => @"SG";
|
public override string Acronym => @"SG";
|
||||||
public override string Description => @"You must only use one key!";
|
public override LocalisableString Description => @"You must only use one key!";
|
||||||
public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModAlternate) }).ToArray();
|
public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModAlternate) }).ToArray();
|
||||||
|
|
||||||
protected override bool CheckValidNewAction(OsuAction action) => LastAcceptedAction == null || LastAcceptedAction == action;
|
protected override bool CheckValidNewAction(OsuAction action) => LastAcceptedAction == null || LastAcceptedAction == action;
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
using osu.Framework.Localisation;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
@ -18,7 +19,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
public override string Acronym => "SI";
|
public override string Acronym => "SI";
|
||||||
public override IconUsage? Icon => FontAwesome.Solid.Undo;
|
public override IconUsage? Icon => FontAwesome.Solid.Undo;
|
||||||
public override ModType Type => ModType.Fun;
|
public override ModType Type => ModType.Fun;
|
||||||
public override string Description => "Circles spin in. No approach circles.";
|
public override LocalisableString Description => "Circles spin in. No approach circles.";
|
||||||
public override double ScoreMultiplier => 1;
|
public override double ScoreMultiplier => 1;
|
||||||
|
|
||||||
// todo: this mod needs to be incompatible with "hidden" due to forcing the circle to remain opaque,
|
// todo: this mod needs to be incompatible with "hidden" due to forcing the circle to remain opaque,
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
using osu.Framework.Localisation;
|
||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
@ -18,7 +19,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
public override string Acronym => "SO";
|
public override string Acronym => "SO";
|
||||||
public override IconUsage? Icon => OsuIcon.ModSpunOut;
|
public override IconUsage? Icon => OsuIcon.ModSpunOut;
|
||||||
public override ModType Type => ModType.Automation;
|
public override ModType Type => ModType.Automation;
|
||||||
public override string Description => @"Spinners will be automatically completed.";
|
public override LocalisableString Description => @"Spinners will be automatically completed.";
|
||||||
public override double ScoreMultiplier => 0.9;
|
public override double ScoreMultiplier => 0.9;
|
||||||
public override Type[] IncompatibleMods => new[] { typeof(ModAutoplay), typeof(OsuModAutopilot), typeof(OsuModTarget) };
|
public override Type[] IncompatibleMods => new[] { typeof(ModAutoplay), typeof(OsuModAutopilot), typeof(OsuModTarget) };
|
||||||
|
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
using osu.Framework.Localisation;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Rulesets.Judgements;
|
using osu.Game.Rulesets.Judgements;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
@ -23,7 +24,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
public override string Name => @"Strict Tracking";
|
public override string Name => @"Strict Tracking";
|
||||||
public override string Acronym => @"ST";
|
public override string Acronym => @"ST";
|
||||||
public override ModType Type => ModType.DifficultyIncrease;
|
public override ModType Type => ModType.DifficultyIncrease;
|
||||||
public override string Description => @"Once you start a slider, follow precisely or get a miss.";
|
public override LocalisableString Description => @"Once you start a slider, follow precisely or get a miss.";
|
||||||
public override double ScoreMultiplier => 1.0;
|
public override double ScoreMultiplier => 1.0;
|
||||||
public override Type[] IncompatibleMods => new[] { typeof(ModClassic), typeof(OsuModTarget) };
|
public override Type[] IncompatibleMods => new[] { typeof(ModClassic), typeof(OsuModTarget) };
|
||||||
|
|
||||||
|
@ -7,6 +7,7 @@ using System.Linq;
|
|||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
using osu.Framework.Localisation;
|
||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
using osu.Game.Audio;
|
using osu.Game.Audio;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
@ -39,7 +40,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
public override string Acronym => "TP";
|
public override string Acronym => "TP";
|
||||||
public override ModType Type => ModType.Conversion;
|
public override ModType Type => ModType.Conversion;
|
||||||
public override IconUsage? Icon => OsuIcon.ModTarget;
|
public override IconUsage? Icon => OsuIcon.ModTarget;
|
||||||
public override string Description => @"Practice keeping up with the beat of the song.";
|
public override LocalisableString Description => @"Practice keeping up with the beat of the song.";
|
||||||
public override double ScoreMultiplier => 1;
|
public override double ScoreMultiplier => 1;
|
||||||
|
|
||||||
public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[]
|
public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[]
|
||||||
@ -58,6 +59,9 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
Value = null
|
Value = null
|
||||||
};
|
};
|
||||||
|
|
||||||
|
[SettingSource("Metronome ticks", "Whether a metronome beat should play in the background")]
|
||||||
|
public Bindable<bool> Metronome { get; } = new BindableBool(true);
|
||||||
|
|
||||||
#region Constants
|
#region Constants
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -336,7 +340,8 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
|
|
||||||
public void ApplyToDrawableRuleset(DrawableRuleset<OsuHitObject> drawableRuleset)
|
public void ApplyToDrawableRuleset(DrawableRuleset<OsuHitObject> drawableRuleset)
|
||||||
{
|
{
|
||||||
drawableRuleset.Overlays.Add(new MetronomeBeat(drawableRuleset.Beatmap.HitObjects.First().StartTime));
|
if (Metronome.Value)
|
||||||
|
drawableRuleset.Overlays.Add(new MetronomeBeat(drawableRuleset.Beatmap.HitObjects.First().StartTime));
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
@ -357,10 +362,12 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
{
|
{
|
||||||
return breaks.Any(breakPeriod =>
|
return breaks.Any(breakPeriod =>
|
||||||
{
|
{
|
||||||
var firstObjAfterBreak = originalHitObjects.First(obj => almostBigger(obj.StartTime, breakPeriod.EndTime));
|
OsuHitObject? firstObjAfterBreak = originalHitObjects.FirstOrDefault(obj => almostBigger(obj.StartTime, breakPeriod.EndTime));
|
||||||
|
|
||||||
return almostBigger(time, breakPeriod.StartTime)
|
return almostBigger(time, breakPeriod.StartTime)
|
||||||
&& definitelyBigger(firstObjAfterBreak.StartTime, time);
|
// There should never really be a break section with no objects after it, but we've seen crashes from users with malformed beatmaps,
|
||||||
|
// so it's best to guard against this.
|
||||||
|
&& (firstObjAfterBreak == null || definitelyBigger(firstObjAfterBreak.StartTime, time));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Localisation;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Mods
|
namespace osu.Game.Rulesets.Osu.Mods
|
||||||
@ -9,7 +10,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
{
|
{
|
||||||
public override string Name => "Touch Device";
|
public override string Name => "Touch Device";
|
||||||
public override string Acronym => "TD";
|
public override string Acronym => "TD";
|
||||||
public override string Description => "Automatically applied to plays on devices with a touchscreen.";
|
public override LocalisableString Description => "Automatically applied to plays on devices with a touchscreen.";
|
||||||
public override double ScoreMultiplier => 1;
|
public override double ScoreMultiplier => 1;
|
||||||
|
|
||||||
public override ModType Type => ModType.System;
|
public override ModType Type => ModType.System;
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using osu.Framework.Extensions.Color4Extensions;
|
using osu.Framework.Extensions.Color4Extensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Localisation;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||||
@ -16,7 +17,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
public override string Name => "Traceable";
|
public override string Name => "Traceable";
|
||||||
public override string Acronym => "TC";
|
public override string Acronym => "TC";
|
||||||
public override ModType Type => ModType.Fun;
|
public override ModType Type => ModType.Fun;
|
||||||
public override string Description => "Put your faith in the approach circles...";
|
public override LocalisableString Description => "Put your faith in the approach circles...";
|
||||||
public override double ScoreMultiplier => 1;
|
public override double ScoreMultiplier => 1;
|
||||||
|
|
||||||
public override Type[] IncompatibleMods => new[] { typeof(IHidesApproachCircles) };
|
public override Type[] IncompatibleMods => new[] { typeof(IHidesApproachCircles) };
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
using osu.Framework.Localisation;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
@ -18,7 +19,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
public override string Acronym => "TR";
|
public override string Acronym => "TR";
|
||||||
public override IconUsage? Icon => FontAwesome.Solid.ArrowsAlt;
|
public override IconUsage? Icon => FontAwesome.Solid.ArrowsAlt;
|
||||||
public override ModType Type => ModType.Fun;
|
public override ModType Type => ModType.Fun;
|
||||||
public override string Description => "Everything rotates. EVERYTHING.";
|
public override LocalisableString Description => "Everything rotates. EVERYTHING.";
|
||||||
public override double ScoreMultiplier => 1;
|
public override double ScoreMultiplier => 1;
|
||||||
public override Type[] IncompatibleMods => new[] { typeof(OsuModWiggle), typeof(OsuModMagnetised), typeof(OsuModRepel) };
|
public override Type[] IncompatibleMods => new[] { typeof(OsuModWiggle), typeof(OsuModMagnetised), typeof(OsuModRepel) };
|
||||||
|
|
||||||
|
@ -5,6 +5,7 @@ using System;
|
|||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
using osu.Framework.Localisation;
|
||||||
using osu.Game.Configuration;
|
using osu.Game.Configuration;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
@ -20,7 +21,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
public override string Acronym => "WG";
|
public override string Acronym => "WG";
|
||||||
public override IconUsage? Icon => FontAwesome.Solid.Certificate;
|
public override IconUsage? Icon => FontAwesome.Solid.Certificate;
|
||||||
public override ModType Type => ModType.Fun;
|
public override ModType Type => ModType.Fun;
|
||||||
public override string Description => "They just won't stay still...";
|
public override LocalisableString Description => "They just won't stay still...";
|
||||||
public override double ScoreMultiplier => 1;
|
public override double ScoreMultiplier => 1;
|
||||||
public override Type[] IncompatibleMods => new[] { typeof(OsuModTransform), typeof(OsuModMagnetised), typeof(OsuModRepel) };
|
public override Type[] IncompatibleMods => new[] { typeof(OsuModTransform), typeof(OsuModMagnetised), typeof(OsuModRepel) };
|
||||||
|
|
||||||
|
@ -77,7 +77,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
// This is a bit ugly but we don't have the concept of InternalContent so it'll have to do for now. (https://github.com/ppy/osu-framework/issues/1690)
|
// This is a bit ugly but we don't have the concept of InternalContent so it'll have to do for now. (https://github.com/ppy/osu-framework/issues/1690)
|
||||||
protected override void AddInternal(Drawable drawable) => shakeContainer.Add(drawable);
|
protected override void AddInternal(Drawable drawable) => shakeContainer.Add(drawable);
|
||||||
protected override void ClearInternal(bool disposeChildren = true) => shakeContainer.Clear(disposeChildren);
|
protected override void ClearInternal(bool disposeChildren = true) => shakeContainer.Clear(disposeChildren);
|
||||||
protected override bool RemoveInternal(Drawable drawable) => shakeContainer.Remove(drawable);
|
protected override bool RemoveInternal(Drawable drawable, bool disposeImmediately) => shakeContainer.Remove(drawable, disposeImmediately);
|
||||||
|
|
||||||
protected sealed override double InitialLifetimeOffset => HitObject.TimePreempt;
|
protected sealed override double InitialLifetimeOffset => HitObject.TimePreempt;
|
||||||
|
|
||||||
|
@ -14,6 +14,7 @@ using osu.Framework.Caching;
|
|||||||
using osu.Game.Audio;
|
using osu.Game.Audio;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Beatmaps.ControlPoints;
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
|
using osu.Game.Beatmaps.Formats;
|
||||||
using osu.Game.Rulesets.Judgements;
|
using osu.Game.Rulesets.Judgements;
|
||||||
using osu.Game.Rulesets.Osu.Judgements;
|
using osu.Game.Rulesets.Osu.Judgements;
|
||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
@ -165,11 +166,15 @@ namespace osu.Game.Rulesets.Osu.Objects
|
|||||||
base.ApplyDefaultsToSelf(controlPointInfo, difficulty);
|
base.ApplyDefaultsToSelf(controlPointInfo, difficulty);
|
||||||
|
|
||||||
TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(StartTime);
|
TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(StartTime);
|
||||||
|
#pragma warning disable 618
|
||||||
|
var legacyDifficultyPoint = DifficultyControlPoint as LegacyBeatmapDecoder.LegacyDifficultyControlPoint;
|
||||||
|
#pragma warning restore 618
|
||||||
|
|
||||||
double scoringDistance = BASE_SCORING_DISTANCE * difficulty.SliderMultiplier * DifficultyControlPoint.SliderVelocity;
|
double scoringDistance = BASE_SCORING_DISTANCE * difficulty.SliderMultiplier * DifficultyControlPoint.SliderVelocity;
|
||||||
|
bool generateTicks = legacyDifficultyPoint?.GenerateTicks ?? true;
|
||||||
|
|
||||||
Velocity = scoringDistance / timingPoint.BeatLength;
|
Velocity = scoringDistance / timingPoint.BeatLength;
|
||||||
TickDistance = scoringDistance / difficulty.SliderTickRate * TickDistanceMultiplier;
|
TickDistance = generateTicks ? (scoringDistance / difficulty.SliderTickRate * TickDistanceMultiplier) : double.PositiveInfinity;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void CreateNestedHitObjects(CancellationToken cancellationToken)
|
protected override void CreateNestedHitObjects(CancellationToken cancellationToken)
|
||||||
|
@ -3,41 +3,42 @@
|
|||||||
|
|
||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
||||||
using osu.Game.Beatmaps;
|
using System;
|
||||||
using osu.Game.Graphics;
|
|
||||||
using osu.Game.Rulesets.Mods;
|
|
||||||
using osu.Game.Rulesets.Osu.Mods;
|
|
||||||
using osu.Game.Rulesets.Osu.UI;
|
|
||||||
using osu.Game.Rulesets.UI;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using osu.Framework.Extensions.EnumExtensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
using osu.Game.Overlays.Settings;
|
|
||||||
using osu.Framework.Input.Bindings;
|
using osu.Framework.Input.Bindings;
|
||||||
using osu.Game.Rulesets.Osu.Edit;
|
using osu.Framework.Localisation;
|
||||||
using osu.Game.Rulesets.Edit;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Rulesets.Osu.Replays;
|
|
||||||
using osu.Game.Rulesets.Replays.Types;
|
|
||||||
using osu.Game.Beatmaps.Legacy;
|
using osu.Game.Beatmaps.Legacy;
|
||||||
using osu.Game.Configuration;
|
using osu.Game.Configuration;
|
||||||
|
using osu.Game.Graphics;
|
||||||
|
using osu.Game.Overlays.Settings;
|
||||||
using osu.Game.Rulesets.Configuration;
|
using osu.Game.Rulesets.Configuration;
|
||||||
using osu.Game.Rulesets.Difficulty;
|
using osu.Game.Rulesets.Difficulty;
|
||||||
|
using osu.Game.Rulesets.Edit;
|
||||||
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.Osu.Beatmaps;
|
using osu.Game.Rulesets.Osu.Beatmaps;
|
||||||
using osu.Game.Rulesets.Osu.Configuration;
|
using osu.Game.Rulesets.Osu.Configuration;
|
||||||
using osu.Game.Rulesets.Osu.Difficulty;
|
using osu.Game.Rulesets.Osu.Difficulty;
|
||||||
using osu.Game.Rulesets.Osu.Scoring;
|
using osu.Game.Rulesets.Osu.Edit;
|
||||||
using osu.Game.Rulesets.Scoring;
|
|
||||||
using osu.Game.Scoring;
|
|
||||||
using osu.Game.Skinning;
|
|
||||||
using System;
|
|
||||||
using System.Linq;
|
|
||||||
using osu.Framework.Extensions.EnumExtensions;
|
|
||||||
using osu.Game.Rulesets.Osu.Edit.Setup;
|
using osu.Game.Rulesets.Osu.Edit.Setup;
|
||||||
|
using osu.Game.Rulesets.Osu.Mods;
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
|
using osu.Game.Rulesets.Osu.Replays;
|
||||||
|
using osu.Game.Rulesets.Osu.Scoring;
|
||||||
using osu.Game.Rulesets.Osu.Skinning.Legacy;
|
using osu.Game.Rulesets.Osu.Skinning.Legacy;
|
||||||
using osu.Game.Rulesets.Osu.Statistics;
|
using osu.Game.Rulesets.Osu.Statistics;
|
||||||
|
using osu.Game.Rulesets.Osu.UI;
|
||||||
|
using osu.Game.Rulesets.Replays.Types;
|
||||||
|
using osu.Game.Rulesets.Scoring;
|
||||||
|
using osu.Game.Rulesets.UI;
|
||||||
|
using osu.Game.Scoring;
|
||||||
using osu.Game.Screens.Edit.Setup;
|
using osu.Game.Screens.Edit.Setup;
|
||||||
using osu.Game.Screens.Ranking.Statistics;
|
using osu.Game.Screens.Ranking.Statistics;
|
||||||
|
using osu.Game.Skinning;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu
|
namespace osu.Game.Rulesets.Osu
|
||||||
{
|
{
|
||||||
@ -53,6 +54,8 @@ namespace osu.Game.Rulesets.Osu
|
|||||||
|
|
||||||
public const string SHORT_NAME = "osu";
|
public const string SHORT_NAME = "osu";
|
||||||
|
|
||||||
|
public override string RulesetAPIVersionSupported => CURRENT_RULESET_API_VERSION;
|
||||||
|
|
||||||
public override IEnumerable<KeyBinding> GetDefaultKeyBindings(int variant = 0) => new[]
|
public override IEnumerable<KeyBinding> GetDefaultKeyBindings(int variant = 0) => new[]
|
||||||
{
|
{
|
||||||
new KeyBinding(InputKey.Z, OsuAction.LeftButton),
|
new KeyBinding(InputKey.Z, OsuAction.LeftButton),
|
||||||
@ -253,7 +256,7 @@ namespace osu.Game.Rulesets.Osu
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string GetDisplayNameForHitResult(HitResult result)
|
public override LocalisableString GetDisplayNameForHitResult(HitResult result)
|
||||||
{
|
{
|
||||||
switch (result)
|
switch (result)
|
||||||
{
|
{
|
||||||
|
@ -59,7 +59,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default
|
|||||||
private bool rotationTransferred;
|
private bool rotationTransferred;
|
||||||
|
|
||||||
[Resolved(canBeNull: true)]
|
[Resolved(canBeNull: true)]
|
||||||
private GameplayClock gameplayClock { get; set; }
|
private IGameplayClock gameplayClock { get; set; }
|
||||||
|
|
||||||
protected override void Update()
|
protected override void Update()
|
||||||
{
|
{
|
||||||
|
@ -11,6 +11,7 @@ using osu.Framework.Graphics.Containers;
|
|||||||
using osu.Framework.Graphics.Cursor;
|
using osu.Framework.Graphics.Cursor;
|
||||||
using osu.Framework.Input.Bindings;
|
using osu.Framework.Input.Bindings;
|
||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
|
using osu.Framework.Localisation;
|
||||||
using osu.Game.Rulesets.Osu.UI.Cursor;
|
using osu.Game.Rulesets.Osu.UI.Cursor;
|
||||||
using osu.Game.Screens.Play;
|
using osu.Game.Screens.Play;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
@ -28,7 +29,7 @@ namespace osu.Game.Rulesets.Osu.UI
|
|||||||
|
|
||||||
public override CursorContainer LocalCursor => State.Value == Visibility.Visible ? localCursorContainer : null;
|
public override CursorContainer LocalCursor => State.Value == Visibility.Visible ? localCursorContainer : null;
|
||||||
|
|
||||||
protected override string Message => "Click the orange cursor to resume";
|
protected override LocalisableString Message => "Click the orange cursor to resume";
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load()
|
||||||
|
@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Editor
|
|||||||
public void Setup() => Schedule(() =>
|
public void Setup() => Schedule(() =>
|
||||||
{
|
{
|
||||||
BeatDivisor.Value = 8;
|
BeatDivisor.Value = 8;
|
||||||
Clock.Seek(0);
|
EditorClock.Seek(0);
|
||||||
|
|
||||||
Child = new TestComposer { RelativeSizeAxes = Axes.Both };
|
Child = new TestComposer { RelativeSizeAxes = Axes.Both };
|
||||||
});
|
});
|
||||||
|
96
osu.Game.Rulesets.Taiko.Tests/Judgements/JudgementTest.cs
Normal file
96
osu.Game.Rulesets.Taiko.Tests/Judgements/JudgementTest.cs
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Extensions.TypeExtensions;
|
||||||
|
using osu.Framework.Screens;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
|
using osu.Game.Replays;
|
||||||
|
using osu.Game.Rulesets.Judgements;
|
||||||
|
using osu.Game.Rulesets.Replays;
|
||||||
|
using osu.Game.Rulesets.Scoring;
|
||||||
|
using osu.Game.Rulesets.Taiko.Objects;
|
||||||
|
using osu.Game.Scoring;
|
||||||
|
using osu.Game.Screens.Play;
|
||||||
|
using osu.Game.Tests.Visual;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Taiko.Tests.Judgements
|
||||||
|
{
|
||||||
|
public class JudgementTest : RateAdjustedBeatmapTestScene
|
||||||
|
{
|
||||||
|
private ScoreAccessibleReplayPlayer currentPlayer = null!;
|
||||||
|
protected List<JudgementResult> JudgementResults { get; private set; } = null!;
|
||||||
|
|
||||||
|
protected void AssertJudgementCount(int count)
|
||||||
|
{
|
||||||
|
AddAssert($"{count} judgement{(count > 0 ? "s" : "")}", () => JudgementResults, () => Has.Count.EqualTo(count));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void AssertResult<T>(int index, HitResult expectedResult)
|
||||||
|
{
|
||||||
|
AddAssert($"{typeof(T).ReadableName()} ({index}) judged as {expectedResult}",
|
||||||
|
() => JudgementResults.Where(j => j.HitObject is T).OrderBy(j => j.HitObject.StartTime).ElementAt(index).Type,
|
||||||
|
() => Is.EqualTo(expectedResult));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void PerformTest(List<ReplayFrame> frames, Beatmap<TaikoHitObject>? beatmap = null)
|
||||||
|
{
|
||||||
|
AddStep("load player", () =>
|
||||||
|
{
|
||||||
|
Beatmap.Value = CreateWorkingBeatmap(beatmap);
|
||||||
|
|
||||||
|
var p = new ScoreAccessibleReplayPlayer(new Score { Replay = new Replay { Frames = frames } });
|
||||||
|
|
||||||
|
p.OnLoadComplete += _ =>
|
||||||
|
{
|
||||||
|
p.ScoreProcessor.NewJudgement += result =>
|
||||||
|
{
|
||||||
|
if (currentPlayer == p) JudgementResults.Add(result);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
LoadScreen(currentPlayer = p);
|
||||||
|
JudgementResults = new List<JudgementResult>();
|
||||||
|
});
|
||||||
|
|
||||||
|
AddUntilStep("Beatmap at 0", () => Beatmap.Value.Track.CurrentTime == 0);
|
||||||
|
AddUntilStep("Wait until player is loaded", () => currentPlayer.IsCurrentScreen());
|
||||||
|
AddUntilStep("Wait for completion", () => currentPlayer.ScoreProcessor.HasCompleted.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Beatmap<TaikoHitObject> CreateBeatmap(params TaikoHitObject[] hitObjects)
|
||||||
|
{
|
||||||
|
var beatmap = new Beatmap<TaikoHitObject>
|
||||||
|
{
|
||||||
|
HitObjects = hitObjects.ToList(),
|
||||||
|
BeatmapInfo =
|
||||||
|
{
|
||||||
|
Difficulty = new BeatmapDifficulty { SliderTickRate = 4 },
|
||||||
|
Ruleset = new TaikoRuleset().RulesetInfo
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
beatmap.ControlPointInfo.Add(0, new EffectControlPoint { ScrollSpeed = 0.1f });
|
||||||
|
return beatmap;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ScoreAccessibleReplayPlayer : ReplayPlayer
|
||||||
|
{
|
||||||
|
public new ScoreProcessor ScoreProcessor => base.ScoreProcessor;
|
||||||
|
|
||||||
|
protected override bool PauseOnFocusLost => false;
|
||||||
|
|
||||||
|
public ScoreAccessibleReplayPlayer(Score score)
|
||||||
|
: base(score, new PlayerConfiguration
|
||||||
|
{
|
||||||
|
AllowPause = false,
|
||||||
|
ShowResults = false,
|
||||||
|
})
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,201 @@
|
|||||||
|
// 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 NUnit.Framework;
|
||||||
|
using osu.Game.Rulesets.Replays;
|
||||||
|
using osu.Game.Rulesets.Scoring;
|
||||||
|
using osu.Game.Rulesets.Taiko.Objects;
|
||||||
|
using osu.Game.Rulesets.Taiko.Replays;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Taiko.Tests.Judgements
|
||||||
|
{
|
||||||
|
public class TestSceneDrumRollJudgements : JudgementTest
|
||||||
|
{
|
||||||
|
[Test]
|
||||||
|
public void TestHitAllDrumRoll()
|
||||||
|
{
|
||||||
|
const double hit_time = 1000;
|
||||||
|
|
||||||
|
PerformTest(new List<ReplayFrame>
|
||||||
|
{
|
||||||
|
new TaikoReplayFrame(0),
|
||||||
|
new TaikoReplayFrame(1000, TaikoAction.LeftCentre),
|
||||||
|
new TaikoReplayFrame(1001),
|
||||||
|
new TaikoReplayFrame(2000, TaikoAction.LeftCentre),
|
||||||
|
new TaikoReplayFrame(2001),
|
||||||
|
}, CreateBeatmap(new DrumRoll
|
||||||
|
{
|
||||||
|
StartTime = hit_time,
|
||||||
|
Duration = 1000
|
||||||
|
}));
|
||||||
|
|
||||||
|
AssertJudgementCount(3);
|
||||||
|
AssertResult<DrumRollTick>(0, HitResult.SmallBonus);
|
||||||
|
AssertResult<DrumRollTick>(1, HitResult.SmallBonus);
|
||||||
|
AssertResult<DrumRoll>(0, HitResult.IgnoreHit);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestHitSomeDrumRoll()
|
||||||
|
{
|
||||||
|
const double hit_time = 1000;
|
||||||
|
|
||||||
|
PerformTest(new List<ReplayFrame>
|
||||||
|
{
|
||||||
|
new TaikoReplayFrame(0),
|
||||||
|
new TaikoReplayFrame(2000, TaikoAction.LeftCentre),
|
||||||
|
new TaikoReplayFrame(2001),
|
||||||
|
}, CreateBeatmap(new DrumRoll
|
||||||
|
{
|
||||||
|
StartTime = hit_time,
|
||||||
|
Duration = 1000
|
||||||
|
}));
|
||||||
|
|
||||||
|
AssertJudgementCount(3);
|
||||||
|
AssertResult<DrumRollTick>(0, HitResult.IgnoreMiss);
|
||||||
|
AssertResult<DrumRollTick>(1, HitResult.SmallBonus);
|
||||||
|
AssertResult<DrumRoll>(0, HitResult.IgnoreHit);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestHitNoneDrumRoll()
|
||||||
|
{
|
||||||
|
const double hit_time = 1000;
|
||||||
|
|
||||||
|
PerformTest(new List<ReplayFrame>
|
||||||
|
{
|
||||||
|
new TaikoReplayFrame(0),
|
||||||
|
}, CreateBeatmap(new DrumRoll
|
||||||
|
{
|
||||||
|
StartTime = hit_time,
|
||||||
|
Duration = 1000
|
||||||
|
}));
|
||||||
|
|
||||||
|
AssertJudgementCount(3);
|
||||||
|
AssertResult<DrumRollTick>(0, HitResult.IgnoreMiss);
|
||||||
|
AssertResult<DrumRollTick>(1, HitResult.IgnoreMiss);
|
||||||
|
AssertResult<DrumRoll>(0, HitResult.IgnoreHit);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestHitAllStrongDrumRollWithOneKey()
|
||||||
|
{
|
||||||
|
const double hit_time = 1000;
|
||||||
|
|
||||||
|
PerformTest(new List<ReplayFrame>
|
||||||
|
{
|
||||||
|
new TaikoReplayFrame(0),
|
||||||
|
new TaikoReplayFrame(1000, TaikoAction.LeftCentre),
|
||||||
|
new TaikoReplayFrame(1001),
|
||||||
|
new TaikoReplayFrame(2000, TaikoAction.LeftCentre),
|
||||||
|
new TaikoReplayFrame(2001),
|
||||||
|
}, CreateBeatmap(new DrumRoll
|
||||||
|
{
|
||||||
|
StartTime = hit_time,
|
||||||
|
Duration = 1000,
|
||||||
|
IsStrong = true
|
||||||
|
}));
|
||||||
|
|
||||||
|
AssertJudgementCount(6);
|
||||||
|
|
||||||
|
AssertResult<DrumRollTick>(0, HitResult.SmallBonus);
|
||||||
|
AssertResult<StrongNestedHitObject>(0, HitResult.LargeBonus);
|
||||||
|
|
||||||
|
AssertResult<DrumRollTick>(1, HitResult.SmallBonus);
|
||||||
|
AssertResult<StrongNestedHitObject>(1, HitResult.LargeBonus);
|
||||||
|
|
||||||
|
AssertResult<DrumRoll>(0, HitResult.IgnoreHit);
|
||||||
|
AssertResult<StrongNestedHitObject>(2, HitResult.IgnoreHit);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestHitSomeStrongDrumRollWithOneKey()
|
||||||
|
{
|
||||||
|
const double hit_time = 1000;
|
||||||
|
|
||||||
|
PerformTest(new List<ReplayFrame>
|
||||||
|
{
|
||||||
|
new TaikoReplayFrame(0),
|
||||||
|
new TaikoReplayFrame(2000, TaikoAction.LeftCentre),
|
||||||
|
new TaikoReplayFrame(2001),
|
||||||
|
}, CreateBeatmap(new DrumRoll
|
||||||
|
{
|
||||||
|
StartTime = hit_time,
|
||||||
|
Duration = 1000,
|
||||||
|
IsStrong = true
|
||||||
|
}));
|
||||||
|
|
||||||
|
AssertJudgementCount(6);
|
||||||
|
|
||||||
|
AssertResult<DrumRollTick>(0, HitResult.IgnoreMiss);
|
||||||
|
AssertResult<StrongNestedHitObject>(0, HitResult.IgnoreMiss);
|
||||||
|
|
||||||
|
AssertResult<DrumRollTick>(1, HitResult.SmallBonus);
|
||||||
|
AssertResult<StrongNestedHitObject>(1, HitResult.LargeBonus);
|
||||||
|
|
||||||
|
AssertResult<DrumRoll>(0, HitResult.IgnoreHit);
|
||||||
|
AssertResult<StrongNestedHitObject>(2, HitResult.IgnoreHit);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestHitAllStrongDrumRollWithBothKeys()
|
||||||
|
{
|
||||||
|
const double hit_time = 1000;
|
||||||
|
|
||||||
|
PerformTest(new List<ReplayFrame>
|
||||||
|
{
|
||||||
|
new TaikoReplayFrame(0),
|
||||||
|
new TaikoReplayFrame(1000, TaikoAction.LeftCentre, TaikoAction.RightCentre),
|
||||||
|
new TaikoReplayFrame(1001),
|
||||||
|
new TaikoReplayFrame(2000, TaikoAction.LeftCentre, TaikoAction.RightCentre),
|
||||||
|
new TaikoReplayFrame(2001),
|
||||||
|
}, CreateBeatmap(new DrumRoll
|
||||||
|
{
|
||||||
|
StartTime = hit_time,
|
||||||
|
Duration = 1000,
|
||||||
|
IsStrong = true
|
||||||
|
}));
|
||||||
|
|
||||||
|
AssertJudgementCount(6);
|
||||||
|
|
||||||
|
AssertResult<DrumRollTick>(0, HitResult.SmallBonus);
|
||||||
|
AssertResult<StrongNestedHitObject>(0, HitResult.LargeBonus);
|
||||||
|
|
||||||
|
AssertResult<DrumRollTick>(1, HitResult.SmallBonus);
|
||||||
|
AssertResult<StrongNestedHitObject>(1, HitResult.LargeBonus);
|
||||||
|
|
||||||
|
AssertResult<DrumRoll>(0, HitResult.IgnoreHit);
|
||||||
|
AssertResult<StrongNestedHitObject>(2, HitResult.IgnoreHit);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestHitSomeStrongDrumRollWithBothKeys()
|
||||||
|
{
|
||||||
|
const double hit_time = 1000;
|
||||||
|
|
||||||
|
PerformTest(new List<ReplayFrame>
|
||||||
|
{
|
||||||
|
new TaikoReplayFrame(0),
|
||||||
|
new TaikoReplayFrame(2000, TaikoAction.LeftCentre, TaikoAction.RightCentre),
|
||||||
|
new TaikoReplayFrame(2001),
|
||||||
|
}, CreateBeatmap(new DrumRoll
|
||||||
|
{
|
||||||
|
StartTime = hit_time,
|
||||||
|
Duration = 1000,
|
||||||
|
IsStrong = true
|
||||||
|
}));
|
||||||
|
|
||||||
|
AssertJudgementCount(6);
|
||||||
|
|
||||||
|
AssertResult<DrumRollTick>(0, HitResult.IgnoreMiss);
|
||||||
|
AssertResult<StrongNestedHitObject>(0, HitResult.IgnoreMiss);
|
||||||
|
|
||||||
|
AssertResult<DrumRollTick>(1, HitResult.SmallBonus);
|
||||||
|
AssertResult<StrongNestedHitObject>(1, HitResult.LargeBonus);
|
||||||
|
|
||||||
|
AssertResult<DrumRoll>(0, HitResult.IgnoreHit);
|
||||||
|
AssertResult<StrongNestedHitObject>(2, HitResult.IgnoreHit);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user