mirror of
https://github.com/ppy/osu.git
synced 2024-12-14 20:13:21 +08:00
Merge branch 'master' into acc-challenge
This commit is contained in:
commit
5604dfb758
@ -191,6 +191,8 @@ csharp_style_prefer_index_operator = false:silent
|
||||
csharp_style_prefer_range_operator = false:silent
|
||||
csharp_style_prefer_switch_expression = false:none
|
||||
|
||||
csharp_style_namespace_declarations = block_scoped:warning
|
||||
|
||||
[*.{yaml,yml}]
|
||||
insert_final_newline = true
|
||||
indent_style = space
|
||||
|
14
.github/workflows/ci.yml
vendored
14
.github/workflows/ci.yml
vendored
@ -121,12 +121,24 @@ jobs:
|
||||
|
||||
build-only-ios:
|
||||
name: Build only (iOS)
|
||||
runs-on: macos-latest
|
||||
# `macos-13` is required, because Xcode 14.3 is required (see below).
|
||||
# TODO: can be changed to `macos-latest` once `macos-13` becomes latest (currently in beta)
|
||||
runs-on: macos-13
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
# newest Microsoft.iOS.Sdk versions require Xcode 14.3.
|
||||
# 14.3 is currently not the default Xcode version (https://github.com/actions/runner-images/blob/main/images/macos/macos-13-Readme.md#xcode),
|
||||
# so set it manually.
|
||||
# TODO: remove when 14.3 becomes the default Xcode version.
|
||||
- name: Set Xcode version
|
||||
shell: bash
|
||||
run: |
|
||||
sudo xcode-select -s "/Applications/Xcode_14.3.app"
|
||||
echo "MD_APPLE_SDK_ROOT=/Applications/Xcode_14.3.app" >> $GITHUB_ENV
|
||||
|
||||
- name: Install .NET 6.0.x
|
||||
uses: actions/setup-dotnet@v3
|
||||
with:
|
||||
|
53
.github/workflows/update-web-mod-definitions.yml
vendored
Normal file
53
.github/workflows/update-web-mod-definitions.yml
vendored
Normal file
@ -0,0 +1,53 @@
|
||||
name: Update osu-web mod definitions
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- '*'
|
||||
|
||||
permissions:
|
||||
contents: read # to fetch code (actions/checkout)
|
||||
|
||||
jobs:
|
||||
update-mod-definitions:
|
||||
name: Update osu-web mod definitions
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Install .NET 6.0.x
|
||||
uses: actions/setup-dotnet@v3
|
||||
with:
|
||||
dotnet-version: "6.0.x"
|
||||
|
||||
- name: Checkout ppy/osu
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
path: osu
|
||||
|
||||
- name: Checkout ppy/osu-tools
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
repository: ppy/osu-tools
|
||||
path: osu-tools
|
||||
|
||||
- name: Checkout ppy/osu-web
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
repository: ppy/osu-web
|
||||
path: osu-web
|
||||
|
||||
- name: Setup local game checkout for tools
|
||||
run: ./UseLocalOsu.sh
|
||||
working-directory: ./osu-tools
|
||||
|
||||
- name: Regenerate mod definitions
|
||||
run: dotnet run --project PerformanceCalculator -- mods > ../osu-web/database/mods.json
|
||||
working-directory: ./osu-tools
|
||||
|
||||
- name: Create pull request with changes
|
||||
uses: peter-evans/create-pull-request@v5
|
||||
with:
|
||||
title: Update mod definitions
|
||||
body: "This PR has been auto-generated to update the mod definitions to match ppy/osu@${{ github.ref_name }}."
|
||||
branch: update-mod-definitions
|
||||
commit-message: Update mod definitions
|
||||
path: osu-web
|
||||
token: ${{ secrets.OSU_WEB_PULL_REQUEST_PAT }}
|
@ -8,7 +8,6 @@ using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Graphics.Textures;
|
||||
using osu.Game.Audio;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osuTK;
|
||||
@ -44,7 +43,7 @@ namespace osu.Game.Rulesets.Pippidon.Objects.Drawables
|
||||
|
||||
public override IEnumerable<HitSampleInfo> GetSamples() => new[]
|
||||
{
|
||||
new HitSampleInfo(HitSampleInfo.HIT_NORMAL, SampleControlPoint.DEFAULT_BANK)
|
||||
new HitSampleInfo(HitSampleInfo.HIT_NORMAL)
|
||||
};
|
||||
|
||||
protected override void CheckForResult(bool userTriggered, double timeOffset)
|
||||
|
@ -8,7 +8,6 @@ using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Graphics.Textures;
|
||||
using osu.Game.Audio;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Pippidon.UI;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
@ -44,7 +43,7 @@ namespace osu.Game.Rulesets.Pippidon.Objects.Drawables
|
||||
|
||||
public override IEnumerable<HitSampleInfo> GetSamples() => new[]
|
||||
{
|
||||
new HitSampleInfo(HitSampleInfo.HIT_NORMAL, SampleControlPoint.DEFAULT_BANK)
|
||||
new HitSampleInfo(HitSampleInfo.HIT_NORMAL)
|
||||
};
|
||||
|
||||
protected override void CheckForResult(bool userTriggered, double timeOffset)
|
||||
|
7
global.json
Normal file
7
global.json
Normal file
@ -0,0 +1,7 @@
|
||||
{
|
||||
"sdk": {
|
||||
"version": "6.0.100",
|
||||
"rollForward": "latestFeature"
|
||||
}
|
||||
}
|
||||
|
@ -11,7 +11,7 @@
|
||||
<AndroidManifestMerger>manifestmerger.jar</AndroidManifestMerger>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2023.418.0" />
|
||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2023.521.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidManifestOverlay Include="$(MSBuildThisFileDirectory)osu.Android\Properties\AndroidManifestOverlay.xml" />
|
||||
|
@ -9,6 +9,7 @@ using osu.Framework.Logging;
|
||||
using osu.Game;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Overlays.Notifications;
|
||||
using osu.Game.Screens.Play;
|
||||
using Squirrel;
|
||||
using Squirrel.SimpleSplat;
|
||||
using LogLevel = Squirrel.SimpleSplat.LogLevel;
|
||||
@ -36,6 +37,9 @@ namespace osu.Desktop.Updater
|
||||
[Resolved]
|
||||
private OsuGameBase game { get; set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private ILocalUserPlayInfo? localUserInfo { get; set; }
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(INotificationOverlay notifications)
|
||||
{
|
||||
@ -55,6 +59,10 @@ namespace osu.Desktop.Updater
|
||||
|
||||
try
|
||||
{
|
||||
// Avoid any kind of update checking while gameplay is running.
|
||||
if (localUserInfo?.IsPlaying.Value == true)
|
||||
return false;
|
||||
|
||||
updateManager ??= new GithubUpdateManager(@"https://github.com/ppy/osu", false, github_token, @"osulazer");
|
||||
|
||||
var info = await updateManager.CheckForUpdate(!useDeltaPatching).ConfigureAwait(false);
|
||||
|
@ -45,7 +45,7 @@ namespace osu.Game.Rulesets.Catch.Tests.Editor
|
||||
AddAssert("end time is correct", () => Precision.AlmostEquals(lastObject.EndTime, times[1]));
|
||||
AddAssert("start position is correct", () => Precision.AlmostEquals(lastObject.OriginalX, positions[0]));
|
||||
AddAssert("end position is correct", () => Precision.AlmostEquals(lastObject.EndX, positions[1]));
|
||||
AddAssert("default slider velocity", () => lastObject.DifficultyControlPoint.SliderVelocityBindable.IsDefault);
|
||||
AddAssert("default slider velocity", () => lastObject.SliderVelocityBindable.IsDefault);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -76,7 +76,7 @@ namespace osu.Game.Rulesets.Catch.Tests.Editor
|
||||
addPlacementSteps(times, positions);
|
||||
addPathCheckStep(times, positions);
|
||||
|
||||
AddAssert("slider velocity changed", () => !lastObject.DifficultyControlPoint.SliderVelocityBindable.IsDefault);
|
||||
AddAssert("slider velocity changed", () => !lastObject.SliderVelocityBindable.IsDefault);
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
@ -108,11 +108,11 @@ namespace osu.Game.Rulesets.Catch.Tests.Editor
|
||||
double[] times = { 100, 300 };
|
||||
float[] positions = { 200, 300 };
|
||||
addBlueprintStep(times, positions);
|
||||
AddAssert("default slider velocity", () => hitObject.DifficultyControlPoint.SliderVelocityBindable.IsDefault);
|
||||
AddAssert("default slider velocity", () => hitObject.SliderVelocityBindable.IsDefault);
|
||||
|
||||
addDragStartStep(times[1], positions[1]);
|
||||
AddMouseMoveStep(times[1], 400);
|
||||
AddAssert("slider velocity changed", () => !hitObject.DifficultyControlPoint.SliderVelocityBindable.IsDefault);
|
||||
AddAssert("slider velocity changed", () => !hitObject.SliderVelocityBindable.IsDefault);
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
@ -45,7 +45,7 @@ namespace osu.Game.Rulesets.Catch.Tests
|
||||
NewCombo = i % 8 == 0,
|
||||
Samples = new List<HitSampleInfo>(new[]
|
||||
{
|
||||
new HitSampleInfo(HitSampleInfo.HIT_NORMAL, "normal", volume: 100)
|
||||
new HitSampleInfo(HitSampleInfo.HIT_NORMAL)
|
||||
})
|
||||
});
|
||||
}
|
||||
|
@ -26,6 +26,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
|
||||
var xPositionData = obj as IHasXPosition;
|
||||
var yPositionData = obj as IHasYPosition;
|
||||
var comboData = obj as IHasCombo;
|
||||
var sliderVelocityData = obj as IHasSliderVelocity;
|
||||
|
||||
switch (obj)
|
||||
{
|
||||
@ -41,7 +42,8 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
|
||||
NewCombo = comboData?.NewCombo ?? false,
|
||||
ComboOffset = comboData?.ComboOffset ?? 0,
|
||||
LegacyLastTickOffset = (obj as IHasLegacyLastTickOffset)?.LegacyLastTickOffset ?? 0,
|
||||
LegacyConvertedY = yPositionData?.Y ?? CatchHitObject.DEFAULT_LEGACY_CONVERT_Y
|
||||
LegacyConvertedY = yPositionData?.Y ?? CatchHitObject.DEFAULT_LEGACY_CONVERT_Y,
|
||||
SliderVelocity = sliderVelocityData?.SliderVelocity ?? 1
|
||||
}.Yield();
|
||||
|
||||
case IHasDuration endTime:
|
||||
|
@ -17,6 +17,8 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints
|
||||
private double placementStartTime;
|
||||
private double placementEndTime;
|
||||
|
||||
protected override bool IsValidForPlacement => HitObject.Duration > 0;
|
||||
|
||||
public BananaShowerPlacementBlueprint()
|
||||
{
|
||||
InternalChild = outline = new TimeSpanOutline();
|
||||
@ -49,7 +51,7 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints
|
||||
case PlacementState.Active:
|
||||
if (e.Button != MouseButton.Right) break;
|
||||
|
||||
EndPlacement(HitObject.Duration > 0);
|
||||
EndPlacement(true);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -91,7 +91,7 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components
|
||||
public void UpdateHitObjectFromPath(JuiceStream hitObject)
|
||||
{
|
||||
// The SV setting may need to be changed for the current path.
|
||||
var svBindable = hitObject.DifficultyControlPoint.SliderVelocityBindable;
|
||||
var svBindable = hitObject.SliderVelocityBindable;
|
||||
double svToVelocityFactor = hitObject.Velocity / svBindable.Value;
|
||||
double requiredVelocity = path.ComputeRequiredVelocity();
|
||||
|
||||
|
@ -24,6 +24,8 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints
|
||||
|
||||
private InputManager inputManager = null!;
|
||||
|
||||
protected override bool IsValidForPlacement => HitObject.Duration > 0;
|
||||
|
||||
public JuiceStreamPlacementBlueprint()
|
||||
{
|
||||
InternalChildren = new Drawable[]
|
||||
@ -70,7 +72,7 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints
|
||||
return true;
|
||||
|
||||
case MouseButton.Right:
|
||||
EndPlacement(HitObject.Duration > 0);
|
||||
EndPlacement(true);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -132,7 +132,7 @@ namespace osu.Game.Rulesets.Catch.Edit
|
||||
|
||||
result.ScreenSpacePosition.X = screenSpacePosition.X;
|
||||
|
||||
if (snapType.HasFlagFast(SnapType.Grids))
|
||||
if (snapType.HasFlagFast(SnapType.RelativeGrids))
|
||||
{
|
||||
if (distanceSnapGrid.IsPresent && distanceSnapGrid.GetSnappedPosition(result.ScreenSpacePosition) is SnapResult snapResult &&
|
||||
Vector2.Distance(snapResult.ScreenSpacePosition, result.ScreenSpacePosition) < distance_snap_radius)
|
||||
|
@ -22,11 +22,11 @@ namespace osu.Game.Rulesets.Catch.Objects
|
||||
|
||||
public override Judgement CreateJudgement() => new CatchBananaJudgement();
|
||||
|
||||
private static readonly List<HitSampleInfo> samples = new List<HitSampleInfo> { new BananaHitSampleInfo() };
|
||||
private static readonly IList<HitSampleInfo> default_banana_samples = new List<HitSampleInfo> { new BananaHitSampleInfo() }.AsReadOnly();
|
||||
|
||||
public Banana()
|
||||
{
|
||||
Samples = samples;
|
||||
Samples = default_banana_samples;
|
||||
}
|
||||
|
||||
// override any external colour changes with banananana
|
||||
@ -47,18 +47,18 @@ namespace osu.Game.Rulesets.Catch.Objects
|
||||
}
|
||||
}
|
||||
|
||||
private class BananaHitSampleInfo : HitSampleInfo, IEquatable<BananaHitSampleInfo>
|
||||
public class BananaHitSampleInfo : HitSampleInfo, IEquatable<BananaHitSampleInfo>
|
||||
{
|
||||
private static readonly string[] lookup_names = { "Gameplay/metronomelow", "Gameplay/catch-banana" };
|
||||
|
||||
public override IEnumerable<string> LookupNames => lookup_names;
|
||||
|
||||
public BananaHitSampleInfo(int volume = 0)
|
||||
public BananaHitSampleInfo(int volume = 100)
|
||||
: base(string.Empty, volume: volume)
|
||||
{
|
||||
}
|
||||
|
||||
public sealed override HitSampleInfo With(Optional<string> newName = default, Optional<string?> newBank = default, Optional<string?> newSuffix = default, Optional<int> newVolume = default)
|
||||
public sealed override HitSampleInfo With(Optional<string> newName = default, Optional<string> newBank = default, Optional<string?> newSuffix = default, Optional<int> newVolume = default)
|
||||
=> new BananaHitSampleInfo(newVolume.GetOr(Volume));
|
||||
|
||||
public bool Equals(BananaHitSampleInfo? other)
|
||||
|
@ -1,7 +1,9 @@
|
||||
// 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.Threading;
|
||||
using osu.Game.Audio;
|
||||
using osu.Game.Rulesets.Judgements;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
|
||||
@ -39,6 +41,7 @@ namespace osu.Game.Rulesets.Catch.Objects
|
||||
{
|
||||
StartTime = time,
|
||||
BananaIndex = i,
|
||||
Samples = new List<HitSampleInfo> { new Banana.BananaHitSampleInfo(CreateHitSampleInfo().Volume) }
|
||||
});
|
||||
|
||||
time += spacing;
|
||||
|
@ -6,6 +6,7 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using Newtonsoft.Json;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Game.Audio;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
@ -16,7 +17,7 @@ using osu.Game.Rulesets.Objects.Types;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Objects
|
||||
{
|
||||
public class JuiceStream : CatchHitObject, IHasPathWithRepeats
|
||||
public class JuiceStream : CatchHitObject, IHasPathWithRepeats, IHasSliderVelocity
|
||||
{
|
||||
/// <summary>
|
||||
/// Positional distance that results in a duration of one second, before any speed adjustments.
|
||||
@ -27,6 +28,19 @@ namespace osu.Game.Rulesets.Catch.Objects
|
||||
|
||||
public int RepeatCount { get; set; }
|
||||
|
||||
public BindableNumber<double> SliderVelocityBindable { get; } = new BindableDouble(1)
|
||||
{
|
||||
Precision = 0.01,
|
||||
MinValue = 0.1,
|
||||
MaxValue = 10
|
||||
};
|
||||
|
||||
public double SliderVelocity
|
||||
{
|
||||
get => SliderVelocityBindable.Value;
|
||||
set => SliderVelocityBindable.Value = value;
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
private double velocityFactor;
|
||||
|
||||
@ -34,10 +48,10 @@ namespace osu.Game.Rulesets.Catch.Objects
|
||||
private double tickDistanceFactor;
|
||||
|
||||
[JsonIgnore]
|
||||
public double Velocity => velocityFactor * DifficultyControlPoint.SliderVelocity;
|
||||
public double Velocity => velocityFactor * SliderVelocity;
|
||||
|
||||
[JsonIgnore]
|
||||
public double TickDistance => tickDistanceFactor * DifficultyControlPoint.SliderVelocity;
|
||||
public double TickDistance => tickDistanceFactor * SliderVelocity;
|
||||
|
||||
/// <summary>
|
||||
/// The length of one span of this <see cref="JuiceStream"/>.
|
||||
|
@ -1,8 +1,6 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
@ -40,7 +38,7 @@ namespace osu.Game.Rulesets.Mania.Tests
|
||||
private const double time_tail = 4000;
|
||||
private const double time_after_tail = 5250;
|
||||
|
||||
private List<JudgementResult> judgementResults;
|
||||
private List<JudgementResult> judgementResults = new List<JudgementResult>();
|
||||
|
||||
/// <summary>
|
||||
/// -----[ ]-----
|
||||
@ -61,6 +59,44 @@ namespace osu.Game.Rulesets.Mania.Tests
|
||||
assertNoteJudgement(HitResult.IgnoreMiss);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// -----[ ]-----
|
||||
/// x o
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestCorrectInput()
|
||||
{
|
||||
performTest(new List<ReplayFrame>
|
||||
{
|
||||
new ManiaReplayFrame(time_head, ManiaAction.Key1),
|
||||
new ManiaReplayFrame(time_tail),
|
||||
});
|
||||
|
||||
assertHeadJudgement(HitResult.Perfect);
|
||||
assertTickJudgement(HitResult.LargeTickHit);
|
||||
assertTailJudgement(HitResult.Perfect);
|
||||
assertNoteJudgement(HitResult.IgnoreHit);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// -----[ ]-----
|
||||
/// x o
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestLateRelease()
|
||||
{
|
||||
performTest(new List<ReplayFrame>
|
||||
{
|
||||
new ManiaReplayFrame(time_head, ManiaAction.Key1),
|
||||
new ManiaReplayFrame(time_after_tail),
|
||||
});
|
||||
|
||||
assertHeadJudgement(HitResult.Perfect);
|
||||
assertTickJudgement(HitResult.LargeTickHit);
|
||||
assertTailJudgement(HitResult.Miss);
|
||||
assertNoteJudgement(HitResult.IgnoreMiss);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// -----[ ]-----
|
||||
/// x o
|
||||
@ -521,9 +557,9 @@ namespace osu.Game.Rulesets.Mania.Tests
|
||||
private void assertLastTickJudgement(HitResult result)
|
||||
=> AddAssert($"last tick judged as {result}", () => judgementResults.Last(j => j.HitObject is HoldNoteTick).Type, () => Is.EqualTo(result));
|
||||
|
||||
private ScoreAccessibleReplayPlayer currentPlayer;
|
||||
private ScoreAccessibleReplayPlayer currentPlayer = null!;
|
||||
|
||||
private void performTest(List<ReplayFrame> frames, Beatmap<ManiaHitObject> beatmap = null)
|
||||
private void performTest(List<ReplayFrame> frames, Beatmap<ManiaHitObject>? beatmap = null)
|
||||
{
|
||||
if (beatmap == null)
|
||||
{
|
||||
@ -569,15 +605,13 @@ namespace osu.Game.Rulesets.Mania.Tests
|
||||
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 == true);
|
||||
AddUntilStep("Wait for completion", () => currentPlayer.ScoreProcessor.HasCompleted.Value);
|
||||
}
|
||||
|
||||
private partial class ScoreAccessibleReplayPlayer : ReplayPlayer
|
||||
{
|
||||
public new ScoreProcessor ScoreProcessor => base.ScoreProcessor;
|
||||
|
||||
public new GameplayClockContainer GameplayClockContainer => base.GameplayClockContainer;
|
||||
|
||||
protected override bool PauseOnFocusLost => false;
|
||||
|
||||
public ScoreAccessibleReplayPlayer(Score score)
|
||||
|
@ -14,7 +14,6 @@ using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
using osu.Game.Rulesets.Mania.Objects;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Beatmaps.Formats;
|
||||
using osu.Game.Utils;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
||||
@ -49,15 +48,14 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
||||
Debug.Assert(distanceData != null);
|
||||
|
||||
TimingControlPoint timingPoint = beatmap.ControlPointInfo.TimingPointAt(hitObject.StartTime);
|
||||
DifficultyControlPoint difficultyPoint = hitObject.DifficultyControlPoint;
|
||||
|
||||
double beatLength;
|
||||
#pragma warning disable 618
|
||||
if (difficultyPoint is LegacyBeatmapDecoder.LegacyDifficultyControlPoint legacyDifficultyPoint)
|
||||
#pragma warning restore 618
|
||||
beatLength = timingPoint.BeatLength * legacyDifficultyPoint.BpmMultiplier;
|
||||
if (hitObject.LegacyBpmMultiplier.HasValue)
|
||||
beatLength = timingPoint.BeatLength * hitObject.LegacyBpmMultiplier.Value;
|
||||
else if (hitObject is IHasSliderVelocity hasSliderVelocity)
|
||||
beatLength = timingPoint.BeatLength / hasSliderVelocity.SliderVelocity;
|
||||
else
|
||||
beatLength = timingPoint.BeatLength / difficultyPoint.SliderVelocity;
|
||||
beatLength = timingPoint.BeatLength;
|
||||
|
||||
SpanCount = repeatsData?.SpanCount() ?? 1;
|
||||
StartTime = (int)Math.Round(hitObject.StartTime);
|
||||
|
@ -25,6 +25,8 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
|
||||
[Resolved]
|
||||
private IScrollingInfo scrollingInfo { get; set; }
|
||||
|
||||
protected override bool IsValidForPlacement => HitObject.Duration > 0;
|
||||
|
||||
public HoldNotePlacementBlueprint()
|
||||
: base(new HoldNote())
|
||||
{
|
||||
@ -75,7 +77,7 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
|
||||
return;
|
||||
|
||||
base.OnMouseUp(e);
|
||||
EndPlacement(HitObject.Duration > 0);
|
||||
EndPlacement(true);
|
||||
}
|
||||
|
||||
private double originalStartTime;
|
||||
|
@ -27,7 +27,7 @@ namespace osu.Game.Rulesets.Mania.Edit.Setup
|
||||
specialStyle = new LabelledSwitchButton
|
||||
{
|
||||
Label = "Use special (N+1) style",
|
||||
Description = "Changes one column to act as a classic \"scratch\" or \"special\" column, which can be moved around by the user's skin (to the left/right/centre). Generally used in 5k (4+1) or 8key (7+1) configurations.",
|
||||
Description = "Changes one column to act as a classic \"scratch\" or \"special\" column, which can be moved around by the user's skin (to the left/right/centre). Generally used in 6K (5+1) or 8K (7+1) configurations.",
|
||||
Current = { Value = Beatmap.BeatmapInfo.SpecialStyle }
|
||||
}
|
||||
};
|
||||
|
@ -3,7 +3,6 @@
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
@ -219,6 +218,9 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
||||
if (Time.Current < releaseTime)
|
||||
releaseTime = null;
|
||||
|
||||
if (Time.Current < HoldStartTime)
|
||||
endHold();
|
||||
|
||||
// Pad the full size container so its contents (i.e. the masking container) reach under the tail.
|
||||
// This is required for the tail to not be masked away, since it lies outside the bounds of the hold note.
|
||||
sizingContainer.Padding = new MarginPadding
|
||||
@ -245,9 +247,9 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
||||
// 2. The head note will move along with the new "head position" in the container.
|
||||
if (Head.IsHit && releaseTime == null && DrawHeight > 0)
|
||||
{
|
||||
// How far past the hit target this hold note is. Always a positive value.
|
||||
float yOffset = Math.Max(0, Direction.Value == ScrollingDirection.Up ? -Y : Y);
|
||||
sizingContainer.Height = Math.Clamp(1 - yOffset / DrawHeight, 0, 1);
|
||||
// How far past the hit target this hold note is.
|
||||
float yOffset = Direction.Value == ScrollingDirection.Up ? -Y : Y;
|
||||
sizingContainer.Height = 1 - yOffset / DrawHeight;
|
||||
}
|
||||
}
|
||||
|
||||
@ -322,14 +324,14 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
||||
if (e.Action != Action.Value)
|
||||
return;
|
||||
|
||||
// do not run any of this logic when rewinding, as it inverts order of presses/releases.
|
||||
if (Time.Elapsed < 0)
|
||||
return;
|
||||
|
||||
// Make sure a hold was started
|
||||
if (HoldStartTime == null)
|
||||
return;
|
||||
|
||||
// do not run any of this logic when rewinding, as it inverts order of presses/releases.
|
||||
if (Time.Elapsed < 0)
|
||||
return;
|
||||
|
||||
Tail.UpdateResult();
|
||||
endHold();
|
||||
|
||||
@ -350,13 +352,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
||||
{
|
||||
// Note: base.LoadSamples() isn't called since the slider plays the tail's hitsounds for the time being.
|
||||
|
||||
if (HitObject.SampleControlPoint == null)
|
||||
{
|
||||
throw new InvalidOperationException($"{nameof(HitObject)}s must always have an attached {nameof(HitObject.SampleControlPoint)}."
|
||||
+ $" This is an indication that {nameof(HitObject.ApplyDefaults)} has not been invoked on {this}.");
|
||||
}
|
||||
|
||||
slidingSample.Samples = HitObject.CreateSlidingSamples().Select(s => HitObject.SampleControlPoint.ApplyTo(s)).Cast<ISampleInfo>().ToArray();
|
||||
slidingSample.Samples = HitObject.CreateSlidingSamples().Cast<ISampleInfo>().ToArray();
|
||||
}
|
||||
|
||||
public override void StopAllSamples()
|
||||
|
@ -10,7 +10,7 @@
|
||||
["Gameplay/soft-hitnormal"],
|
||||
["Gameplay/drum-hitnormal"]
|
||||
],
|
||||
"Samples": ["Gameplay/-hitnormal"]
|
||||
"Samples": ["Gameplay/normal-hitnormal"]
|
||||
}, {
|
||||
"StartTime": 1875.0,
|
||||
"EndTime": 2750.0,
|
||||
@ -19,7 +19,7 @@
|
||||
["Gameplay/soft-hitnormal"],
|
||||
["Gameplay/drum-hitnormal"]
|
||||
],
|
||||
"Samples": ["Gameplay/-hitnormal"]
|
||||
"Samples": ["Gameplay/normal-hitnormal"]
|
||||
}]
|
||||
}, {
|
||||
"StartTime": 3750.0,
|
||||
|
@ -138,8 +138,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
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);
|
||||
&& mergedSlider.Samples.SequenceEqual(slider1.Samples);
|
||||
});
|
||||
|
||||
AddAssert("slider end is at same completion for last slider", () =>
|
||||
|
@ -181,10 +181,8 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
{
|
||||
if (slider is null) return;
|
||||
|
||||
slider.SampleControlPoint.SampleBank = "soft";
|
||||
slider.SampleControlPoint.SampleVolume = 70;
|
||||
sample = new HitSampleInfo("hitwhistle");
|
||||
slider.Samples.Add(sample);
|
||||
sample = new HitSampleInfo("hitwhistle", HitSampleInfo.BANK_SOFT, volume: 70);
|
||||
slider.Samples.Add(sample.With());
|
||||
});
|
||||
|
||||
AddStep("select added slider", () =>
|
||||
@ -207,9 +205,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
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));
|
||||
EditorBeatmap.HitObjects.All(o => o.Samples.Contains(sample));
|
||||
}
|
||||
|
||||
private bool sliderCreatedFor(Slider s, double startTime, double endTime, params (Vector2 pos, PathType? pathType)[] expectedControlPoints)
|
||||
|
@ -199,8 +199,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
Precision.AlmostEquals(circle.StartTime, time, 1)
|
||||
&& Precision.AlmostEquals(circle.Position, position, 0.01f)
|
||||
&& circle.NewCombo == startsNewCombo
|
||||
&& circle.Samples.SequenceEqual(slider.HeadCircle.Samples)
|
||||
&& circle.SampleControlPoint.IsRedundant(slider.SampleControlPoint);
|
||||
&& circle.Samples.SequenceEqual(slider.HeadCircle.Samples);
|
||||
}
|
||||
|
||||
private bool sliderRestored(Slider slider)
|
||||
|
19
osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModBubbles.cs
Normal file
19
osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModBubbles.cs
Normal file
@ -0,0 +1,19 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using NUnit.Framework;
|
||||
using osu.Game.Rulesets.Osu.Mods;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Tests.Mods
|
||||
{
|
||||
public partial class TestSceneOsuModBubbles : OsuModTestScene
|
||||
{
|
||||
[Test]
|
||||
public void TestOsuModBubbles() => CreateModTest(new ModTestData
|
||||
{
|
||||
Mod = new OsuModBubbles(),
|
||||
Autoplay = true,
|
||||
PassCondition = () => true
|
||||
});
|
||||
}
|
||||
}
|
Binary file not shown.
After Width: | Height: | Size: 2.1 KiB |
@ -8,6 +8,7 @@ using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio.Sample;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.ObjectExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
@ -18,6 +19,7 @@ using osu.Framework.Testing.Input;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Audio;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Rulesets.Osu.Configuration;
|
||||
using osu.Game.Rulesets.Osu.Skinning;
|
||||
using osu.Game.Rulesets.Osu.UI.Cursor;
|
||||
using osu.Game.Screens.Play;
|
||||
@ -40,6 +42,8 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
|
||||
private Drawable background;
|
||||
|
||||
private readonly Bindable<bool> ripples = new Bindable<bool>();
|
||||
|
||||
public TestSceneGameplayCursor()
|
||||
{
|
||||
var ruleset = new OsuRuleset();
|
||||
@ -57,6 +61,8 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
});
|
||||
});
|
||||
|
||||
AddToggleStep("ripples", v => ripples.Value = v);
|
||||
|
||||
AddSliderStep("circle size", 0f, 10f, 0f, val =>
|
||||
{
|
||||
config.SetValue(OsuSetting.AutoCursorSize, true);
|
||||
@ -67,6 +73,13 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
AddStep("test cursor container", () => loadContent(false));
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
var rulesetConfig = (OsuRulesetConfigManager)RulesetConfigs.GetConfigFor(Ruleset.Value.CreateInstance()).AsNonNull();
|
||||
rulesetConfig.BindWith(OsuRulesetSetting.ShowCursorRipples, ripples);
|
||||
}
|
||||
|
||||
[TestCase(1, 1)]
|
||||
[TestCase(5, 1)]
|
||||
[TestCase(10, 1)]
|
||||
|
@ -439,7 +439,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
{
|
||||
public TestSlider()
|
||||
{
|
||||
DifficultyControlPoint = new DifficultyControlPoint { SliderVelocity = 0.1f };
|
||||
SliderVelocity = 0.1f;
|
||||
|
||||
DefaultsApplied += _ =>
|
||||
{
|
||||
|
@ -15,6 +15,10 @@ using osuTK.Graphics;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.ObjectExtensions;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Beatmaps.Legacy;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
@ -22,6 +26,7 @@ using osu.Game.Rulesets.Judgements;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
using osu.Game.Rulesets.Osu.Configuration;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Tests
|
||||
{
|
||||
@ -30,6 +35,27 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
{
|
||||
private int depthIndex;
|
||||
|
||||
private readonly BindableBool snakingIn = new BindableBool();
|
||||
private readonly BindableBool snakingOut = new BindableBool();
|
||||
|
||||
[SetUpSteps]
|
||||
public void SetUpSteps()
|
||||
{
|
||||
AddToggleStep("toggle snaking", v =>
|
||||
{
|
||||
snakingIn.Value = v;
|
||||
snakingOut.Value = v;
|
||||
});
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
var config = (OsuRulesetConfigManager)RulesetConfigs.GetConfigFor(Ruleset.Value.CreateInstance()).AsNonNull();
|
||||
config.BindWith(OsuRulesetSetting.SnakingInSliders, snakingIn);
|
||||
config.BindWith(OsuRulesetSetting.SnakingOutSliders, snakingOut);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestVariousSliders()
|
||||
{
|
||||
|
@ -7,7 +7,6 @@ using NUnit.Framework;
|
||||
using osu.Framework.Screens;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Replays;
|
||||
using osu.Game.Rulesets.Judgements;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
@ -47,7 +46,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
{
|
||||
StartTime = time_slider_start,
|
||||
Position = new Vector2(0, 0),
|
||||
DifficultyControlPoint = new DifficultyControlPoint { SliderVelocity = velocity },
|
||||
SliderVelocity = velocity,
|
||||
Path = new SliderPath(PathType.Linear, new[]
|
||||
{
|
||||
Vector2.Zero,
|
||||
|
@ -8,7 +8,6 @@ using System.Linq;
|
||||
using NUnit.Framework;
|
||||
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.Objects;
|
||||
@ -350,7 +349,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
{
|
||||
StartTime = time_slider_start,
|
||||
Position = new Vector2(0, 0),
|
||||
DifficultyControlPoint = new DifficultyControlPoint { SliderVelocity = 0.1f },
|
||||
SliderVelocity = 0.1f,
|
||||
Path = new SliderPath(PathType.PerfectCurve, new[]
|
||||
{
|
||||
Vector2.Zero,
|
||||
|
@ -74,7 +74,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
EndTime = Time.Current + delay + length,
|
||||
Samples = new List<HitSampleInfo>
|
||||
{
|
||||
new HitSampleInfo("hitnormal")
|
||||
new HitSampleInfo(HitSampleInfo.HIT_NORMAL)
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -399,7 +399,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
{
|
||||
public TestSlider()
|
||||
{
|
||||
DifficultyControlPoint = new DifficultyControlPoint { SliderVelocity = 0.1f };
|
||||
SliderVelocity = 0.1f;
|
||||
|
||||
DefaultsApplied += _ =>
|
||||
{
|
||||
|
@ -30,6 +30,8 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
|
||||
{
|
||||
var positionData = original as IHasPosition;
|
||||
var comboData = original as IHasCombo;
|
||||
var sliderVelocityData = original as IHasSliderVelocity;
|
||||
var generateTicksData = original as IHasGenerateTicks;
|
||||
|
||||
switch (original)
|
||||
{
|
||||
@ -47,7 +49,9 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
|
||||
LegacyLastTickOffset = (original as IHasLegacyLastTickOffset)?.LegacyLastTickOffset,
|
||||
// prior to v8, speed multipliers don't adjust for how many ticks are generated over the same distance.
|
||||
// this results in more (or less) ticks being generated in <v8 maps for the same time duration.
|
||||
TickDistanceMultiplier = beatmap.BeatmapInfo.BeatmapVersion < 8 ? 1f / ((LegacyControlPointInfo)beatmap.ControlPointInfo).DifficultyPointAt(original.StartTime).SliderVelocity : 1
|
||||
TickDistanceMultiplier = beatmap.BeatmapInfo.BeatmapVersion < 8 ? 1f / ((LegacyControlPointInfo)beatmap.ControlPointInfo).DifficultyPointAt(original.StartTime).SliderVelocity : 1,
|
||||
GenerateTicks = generateTicksData?.GenerateTicks ?? true,
|
||||
SliderVelocity = sliderVelocityData?.SliderVelocity ?? 1,
|
||||
}.Yield();
|
||||
|
||||
case IHasDuration endTimeData:
|
||||
|
@ -22,6 +22,7 @@ namespace osu.Game.Rulesets.Osu.Configuration
|
||||
SetDefault(OsuRulesetSetting.SnakingInSliders, true);
|
||||
SetDefault(OsuRulesetSetting.SnakingOutSliders, true);
|
||||
SetDefault(OsuRulesetSetting.ShowCursorTrail, true);
|
||||
SetDefault(OsuRulesetSetting.ShowCursorRipples, false);
|
||||
SetDefault(OsuRulesetSetting.PlayfieldBorderStyle, PlayfieldBorderStyle.None);
|
||||
}
|
||||
}
|
||||
@ -31,6 +32,7 @@ namespace osu.Game.Rulesets.Osu.Configuration
|
||||
SnakingInSliders,
|
||||
SnakingOutSliders,
|
||||
ShowCursorTrail,
|
||||
ShowCursorRipples,
|
||||
PlayfieldBorderStyle,
|
||||
}
|
||||
}
|
||||
|
@ -309,7 +309,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
||||
}
|
||||
else
|
||||
{
|
||||
var result = snapProvider?.FindSnappedPositionAndTime(Parent.ToScreenSpace(e.MousePosition));
|
||||
var result = snapProvider?.FindSnappedPositionAndTime(Parent.ToScreenSpace(e.MousePosition), SnapType.GlobalGrids);
|
||||
|
||||
Vector2 movementDelta = Parent.ToLocalSpace(result?.ScreenSpacePosition ?? Parent.ToScreenSpace(e.MousePosition)) - dragStartPositions[draggedControlPointIndex] - hitObject.Position;
|
||||
|
||||
|
@ -10,7 +10,6 @@ using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Input;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
@ -42,6 +41,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
||||
[Resolved(CanBeNull = true)]
|
||||
private IDistanceSnapProvider snapProvider { get; set; }
|
||||
|
||||
protected override bool IsValidForPlacement => HitObject.Path.HasValidLength;
|
||||
|
||||
public SliderPlacementBlueprint()
|
||||
: base(new Slider())
|
||||
{
|
||||
@ -83,11 +84,10 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
||||
case SliderPlacementState.Initial:
|
||||
BeginPlacement();
|
||||
|
||||
var nearestDifficultyPoint = editorBeatmap.HitObjects
|
||||
.LastOrDefault(h => h is Slider && h.GetEndTime() < HitObject.StartTime)?
|
||||
.DifficultyControlPoint?.DeepClone() as DifficultyControlPoint;
|
||||
double? nearestSliderVelocity = (editorBeatmap.HitObjects
|
||||
.LastOrDefault(h => h is Slider && h.GetEndTime() < HitObject.StartTime) as Slider)?.SliderVelocity;
|
||||
|
||||
HitObject.DifficultyControlPoint = nearestDifficultyPoint ?? new DifficultyControlPoint();
|
||||
HitObject.SliderVelocity = nearestSliderVelocity ?? 1;
|
||||
HitObject.Position = ToLocalSpace(result.ScreenSpacePosition);
|
||||
|
||||
// Replacing the DifficultyControlPoint above doesn't trigger any kind of invalidation.
|
||||
@ -152,7 +152,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
||||
private void endCurve()
|
||||
{
|
||||
updateSlider();
|
||||
EndPlacement(HitObject.Path.HasValidLength);
|
||||
EndPlacement(true);
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
@ -198,7 +198,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
||||
}
|
||||
|
||||
// Update the cursor position.
|
||||
var result = snapProvider?.FindSnappedPositionAndTime(inputManager.CurrentState.Mouse.Position);
|
||||
var result = snapProvider?.FindSnappedPositionAndTime(inputManager.CurrentState.Mouse.Position, state == SliderPlacementState.Body ? SnapType.GlobalGrids : SnapType.All);
|
||||
cursor.Position = ToLocalSpace(result?.ScreenSpacePosition ?? inputManager.CurrentState.Mouse.Position) - HitObject.Position;
|
||||
}
|
||||
else if (cursor != null)
|
||||
|
@ -14,7 +14,6 @@ using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Audio;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
@ -311,17 +310,11 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
||||
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,
|
||||
@ -378,15 +371,11 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
||||
|
||||
Vector2 position = HitObject.Position + HitObject.Path.PositionAt(pathPosition);
|
||||
|
||||
var samplePoint = (SampleControlPoint)HitObject.SampleControlPoint.DeepClone();
|
||||
samplePoint.Time = time;
|
||||
|
||||
editorBeatmap.Add(new HitCircle
|
||||
{
|
||||
StartTime = time,
|
||||
Position = position,
|
||||
NewCombo = i == 0 && HitObject.NewCombo,
|
||||
SampleControlPoint = samplePoint,
|
||||
Samples = HitObject.HeadCircle.Samples.Select(s => s.With()).ToList()
|
||||
});
|
||||
|
||||
|
@ -1,10 +1,7 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using JetBrains.Annotations;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Input.Events;
|
||||
@ -24,9 +21,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners
|
||||
|
||||
private bool isPlacingEnd;
|
||||
|
||||
[Resolved(CanBeNull = true)]
|
||||
[CanBeNull]
|
||||
private IBeatSnapProvider beatSnapProvider { get; set; }
|
||||
[Resolved]
|
||||
private IBeatSnapProvider? beatSnapProvider { get; set; }
|
||||
|
||||
public SpinnerPlacementBlueprint()
|
||||
: base(new Spinner { Position = OsuPlayfield.BASE_SIZE / 2 })
|
||||
|
@ -13,8 +13,8 @@ using osu.Framework.Extensions.EnumExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Input.Bindings;
|
||||
@ -62,7 +62,12 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
private void load()
|
||||
{
|
||||
// Give a bit of breathing room around the playfield content.
|
||||
PlayfieldContentContainer.Padding = new MarginPadding(10);
|
||||
PlayfieldContentContainer.Padding = new MarginPadding
|
||||
{
|
||||
Vertical = 10,
|
||||
Left = TOOLBOX_CONTRACTED_SIZE_LEFT + 10,
|
||||
Right = TOOLBOX_CONTRACTED_SIZE_RIGHT + 10,
|
||||
};
|
||||
|
||||
LayerBelowRuleset.AddRange(new Drawable[]
|
||||
{
|
||||
@ -138,7 +143,7 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
// We want to ensure that in this particular case, the time-snapping component of distance snap is still applied.
|
||||
// The easiest way to ensure this is to attempt application of distance snap after a nearby object is found, and copy over
|
||||
// the time value if the proposed positions are roughly the same.
|
||||
if (snapType.HasFlagFast(SnapType.Grids) && DistanceSnapToggle.Value == TernaryState.True && distanceSnapGrid != null)
|
||||
if (snapType.HasFlagFast(SnapType.RelativeGrids) && DistanceSnapToggle.Value == TernaryState.True && distanceSnapGrid != null)
|
||||
{
|
||||
(Vector2 distanceSnappedPosition, double distanceSnappedTime) = distanceSnapGrid.GetSnappedPosition(distanceSnapGrid.ToLocalSpace(snapResult.ScreenSpacePosition));
|
||||
if (Precision.AlmostEquals(distanceSnapGrid.ToScreenSpace(distanceSnappedPosition), snapResult.ScreenSpacePosition, 1))
|
||||
@ -150,7 +155,7 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
|
||||
SnapResult result = base.FindSnappedPositionAndTime(screenSpacePosition, snapType);
|
||||
|
||||
if (snapType.HasFlagFast(SnapType.Grids))
|
||||
if (snapType.HasFlagFast(SnapType.RelativeGrids))
|
||||
{
|
||||
if (DistanceSnapToggle.Value == TernaryState.True && distanceSnapGrid != null)
|
||||
{
|
||||
@ -159,7 +164,10 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
result.ScreenSpacePosition = distanceSnapGrid.ToScreenSpace(pos);
|
||||
result.Time = time;
|
||||
}
|
||||
}
|
||||
|
||||
if (snapType.HasFlagFast(SnapType.GlobalGrids))
|
||||
{
|
||||
if (rectangularGridSnapToggle.Value == TernaryState.True)
|
||||
{
|
||||
Vector2 pos = rectangularPositionSnapGrid.GetSnappedPosition(rectangularPositionSnapGrid.ToLocalSpace(result.ScreenSpacePosition));
|
||||
|
@ -362,7 +362,6 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
StartTime = firstHitObject.StartTime,
|
||||
Position = firstHitObject.Position,
|
||||
NewCombo = firstHitObject.NewCombo,
|
||||
SampleControlPoint = firstHitObject.SampleControlPoint,
|
||||
Samples = firstHitObject.Samples,
|
||||
};
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
@ -10,6 +11,8 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
{
|
||||
public class OsuModBarrelRoll : ModBarrelRoll<OsuHitObject>, IApplicableToDrawableHitObject
|
||||
{
|
||||
public override Type[] IncompatibleMods => new[] { typeof(OsuModBubbles) };
|
||||
|
||||
public void ApplyToDrawableHitObject(DrawableHitObject d)
|
||||
{
|
||||
d.OnUpdate += _ =>
|
||||
|
214
osu.Game.Rulesets.Osu/Mods/OsuModBubbles.cs
Normal file
214
osu.Game.Rulesets.Osu/Mods/OsuModBubbles.cs
Normal file
@ -0,0 +1,214 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Extensions.ObjectExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Colour;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Effects;
|
||||
using osu.Framework.Graphics.Pooling;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Rulesets.UI;
|
||||
using osu.Game.Scoring;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Mods
|
||||
{
|
||||
public partial class OsuModBubbles : Mod, IApplicableToDrawableRuleset<OsuHitObject>, IApplicableToDrawableHitObject, IApplicableToScoreProcessor
|
||||
{
|
||||
public override string Name => "Bubbles";
|
||||
|
||||
public override string Acronym => "BU";
|
||||
|
||||
public override LocalisableString Description => "Don't let their popping distract you!";
|
||||
|
||||
public override double ScoreMultiplier => 1;
|
||||
|
||||
public override ModType Type => ModType.Fun;
|
||||
|
||||
// Compatibility with these seems potentially feasible in the future, blocked for now because they don't work as one would expect
|
||||
public override Type[] IncompatibleMods => new[] { typeof(OsuModBarrelRoll), typeof(OsuModMagnetised), typeof(OsuModRepel) };
|
||||
|
||||
private PlayfieldAdjustmentContainer bubbleContainer = null!;
|
||||
|
||||
private readonly Bindable<int> currentCombo = new BindableInt();
|
||||
|
||||
private float maxSize;
|
||||
private float bubbleSize;
|
||||
private double bubbleFade;
|
||||
|
||||
private readonly DrawablePool<BubbleDrawable> bubblePool = new DrawablePool<BubbleDrawable>(100);
|
||||
|
||||
public ScoreRank AdjustRank(ScoreRank rank, double accuracy) => rank;
|
||||
|
||||
public void ApplyToScoreProcessor(ScoreProcessor scoreProcessor)
|
||||
{
|
||||
currentCombo.BindTo(scoreProcessor.Combo);
|
||||
currentCombo.BindValueChanged(combo =>
|
||||
maxSize = Math.Min(1.75f, (float)(1.25 + 0.005 * combo.NewValue)), true);
|
||||
}
|
||||
|
||||
public void ApplyToDrawableRuleset(DrawableRuleset<OsuHitObject> drawableRuleset)
|
||||
{
|
||||
// Multiplying by 2 results in an initial size that is too large, hence 1.90 has been chosen
|
||||
// Also avoids the HitObject bleeding around the edges of the bubble drawable at minimum size
|
||||
bubbleSize = (float)(drawableRuleset.Beatmap.HitObjects.OfType<HitCircle>().First().Radius * 1.90f);
|
||||
bubbleFade = drawableRuleset.Beatmap.HitObjects.OfType<HitCircle>().First().TimePreempt * 2;
|
||||
|
||||
// We want to hide the judgements since they are obscured by the BubbleDrawable (due to layering)
|
||||
drawableRuleset.Playfield.DisplayJudgements.Value = false;
|
||||
|
||||
bubbleContainer = drawableRuleset.CreatePlayfieldAdjustmentContainer();
|
||||
|
||||
drawableRuleset.Overlays.Add(bubbleContainer);
|
||||
}
|
||||
|
||||
public void ApplyToDrawableHitObject(DrawableHitObject drawableObject)
|
||||
{
|
||||
drawableObject.OnNewResult += (drawable, _) =>
|
||||
{
|
||||
if (drawable is not DrawableOsuHitObject drawableOsuHitObject) return;
|
||||
|
||||
switch (drawableOsuHitObject.HitObject)
|
||||
{
|
||||
case Slider:
|
||||
case SpinnerTick:
|
||||
break;
|
||||
|
||||
default:
|
||||
addBubble();
|
||||
break;
|
||||
}
|
||||
|
||||
void addBubble()
|
||||
{
|
||||
BubbleDrawable bubble = bubblePool.Get();
|
||||
|
||||
bubble.DrawableOsuHitObject = drawableOsuHitObject;
|
||||
bubble.InitialSize = new Vector2(bubbleSize);
|
||||
bubble.FadeTime = bubbleFade;
|
||||
bubble.MaxSize = maxSize;
|
||||
|
||||
bubbleContainer.Add(bubble);
|
||||
}
|
||||
};
|
||||
|
||||
drawableObject.OnRevertResult += (drawable, _) =>
|
||||
{
|
||||
if (drawable.HitObject is SpinnerTick or Slider) return;
|
||||
|
||||
BubbleDrawable? lastBubble = bubbleContainer.OfType<BubbleDrawable>().LastOrDefault();
|
||||
|
||||
lastBubble?.ClearTransforms();
|
||||
lastBubble?.Expire(true);
|
||||
};
|
||||
}
|
||||
|
||||
#region Pooled Bubble drawable
|
||||
|
||||
private partial class BubbleDrawable : PoolableDrawable
|
||||
{
|
||||
public DrawableOsuHitObject? DrawableOsuHitObject { get; set; }
|
||||
|
||||
public Vector2 InitialSize { get; set; }
|
||||
|
||||
public float MaxSize { get; set; }
|
||||
|
||||
public double FadeTime { get; set; }
|
||||
|
||||
private readonly Box colourBox;
|
||||
private readonly CircularContainer content;
|
||||
|
||||
public BubbleDrawable()
|
||||
{
|
||||
Origin = Anchor.Centre;
|
||||
InternalChild = content = new CircularContainer
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
MaskingSmoothness = 2,
|
||||
BorderThickness = 0,
|
||||
BorderColour = Colour4.White,
|
||||
Masking = true,
|
||||
EdgeEffect = new EdgeEffectParameters
|
||||
{
|
||||
Type = EdgeEffectType.Shadow,
|
||||
Radius = 3,
|
||||
Colour = Colour4.Black.Opacity(0.05f),
|
||||
},
|
||||
Child = colourBox = new Box { RelativeSizeAxes = Axes.Both, }
|
||||
};
|
||||
}
|
||||
|
||||
protected override void PrepareForUse()
|
||||
{
|
||||
Debug.Assert(DrawableOsuHitObject.IsNotNull());
|
||||
|
||||
Colour = DrawableOsuHitObject.IsHit ? Colour4.White : Colour4.Black;
|
||||
Scale = new Vector2(1);
|
||||
Position = getPosition(DrawableOsuHitObject);
|
||||
Size = InitialSize;
|
||||
|
||||
//We want to fade to a darker colour to avoid colours such as white hiding the "ripple" effect.
|
||||
ColourInfo colourDarker = DrawableOsuHitObject.AccentColour.Value.Darken(0.1f);
|
||||
|
||||
// The absolute length of the bubble's animation, can be used in fractions for animations of partial length
|
||||
double duration = 1700 + Math.Pow(FadeTime, 1.07f);
|
||||
|
||||
// Main bubble scaling based on combo
|
||||
this.FadeTo(1)
|
||||
.ScaleTo(MaxSize, duration * 0.8f)
|
||||
.Then()
|
||||
// Pop at the end of the bubbles life time
|
||||
.ScaleTo(MaxSize * 1.5f, duration * 0.2f, Easing.OutQuint)
|
||||
.FadeOut(duration * 0.2f, Easing.OutCirc).Expire();
|
||||
|
||||
if (!DrawableOsuHitObject.IsHit) return;
|
||||
|
||||
content.BorderThickness = InitialSize.X / 3.5f;
|
||||
content.BorderColour = Colour4.White;
|
||||
|
||||
colourBox.FadeColour(colourDarker);
|
||||
|
||||
content.TransformTo(nameof(BorderColour), colourDarker, duration * 0.3f, Easing.OutQuint);
|
||||
// Ripple effect utilises the border to reduce drawable count
|
||||
content.TransformTo(nameof(BorderThickness), 2f, duration * 0.3f, Easing.OutQuint)
|
||||
.Then()
|
||||
// Avoids transparency overlap issues during the bubble "pop"
|
||||
.TransformTo(nameof(BorderThickness), 0f);
|
||||
}
|
||||
|
||||
private Vector2 getPosition(DrawableOsuHitObject drawableObject)
|
||||
{
|
||||
switch (drawableObject)
|
||||
{
|
||||
// SliderHeads are derived from HitCircles,
|
||||
// so we must handle them before to avoid them using the wrong positioning logic
|
||||
case DrawableSliderHead:
|
||||
return drawableObject.HitObject.Position;
|
||||
|
||||
// Using hitobject position will cause issues with HitCircle placement due to stack leniency.
|
||||
case DrawableHitCircle:
|
||||
return drawableObject.Position;
|
||||
|
||||
default:
|
||||
return drawableObject.HitObject.Position;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
@ -27,7 +27,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
public override ModType Type => ModType.Fun;
|
||||
public override LocalisableString Description => "No need to chase the circles – your cursor is a magnet!";
|
||||
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), typeof(OsuModBubbles) };
|
||||
|
||||
[SettingSource("Attraction strength", "How strong the pull is.", 0)]
|
||||
public BindableFloat AttractionStrength { get; } = new BindableFloat(0.5f)
|
||||
|
@ -9,7 +9,6 @@ using osu.Framework.Localisation;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Overlays.Settings;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Osu.Beatmaps;
|
||||
@ -28,7 +27,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
|
||||
public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(OsuModTargetPractice)).ToArray();
|
||||
|
||||
[SettingSource("Angle sharpness", "How sharp angles should be", SettingControlType = typeof(SettingsSlider<float>))]
|
||||
[SettingSource("Angle sharpness", "How sharp angles should be")]
|
||||
public BindableFloat AngleSharpness { get; } = new BindableFloat(7)
|
||||
{
|
||||
MinValue = 1,
|
||||
|
@ -26,7 +26,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
public override ModType Type => ModType.Fun;
|
||||
public override LocalisableString Description => "Hit objects run away!";
|
||||
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), typeof(OsuModBubbles) };
|
||||
|
||||
[SettingSource("Repulsion strength", "How strong the repulsion is.", 0)]
|
||||
public BindableFloat RepulsionStrength { get; } = new BindableFloat(0.5f)
|
||||
|
@ -98,6 +98,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
ComboOffset = original.ComboOffset;
|
||||
LegacyLastTickOffset = original.LegacyLastTickOffset;
|
||||
TickDistanceMultiplier = original.TickDistanceMultiplier;
|
||||
SliderVelocity = original.SliderVelocity;
|
||||
}
|
||||
|
||||
protected override void CreateNestedHitObjects(CancellationToken cancellationToken)
|
||||
|
@ -133,14 +133,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
{
|
||||
// Note: base.LoadSamples() isn't called since the slider plays the tail's hitsounds for the time being.
|
||||
|
||||
if (HitObject.SampleControlPoint == null)
|
||||
{
|
||||
throw new InvalidOperationException($"{nameof(HitObject)}s must always have an attached {nameof(HitObject.SampleControlPoint)}."
|
||||
+ $" This is an indication that {nameof(HitObject.ApplyDefaults)} has not been invoked on {this}.");
|
||||
}
|
||||
|
||||
Samples.Samples = HitObject.TailSamples.Select(s => HitObject.SampleControlPoint.ApplyTo(s)).Cast<ISampleInfo>().ToArray();
|
||||
slidingSample.Samples = HitObject.CreateSlidingSamples().Select(s => HitObject.SampleControlPoint.ApplyTo(s)).Cast<ISampleInfo>().ToArray();
|
||||
Samples.Samples = HitObject.TailSamples.Cast<ISampleInfo>().ToArray();
|
||||
slidingSample.Samples = HitObject.CreateSlidingSamples().Cast<ISampleInfo>().ToArray();
|
||||
}
|
||||
|
||||
public override void StopAllSamples()
|
||||
|
@ -87,12 +87,15 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
|
||||
protected override void UpdateInitialTransforms()
|
||||
{
|
||||
// When snaking in is enabled, the first end circle needs to be delayed until the snaking completes.
|
||||
bool delayFadeIn = DrawableSlider.SliderBody?.SnakingIn.Value == true && HitObject.RepeatIndex == 0;
|
||||
|
||||
animDuration = Math.Min(300, HitObject.SpanDuration);
|
||||
|
||||
this.Animate(
|
||||
d => d.FadeIn(animDuration),
|
||||
d => d.ScaleTo(0.5f).ScaleTo(1f, animDuration * 2, Easing.OutElasticHalf)
|
||||
);
|
||||
this
|
||||
.FadeOut()
|
||||
.Delay(delayFadeIn ? (Slider?.TimePreempt ?? 0) / 3 : 0)
|
||||
.FadeIn(HitObject.RepeatIndex == 0 ? HitObject.TimeFadeIn : animDuration);
|
||||
}
|
||||
|
||||
protected override void UpdateHitStateTransforms(ArmedState state)
|
||||
|
@ -91,7 +91,13 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
{
|
||||
base.UpdateInitialTransforms();
|
||||
|
||||
CirclePiece.FadeInFromZero(HitObject.TimeFadeIn);
|
||||
// When snaking in is enabled, the first end circle needs to be delayed until the snaking completes.
|
||||
bool delayFadeIn = DrawableSlider.SliderBody?.SnakingIn.Value == true && HitObject.RepeatIndex == 0;
|
||||
|
||||
CirclePiece
|
||||
.FadeOut()
|
||||
.Delay(delayFadeIn ? (Slider?.TimePreempt ?? 0) / 3 : 0)
|
||||
.FadeIn(HitObject.TimeFadeIn);
|
||||
}
|
||||
|
||||
protected override void UpdateHitStateTransforms(ArmedState state)
|
||||
|
@ -126,7 +126,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
{
|
||||
base.LoadSamples();
|
||||
|
||||
spinningSample.Samples = HitObject.CreateSpinningSamples().Select(s => HitObject.SampleControlPoint.ApplyTo(s)).Cast<ISampleInfo>().ToArray();
|
||||
spinningSample.Samples = HitObject.CreateSpinningSamples().Cast<ISampleInfo>().ToArray();
|
||||
spinningSample.Frequency.Value = spinning_sample_initial_frequency;
|
||||
}
|
||||
|
||||
|
@ -10,18 +10,18 @@ using osu.Game.Rulesets.Objects;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using Newtonsoft.Json;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Caching;
|
||||
using osu.Game.Audio;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Beatmaps.Formats;
|
||||
using osu.Game.Rulesets.Judgements;
|
||||
using osu.Game.Rulesets.Osu.Judgements;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Objects
|
||||
{
|
||||
public class Slider : OsuHitObject, IHasPathWithRepeats
|
||||
public class Slider : OsuHitObject, IHasPathWithRepeats, IHasSliderVelocity, IHasGenerateTicks
|
||||
{
|
||||
public double EndTime => StartTime + this.SpanCount() * Path.Distance / Velocity;
|
||||
|
||||
@ -134,6 +134,21 @@ namespace osu.Game.Rulesets.Osu.Objects
|
||||
/// </summary>
|
||||
public bool OnlyJudgeNestedObjects = true;
|
||||
|
||||
public BindableNumber<double> SliderVelocityBindable { get; } = new BindableDouble(1)
|
||||
{
|
||||
Precision = 0.01,
|
||||
MinValue = 0.1,
|
||||
MaxValue = 10
|
||||
};
|
||||
|
||||
public double SliderVelocity
|
||||
{
|
||||
get => SliderVelocityBindable.Value;
|
||||
set => SliderVelocityBindable.Value = value;
|
||||
}
|
||||
|
||||
public bool GenerateTicks { get; set; } = true;
|
||||
|
||||
[JsonIgnore]
|
||||
public SliderHeadCircle HeadCircle { get; protected set; }
|
||||
|
||||
@ -151,15 +166,11 @@ namespace osu.Game.Rulesets.Osu.Objects
|
||||
base.ApplyDefaultsToSelf(controlPointInfo, difficulty);
|
||||
|
||||
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;
|
||||
bool generateTicks = legacyDifficultyPoint?.GenerateTicks ?? true;
|
||||
double scoringDistance = BASE_SCORING_DISTANCE * difficulty.SliderMultiplier * SliderVelocity;
|
||||
|
||||
Velocity = scoringDistance / timingPoint.BeatLength;
|
||||
TickDistance = generateTicks ? (scoringDistance / difficulty.SliderTickRate * TickDistanceMultiplier) : double.PositiveInfinity;
|
||||
TickDistance = GenerateTicks ? (scoringDistance / difficulty.SliderTickRate * TickDistanceMultiplier) : double.PositiveInfinity;
|
||||
}
|
||||
|
||||
protected override void CreateNestedHitObjects(CancellationToken cancellationToken)
|
||||
|
@ -39,11 +39,8 @@ namespace osu.Game.Rulesets.Osu.Objects
|
||||
}
|
||||
else
|
||||
{
|
||||
// taken from osu-stable
|
||||
const float first_end_circle_preempt_adjust = 2 / 3f;
|
||||
|
||||
// The first end circle should fade in with the slider.
|
||||
TimePreempt = (StartTime - slider.StartTime) + slider.TimePreempt * first_end_circle_preempt_adjust;
|
||||
TimePreempt += StartTime - slider.StartTime;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -72,7 +72,7 @@ namespace osu.Game.Rulesets.Osu.Objects
|
||||
|
||||
AddNested(i < SpinsRequired
|
||||
? new SpinnerTick { StartTime = startTime, SpinnerDuration = Duration }
|
||||
: new SpinnerBonusTick { StartTime = startTime, SpinnerDuration = Duration });
|
||||
: new SpinnerBonusTick { StartTime = startTime, SpinnerDuration = Duration, Samples = new[] { CreateHitSampleInfo("spinnerbonus") } });
|
||||
}
|
||||
}
|
||||
|
||||
@ -91,7 +91,7 @@ namespace osu.Game.Rulesets.Osu.Objects
|
||||
|
||||
return new[]
|
||||
{
|
||||
SampleControlPoint.ApplyTo(referenceSample).With("spinnerspin")
|
||||
referenceSample.With("spinnerspin")
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,6 @@
|
||||
|
||||
#nullable disable
|
||||
|
||||
using osu.Game.Audio;
|
||||
using osu.Game.Rulesets.Judgements;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
|
||||
@ -11,11 +10,6 @@ namespace osu.Game.Rulesets.Osu.Objects
|
||||
{
|
||||
public class SpinnerBonusTick : SpinnerTick
|
||||
{
|
||||
public SpinnerBonusTick()
|
||||
{
|
||||
Samples.Add(new HitSampleInfo("spinnerbonus"));
|
||||
}
|
||||
|
||||
public override Judgement CreateJudgement() => new OsuSpinnerBonusTickJudgement();
|
||||
|
||||
public class OsuSpinnerBonusTickJudgement : OsuSpinnerTickJudgement
|
||||
|
@ -203,7 +203,8 @@ namespace osu.Game.Rulesets.Osu
|
||||
new OsuModNoScope(),
|
||||
new MultiMod(new OsuModMagnetised(), new OsuModRepel()),
|
||||
new ModAdaptiveSpeed(),
|
||||
new OsuModFreezeFrame()
|
||||
new OsuModFreezeFrame(),
|
||||
new OsuModBubbles()
|
||||
};
|
||||
|
||||
case ModType.System:
|
||||
|
@ -10,6 +10,7 @@ namespace osu.Game.Rulesets.Osu
|
||||
Cursor,
|
||||
CursorTrail,
|
||||
CursorParticles,
|
||||
CursorRipple,
|
||||
SliderScorePoint,
|
||||
ReverseArrow,
|
||||
HitCircleText,
|
||||
|
@ -98,7 +98,8 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon
|
||||
|
||||
using (BeginAbsoluteSequence(drawableObject.HitStateUpdateTime))
|
||||
{
|
||||
this.FadeOut(duration, Easing.OutQuint);
|
||||
// intentionally pile on an extra FadeOut to make it happen much faster
|
||||
this.FadeOut(duration / 4, Easing.OutQuint);
|
||||
icon.ScaleTo(defaultIconScale * icon_scale, duration, Easing.OutQuint);
|
||||
}
|
||||
}
|
||||
|
@ -3,11 +3,13 @@
|
||||
|
||||
using System.Diagnostics;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||
using osu.Game.Skinning;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
||||
{
|
||||
@ -18,6 +20,12 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
||||
|
||||
private Drawable proxy = null!;
|
||||
|
||||
private Bindable<Color4> accentColour = null!;
|
||||
|
||||
private bool textureIsDefaultSkin;
|
||||
|
||||
private Drawable arrow = null!;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(ISkinSource skinSource)
|
||||
{
|
||||
@ -26,7 +34,9 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
||||
string lookupName = new OsuSkinComponentLookup(OsuSkinComponents.ReverseArrow).LookupName;
|
||||
|
||||
var skin = skinSource.FindProvider(s => s.GetTexture(lookupName) != null);
|
||||
InternalChild = skin?.GetAnimation(lookupName, true, true) ?? Empty();
|
||||
|
||||
InternalChild = arrow = (skin?.GetAnimation(lookupName, true, true) ?? Empty());
|
||||
textureIsDefaultSkin = skin is ISkinTransformer transformer && transformer.Skin is DefaultLegacySkin;
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
@ -39,6 +49,12 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
||||
{
|
||||
drawableHitObject.HitObjectApplied += onHitObjectApplied;
|
||||
onHitObjectApplied(drawableHitObject);
|
||||
|
||||
accentColour = drawableHitObject.AccentColour.GetBoundCopy();
|
||||
accentColour.BindValueChanged(c =>
|
||||
{
|
||||
arrow.Colour = textureIsDefaultSkin && c.NewValue.R + c.NewValue.G + c.NewValue.B > (600 / 255f) ? Color4.Black : Color4.White;
|
||||
}, true);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -100,6 +100,28 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
||||
|
||||
return null;
|
||||
|
||||
case OsuSkinComponents.CursorRipple:
|
||||
if (GetTexture("cursor-ripple") != null)
|
||||
{
|
||||
var ripple = this.GetAnimation("cursor-ripple", false, false);
|
||||
|
||||
// In stable this element was scaled down to 50% and opacity 20%, but this makes the elements WAY too big and inflexible.
|
||||
// If anyone complains about these not being applied, this can be uncommented.
|
||||
//
|
||||
// But if no one complains I'd rather fix this in lazer. Wiki documentation doesn't mention size,
|
||||
// so we might be okay.
|
||||
//
|
||||
// if (ripple != null)
|
||||
// {
|
||||
// ripple.Scale = new Vector2(0.5f);
|
||||
// ripple.Alpha = 0.2f;
|
||||
// }
|
||||
|
||||
return ripple;
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
case OsuSkinComponents.CursorParticles:
|
||||
if (GetTexture("star2") != null)
|
||||
return new LegacyCursorParticles();
|
||||
|
@ -1,8 +1,6 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
@ -13,6 +11,7 @@ using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Scoring;
|
||||
using osuTK;
|
||||
@ -36,8 +35,8 @@ namespace osu.Game.Rulesets.Osu.Statistics
|
||||
|
||||
private const float rotation = 45;
|
||||
|
||||
private BufferedContainer bufferedGrid;
|
||||
private GridContainer pointGrid;
|
||||
private BufferedContainer bufferedGrid = null!;
|
||||
private GridContainer pointGrid = null!;
|
||||
|
||||
private readonly ScoreInfo score;
|
||||
private readonly IBeatmap playableBeatmap;
|
||||
@ -58,6 +57,8 @@ namespace osu.Game.Rulesets.Osu.Statistics
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
const float line_extension = 0.2f;
|
||||
|
||||
InternalChild = new Container
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
@ -66,76 +67,99 @@ namespace osu.Game.Rulesets.Osu.Statistics
|
||||
FillMode = FillMode.Fit,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new CircularContainer
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Size = new Vector2(inner_portion),
|
||||
Masking = true,
|
||||
BorderThickness = line_thickness,
|
||||
BorderColour = Color4.White,
|
||||
Child = new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = Color4Extensions.FromHex("#202624")
|
||||
}
|
||||
},
|
||||
new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new CircularContainer
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Size = new Vector2(inner_portion),
|
||||
Masking = true,
|
||||
BorderThickness = line_thickness,
|
||||
BorderColour = Color4.White,
|
||||
Child = new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = Color4Extensions.FromHex("#202624")
|
||||
}
|
||||
},
|
||||
new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Padding = new MarginPadding(1),
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Rotation = rotation,
|
||||
Child = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Masking = true,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Box
|
||||
new Circle
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
EdgeSmoothness = new Vector2(1),
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
Height = 2, // We're rotating along a diagonal - we don't really care how big this is.
|
||||
Width = line_thickness / 2,
|
||||
Rotation = -rotation,
|
||||
Alpha = 0.3f,
|
||||
Width = line_thickness,
|
||||
Height = inner_portion + line_extension,
|
||||
Rotation = -rotation * 2,
|
||||
Alpha = 0.6f,
|
||||
},
|
||||
new Box
|
||||
new Circle
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
EdgeSmoothness = new Vector2(1),
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
Height = 2, // We're rotating along a diagonal - we don't really care how big this is.
|
||||
Width = line_thickness / 2, // adjust for edgesmoothness
|
||||
Rotation = rotation
|
||||
Width = line_thickness,
|
||||
Height = inner_portion + line_extension,
|
||||
},
|
||||
new OsuSpriteText
|
||||
{
|
||||
Text = "Overshoot",
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.BottomCentre,
|
||||
Padding = new MarginPadding(3),
|
||||
RelativePositionAxes = Axes.Both,
|
||||
Y = -(inner_portion + line_extension) / 2,
|
||||
},
|
||||
new OsuSpriteText
|
||||
{
|
||||
Text = "Undershoot",
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.TopCentre,
|
||||
Padding = new MarginPadding(3),
|
||||
RelativePositionAxes = Axes.Both,
|
||||
Y = (inner_portion + line_extension) / 2,
|
||||
},
|
||||
new Circle
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.TopCentre,
|
||||
RelativePositionAxes = Axes.Both,
|
||||
Y = -(inner_portion + line_extension) / 2,
|
||||
Margin = new MarginPadding(-line_thickness / 2),
|
||||
Width = line_thickness,
|
||||
Height = 10,
|
||||
Rotation = 45,
|
||||
},
|
||||
new Circle
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.TopCentre,
|
||||
RelativePositionAxes = Axes.Both,
|
||||
Y = -(inner_portion + line_extension) / 2,
|
||||
Margin = new MarginPadding(-line_thickness / 2),
|
||||
Width = line_thickness,
|
||||
Height = 10,
|
||||
Rotation = -45,
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
new Box
|
||||
{
|
||||
Anchor = Anchor.TopRight,
|
||||
Origin = Anchor.TopRight,
|
||||
Width = 10,
|
||||
EdgeSmoothness = new Vector2(1),
|
||||
Height = line_thickness / 2, // adjust for edgesmoothness
|
||||
},
|
||||
new Box
|
||||
{
|
||||
Anchor = Anchor.TopRight,
|
||||
Origin = Anchor.TopRight,
|
||||
EdgeSmoothness = new Vector2(1),
|
||||
Width = line_thickness / 2, // adjust for edgesmoothness
|
||||
Height = 10,
|
||||
}
|
||||
}
|
||||
},
|
||||
bufferedGrid = new BufferedContainer(cachedFrameBuffer: true)
|
||||
|
105
osu.Game.Rulesets.Osu/UI/Cursor/CursorRippleVisualiser.cs
Normal file
105
osu.Game.Rulesets.Osu/UI/Cursor/CursorRippleVisualiser.cs
Normal file
@ -0,0 +1,105 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Pooling;
|
||||
using osu.Framework.Input.Bindings;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Game.Rulesets.Osu.Configuration;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Rulesets.Osu.Skinning.Default;
|
||||
using osu.Game.Skinning;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.UI.Cursor
|
||||
{
|
||||
public partial class CursorRippleVisualiser : CompositeDrawable, IKeyBindingHandler<OsuAction>
|
||||
{
|
||||
private readonly Bindable<bool> showRipples = new Bindable<bool>(true);
|
||||
|
||||
private readonly DrawablePool<CursorRipple> ripplePool = new DrawablePool<CursorRipple>(20);
|
||||
|
||||
public CursorRippleVisualiser()
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
}
|
||||
|
||||
public Vector2 CursorScale { get; set; } = Vector2.One;
|
||||
|
||||
[BackgroundDependencyLoader(true)]
|
||||
private void load(OsuRulesetConfigManager? rulesetConfig)
|
||||
{
|
||||
rulesetConfig?.BindWith(OsuRulesetSetting.ShowCursorRipples, showRipples);
|
||||
}
|
||||
|
||||
public bool OnPressed(KeyBindingPressEvent<OsuAction> e)
|
||||
{
|
||||
if (showRipples.Value)
|
||||
{
|
||||
AddInternal(ripplePool.Get(r =>
|
||||
{
|
||||
r.Position = e.MousePosition;
|
||||
r.Scale = CursorScale;
|
||||
}));
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void OnReleased(KeyBindingReleaseEvent<OsuAction> e)
|
||||
{
|
||||
}
|
||||
|
||||
private partial class CursorRipple : PoolableDrawable
|
||||
{
|
||||
private Drawable ripple = null!;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
AutoSizeAxes = Axes.Both;
|
||||
Origin = Anchor.Centre;
|
||||
|
||||
InternalChild = ripple = new SkinnableDrawable(new OsuSkinComponentLookup(OsuSkinComponents.CursorRipple), _ => new DefaultCursorRipple())
|
||||
{
|
||||
Blending = BlendingParameters.Additive,
|
||||
};
|
||||
}
|
||||
|
||||
protected override void PrepareForUse()
|
||||
{
|
||||
base.PrepareForUse();
|
||||
|
||||
ClearTransforms(true);
|
||||
|
||||
ripple.ScaleTo(0.1f)
|
||||
.ScaleTo(1, 700, Easing.Out);
|
||||
|
||||
this
|
||||
.FadeOutFromOne(700)
|
||||
.Expire(true);
|
||||
}
|
||||
}
|
||||
|
||||
public partial class DefaultCursorRipple : CompositeDrawable
|
||||
{
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
AutoSizeAxes = Axes.Both;
|
||||
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
new RingPiece(3)
|
||||
{
|
||||
Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2),
|
||||
Alpha = 0.1f,
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -40,6 +40,8 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
|
||||
private Bindable<float> userCursorScale;
|
||||
private Bindable<bool> autoCursorScale;
|
||||
|
||||
private readonly CursorRippleVisualiser rippleVisualiser;
|
||||
|
||||
public OsuCursorContainer()
|
||||
{
|
||||
InternalChild = fadeContainer = new Container
|
||||
@ -48,6 +50,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
|
||||
Children = new[]
|
||||
{
|
||||
cursorTrail = new SkinnableDrawable(new OsuSkinComponentLookup(OsuSkinComponents.CursorTrail), _ => new DefaultCursorTrail(), confineMode: ConfineMode.NoScaling),
|
||||
rippleVisualiser = new CursorRippleVisualiser(),
|
||||
new SkinnableDrawable(new OsuSkinComponentLookup(OsuSkinComponents.CursorParticles), confineMode: ConfineMode.NoScaling),
|
||||
}
|
||||
};
|
||||
@ -82,6 +85,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
|
||||
var newScale = new Vector2(e.NewValue);
|
||||
|
||||
ActiveCursor.Scale = newScale;
|
||||
rippleVisualiser.CursorScale = newScale;
|
||||
cursorTrail.Scale = newScale;
|
||||
}, true);
|
||||
|
||||
|
@ -64,7 +64,7 @@ namespace osu.Game.Rulesets.Osu.UI
|
||||
|
||||
localCursorContainer?.Expire();
|
||||
localCursorContainer = null;
|
||||
GameplayCursor?.ActiveCursor?.Show();
|
||||
GameplayCursor?.ActiveCursor.Show();
|
||||
}
|
||||
|
||||
protected override bool OnHover(HoverEvent e) => true;
|
||||
|
@ -43,6 +43,11 @@ namespace osu.Game.Rulesets.Osu.UI
|
||||
LabelText = RulesetSettingsStrings.CursorTrail,
|
||||
Current = config.GetBindable<bool>(OsuRulesetSetting.ShowCursorTrail)
|
||||
},
|
||||
new SettingsCheckbox
|
||||
{
|
||||
LabelText = RulesetSettingsStrings.CursorRipples,
|
||||
Current = config.GetBindable<bool>(OsuRulesetSetting.ShowCursorRipples)
|
||||
},
|
||||
new SettingsEnumDropdown<PlayfieldBorderStyle>
|
||||
{
|
||||
LabelText = RulesetSettingsStrings.PlayfieldBorderStyle,
|
||||
|
@ -0,0 +1,337 @@
|
||||
// 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.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Timing;
|
||||
using osu.Game.Audio;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Taiko.Objects;
|
||||
using osu.Game.Rulesets.Taiko.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Taiko.UI;
|
||||
using osu.Game.Rulesets.UI;
|
||||
using osu.Game.Rulesets.UI.Scrolling;
|
||||
using osu.Game.Tests.Visual;
|
||||
|
||||
namespace osu.Game.Rulesets.Taiko.Tests
|
||||
{
|
||||
public partial class TestSceneDrumSampleTriggerSource : OsuTestScene
|
||||
{
|
||||
private readonly ManualClock manualClock = new ManualClock();
|
||||
|
||||
[Cached(typeof(IScrollingInfo))]
|
||||
private ScrollingTestContainer.TestScrollingInfo info = new ScrollingTestContainer.TestScrollingInfo
|
||||
{
|
||||
Direction = { Value = ScrollingDirection.Left },
|
||||
TimeRange = { Value = 200 },
|
||||
};
|
||||
|
||||
private ScrollingHitObjectContainer hitObjectContainer = null!;
|
||||
private TestDrumSampleTriggerSource triggerSource = null!;
|
||||
|
||||
[SetUp]
|
||||
public void SetUp() => Schedule(() =>
|
||||
{
|
||||
hitObjectContainer = new ScrollingHitObjectContainer();
|
||||
manualClock.CurrentTime = 0;
|
||||
|
||||
Child = new Container
|
||||
{
|
||||
Clock = new FramedClock(manualClock),
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
hitObjectContainer,
|
||||
triggerSource = new TestDrumSampleTriggerSource(hitObjectContainer)
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
[Test]
|
||||
public void TestNormalHit()
|
||||
{
|
||||
AddStep("add hit with normal samples", () =>
|
||||
{
|
||||
var hit = new Hit
|
||||
{
|
||||
StartTime = 100,
|
||||
Samples = new List<HitSampleInfo>
|
||||
{
|
||||
new HitSampleInfo(HitSampleInfo.HIT_NORMAL)
|
||||
}
|
||||
};
|
||||
hit.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
|
||||
var drawableHit = new DrawableHit(hit);
|
||||
hitObjectContainer.Add(drawableHit);
|
||||
});
|
||||
|
||||
AddAssert("most valid object is hit", () => triggerSource.GetMostValidObject(), Is.InstanceOf<Hit>);
|
||||
checkSound(HitType.Centre, HitSampleInfo.HIT_NORMAL, SampleControlPoint.DEFAULT_BANK);
|
||||
checkSound(HitType.Rim, HitSampleInfo.HIT_CLAP, SampleControlPoint.DEFAULT_BANK);
|
||||
|
||||
AddStep("seek past hit", () => manualClock.CurrentTime = 200);
|
||||
AddAssert("most valid object is hit", () => triggerSource.GetMostValidObject(), Is.InstanceOf<Hit>);
|
||||
checkSound(HitType.Centre, HitSampleInfo.HIT_NORMAL, SampleControlPoint.DEFAULT_BANK);
|
||||
checkSound(HitType.Rim, HitSampleInfo.HIT_CLAP, SampleControlPoint.DEFAULT_BANK);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestSoftHit()
|
||||
{
|
||||
AddStep("add hit with soft samples", () =>
|
||||
{
|
||||
var hit = new Hit
|
||||
{
|
||||
StartTime = 100,
|
||||
Samples = new List<HitSampleInfo>
|
||||
{
|
||||
new HitSampleInfo(HitSampleInfo.HIT_NORMAL, HitSampleInfo.BANK_SOFT)
|
||||
}
|
||||
};
|
||||
hit.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
|
||||
var drawableHit = new DrawableHit(hit);
|
||||
hitObjectContainer.Add(drawableHit);
|
||||
});
|
||||
|
||||
AddAssert("most valid object is hit", () => triggerSource.GetMostValidObject(), Is.InstanceOf<Hit>);
|
||||
checkSound(HitType.Centre, HitSampleInfo.HIT_NORMAL, HitSampleInfo.BANK_SOFT);
|
||||
checkSound(HitType.Rim, HitSampleInfo.HIT_CLAP, HitSampleInfo.BANK_SOFT);
|
||||
|
||||
AddStep("seek past hit", () => manualClock.CurrentTime = 200);
|
||||
AddAssert("most valid object is hit", () => triggerSource.GetMostValidObject(), Is.InstanceOf<Hit>);
|
||||
checkSound(HitType.Centre, HitSampleInfo.HIT_NORMAL, HitSampleInfo.BANK_SOFT);
|
||||
checkSound(HitType.Rim, HitSampleInfo.HIT_CLAP, HitSampleInfo.BANK_SOFT);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestDrumStrongHit()
|
||||
{
|
||||
AddStep("add strong hit with drum samples", () =>
|
||||
{
|
||||
var hit = new Hit
|
||||
{
|
||||
StartTime = 100,
|
||||
Samples = new List<HitSampleInfo>
|
||||
{
|
||||
new HitSampleInfo(HitSampleInfo.HIT_NORMAL, "drum"),
|
||||
new HitSampleInfo(HitSampleInfo.HIT_FINISH, "drum") // implies strong
|
||||
}
|
||||
};
|
||||
hit.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
|
||||
var drawableHit = new DrawableHit(hit);
|
||||
hitObjectContainer.Add(drawableHit);
|
||||
});
|
||||
|
||||
AddAssert("most valid object is strong nested hit", () => triggerSource.GetMostValidObject(), Is.InstanceOf<Hit.StrongNestedHit>);
|
||||
checkSound(HitType.Centre, HitSampleInfo.HIT_NORMAL, "drum");
|
||||
checkSound(HitType.Rim, HitSampleInfo.HIT_CLAP, "drum");
|
||||
|
||||
AddStep("seek past hit", () => manualClock.CurrentTime = 200);
|
||||
AddAssert("most valid object is hit", () => triggerSource.GetMostValidObject(), Is.InstanceOf<Hit>);
|
||||
checkSound(HitType.Centre, HitSampleInfo.HIT_NORMAL, "drum");
|
||||
checkSound(HitType.Rim, HitSampleInfo.HIT_CLAP, "drum");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestNormalDrumRoll()
|
||||
{
|
||||
AddStep("add drum roll with normal samples", () =>
|
||||
{
|
||||
var drumRoll = new DrumRoll
|
||||
{
|
||||
StartTime = 100,
|
||||
EndTime = 1100,
|
||||
Samples = new List<HitSampleInfo>
|
||||
{
|
||||
new HitSampleInfo(HitSampleInfo.HIT_NORMAL)
|
||||
}
|
||||
};
|
||||
drumRoll.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
|
||||
var drawableDrumRoll = new DrawableDrumRoll(drumRoll);
|
||||
hitObjectContainer.Add(drawableDrumRoll);
|
||||
});
|
||||
|
||||
AddAssert("most valid object is drum roll tick", () => triggerSource.GetMostValidObject(), Is.InstanceOf<DrumRollTick>);
|
||||
checkSound(HitType.Centre, HitSampleInfo.HIT_NORMAL, SampleControlPoint.DEFAULT_BANK);
|
||||
checkSound(HitType.Rim, HitSampleInfo.HIT_CLAP, SampleControlPoint.DEFAULT_BANK);
|
||||
|
||||
AddStep("seek to middle of drum roll", () => manualClock.CurrentTime = 600);
|
||||
AddAssert("most valid object is drum roll tick", () => triggerSource.GetMostValidObject(), Is.InstanceOf<DrumRollTick>);
|
||||
checkSound(HitType.Centre, HitSampleInfo.HIT_NORMAL, SampleControlPoint.DEFAULT_BANK);
|
||||
checkSound(HitType.Rim, HitSampleInfo.HIT_CLAP, SampleControlPoint.DEFAULT_BANK);
|
||||
|
||||
AddStep("seek past drum roll", () => manualClock.CurrentTime = 1200);
|
||||
AddAssert("most valid object is drum roll", () => triggerSource.GetMostValidObject(), Is.InstanceOf<DrumRoll>);
|
||||
checkSound(HitType.Centre, HitSampleInfo.HIT_NORMAL, SampleControlPoint.DEFAULT_BANK);
|
||||
checkSound(HitType.Rim, HitSampleInfo.HIT_CLAP, SampleControlPoint.DEFAULT_BANK);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestSoftDrumRoll()
|
||||
{
|
||||
AddStep("add drum roll with soft samples", () =>
|
||||
{
|
||||
var drumRoll = new DrumRoll
|
||||
{
|
||||
StartTime = 100,
|
||||
EndTime = 1100,
|
||||
Samples = new List<HitSampleInfo>
|
||||
{
|
||||
new HitSampleInfo(HitSampleInfo.HIT_NORMAL, HitSampleInfo.BANK_SOFT)
|
||||
}
|
||||
};
|
||||
drumRoll.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
|
||||
var drawableDrumRoll = new DrawableDrumRoll(drumRoll);
|
||||
hitObjectContainer.Add(drawableDrumRoll);
|
||||
});
|
||||
|
||||
AddAssert("most valid object is drum roll tick", () => triggerSource.GetMostValidObject(), Is.InstanceOf<DrumRollTick>);
|
||||
checkSound(HitType.Centre, HitSampleInfo.HIT_NORMAL, HitSampleInfo.BANK_SOFT);
|
||||
checkSound(HitType.Rim, HitSampleInfo.HIT_CLAP, HitSampleInfo.BANK_SOFT);
|
||||
|
||||
AddStep("seek to middle of drum roll", () => manualClock.CurrentTime = 600);
|
||||
AddAssert("most valid object is drum roll tick", () => triggerSource.GetMostValidObject(), Is.InstanceOf<DrumRollTick>);
|
||||
checkSound(HitType.Centre, HitSampleInfo.HIT_NORMAL, HitSampleInfo.BANK_SOFT);
|
||||
checkSound(HitType.Rim, HitSampleInfo.HIT_CLAP, HitSampleInfo.BANK_SOFT);
|
||||
|
||||
AddStep("seek past drum roll", () => manualClock.CurrentTime = 1200);
|
||||
AddAssert("most valid object is drum roll", () => triggerSource.GetMostValidObject(), Is.InstanceOf<DrumRoll>);
|
||||
checkSound(HitType.Centre, HitSampleInfo.HIT_NORMAL, HitSampleInfo.BANK_SOFT);
|
||||
checkSound(HitType.Rim, HitSampleInfo.HIT_CLAP, HitSampleInfo.BANK_SOFT);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestDrumStrongDrumRoll()
|
||||
{
|
||||
AddStep("add strong drum roll with drum samples", () =>
|
||||
{
|
||||
var drumRoll = new DrumRoll
|
||||
{
|
||||
StartTime = 100,
|
||||
EndTime = 1100,
|
||||
Samples = new List<HitSampleInfo>
|
||||
{
|
||||
new HitSampleInfo(HitSampleInfo.HIT_NORMAL, "drum"),
|
||||
new HitSampleInfo(HitSampleInfo.HIT_FINISH, "drum") // implies strong
|
||||
}
|
||||
};
|
||||
drumRoll.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
|
||||
var drawableDrumRoll = new DrawableDrumRoll(drumRoll);
|
||||
hitObjectContainer.Add(drawableDrumRoll);
|
||||
});
|
||||
|
||||
AddAssert("most valid object is drum roll tick's nested strong hit", () => triggerSource.GetMostValidObject(), Is.InstanceOf<DrumRollTick.StrongNestedHit>);
|
||||
checkSound(HitType.Centre, HitSampleInfo.HIT_NORMAL, "drum");
|
||||
checkSound(HitType.Rim, HitSampleInfo.HIT_CLAP, "drum");
|
||||
|
||||
AddStep("seek to middle of drum roll", () => manualClock.CurrentTime = 600);
|
||||
AddAssert("most valid object is drum roll tick's nested strong hit", () => triggerSource.GetMostValidObject(), Is.InstanceOf<DrumRollTick.StrongNestedHit>);
|
||||
checkSound(HitType.Centre, HitSampleInfo.HIT_NORMAL, "drum");
|
||||
checkSound(HitType.Rim, HitSampleInfo.HIT_CLAP, "drum");
|
||||
|
||||
AddStep("seek past drum roll", () => manualClock.CurrentTime = 1200);
|
||||
AddAssert("most valid object is drum roll", () => triggerSource.GetMostValidObject(), Is.InstanceOf<DrumRoll>);
|
||||
checkSound(HitType.Centre, HitSampleInfo.HIT_NORMAL, "drum");
|
||||
checkSound(HitType.Rim, HitSampleInfo.HIT_CLAP, "drum");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestNormalSwell()
|
||||
{
|
||||
AddStep("add swell with normal samples", () =>
|
||||
{
|
||||
var swell = new Swell
|
||||
{
|
||||
StartTime = 100,
|
||||
EndTime = 1100,
|
||||
Samples = new List<HitSampleInfo>
|
||||
{
|
||||
new HitSampleInfo(HitSampleInfo.HIT_NORMAL)
|
||||
}
|
||||
};
|
||||
swell.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
|
||||
var drawableSwell = new DrawableSwell(swell);
|
||||
hitObjectContainer.Add(drawableSwell);
|
||||
});
|
||||
|
||||
AddAssert("most valid object is swell tick", () => triggerSource.GetMostValidObject(), Is.InstanceOf<SwellTick>);
|
||||
checkSound(HitType.Centre, HitSampleInfo.HIT_NORMAL, SampleControlPoint.DEFAULT_BANK);
|
||||
checkSound(HitType.Rim, HitSampleInfo.HIT_CLAP, SampleControlPoint.DEFAULT_BANK);
|
||||
|
||||
AddStep("seek to middle of swell", () => manualClock.CurrentTime = 600);
|
||||
AddAssert("most valid object is swell tick", () => triggerSource.GetMostValidObject(), Is.InstanceOf<SwellTick>);
|
||||
checkSound(HitType.Centre, HitSampleInfo.HIT_NORMAL, SampleControlPoint.DEFAULT_BANK);
|
||||
checkSound(HitType.Rim, HitSampleInfo.HIT_CLAP, SampleControlPoint.DEFAULT_BANK);
|
||||
|
||||
AddStep("seek past swell", () => manualClock.CurrentTime = 1200);
|
||||
AddAssert("most valid object is swell", () => triggerSource.GetMostValidObject(), Is.InstanceOf<Swell>);
|
||||
checkSound(HitType.Centre, HitSampleInfo.HIT_NORMAL, SampleControlPoint.DEFAULT_BANK);
|
||||
checkSound(HitType.Rim, HitSampleInfo.HIT_CLAP, SampleControlPoint.DEFAULT_BANK);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestDrumSwell()
|
||||
{
|
||||
AddStep("add swell with drum samples", () =>
|
||||
{
|
||||
var swell = new Swell
|
||||
{
|
||||
StartTime = 100,
|
||||
EndTime = 1100,
|
||||
Samples = new List<HitSampleInfo>
|
||||
{
|
||||
new HitSampleInfo(HitSampleInfo.HIT_NORMAL, "drum")
|
||||
}
|
||||
};
|
||||
swell.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
|
||||
var drawableSwell = new DrawableSwell(swell);
|
||||
hitObjectContainer.Add(drawableSwell);
|
||||
});
|
||||
|
||||
AddAssert("most valid object is swell tick", () => triggerSource.GetMostValidObject(), Is.InstanceOf<SwellTick>);
|
||||
checkSound(HitType.Centre, HitSampleInfo.HIT_NORMAL, "drum");
|
||||
checkSound(HitType.Rim, HitSampleInfo.HIT_CLAP, "drum");
|
||||
|
||||
AddStep("seek to middle of swell", () => manualClock.CurrentTime = 600);
|
||||
AddAssert("most valid object is swell tick", () => triggerSource.GetMostValidObject(), Is.InstanceOf<SwellTick>);
|
||||
checkSound(HitType.Centre, HitSampleInfo.HIT_NORMAL, "drum");
|
||||
checkSound(HitType.Rim, HitSampleInfo.HIT_CLAP, "drum");
|
||||
|
||||
AddStep("seek past swell", () => manualClock.CurrentTime = 1200);
|
||||
AddAssert("most valid object is swell", () => triggerSource.GetMostValidObject(), Is.InstanceOf<Swell>);
|
||||
checkSound(HitType.Centre, HitSampleInfo.HIT_NORMAL, "drum");
|
||||
checkSound(HitType.Rim, HitSampleInfo.HIT_CLAP, "drum");
|
||||
}
|
||||
|
||||
private void checkSound(HitType hitType, string expectedName, string expectedBank)
|
||||
{
|
||||
AddStep($"hit {hitType}", () => triggerSource.Play(hitType));
|
||||
AddAssert($"last played sample is {expectedName}", () => triggerSource.LastPlayedSamples!.OfType<HitSampleInfo>().Single().Name, () => Is.EqualTo(expectedName));
|
||||
AddAssert($"last played sample has {expectedBank} bank", () => triggerSource.LastPlayedSamples!.OfType<HitSampleInfo>().Single().Bank, () => Is.EqualTo(expectedBank));
|
||||
}
|
||||
|
||||
private partial class TestDrumSampleTriggerSource : DrumSampleTriggerSource
|
||||
{
|
||||
public ISampleInfo[]? LastPlayedSamples { get; private set; }
|
||||
|
||||
public TestDrumSampleTriggerSource(HitObjectContainer hitObjectContainer)
|
||||
: base(hitObjectContainer)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void PlaySamples(ISampleInfo[] samples)
|
||||
{
|
||||
base.PlaySamples(samples);
|
||||
LastPlayedSamples = samples;
|
||||
}
|
||||
|
||||
public new HitObject GetMostValidObject() => base.GetMostValidObject();
|
||||
}
|
||||
}
|
||||
}
|
@ -64,7 +64,9 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
|
||||
|
||||
foreach (HitObject hitObject in original.HitObjects)
|
||||
{
|
||||
double nextScrollSpeed = hitObject.DifficultyControlPoint.SliderVelocity;
|
||||
if (hitObject is not IHasSliderVelocity hasSliderVelocity) continue;
|
||||
|
||||
double nextScrollSpeed = hasSliderVelocity.SliderVelocity;
|
||||
EffectControlPoint currentEffectPoint = converted.ControlPointInfo.EffectPointAt(hitObject.StartTime);
|
||||
|
||||
if (!Precision.AlmostEquals(lastScrollSpeed, nextScrollSpeed, acceptableDifference: currentEffectPoint.ScrollSpeedBindable.Precision))
|
||||
@ -131,7 +133,8 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
|
||||
StartTime = obj.StartTime,
|
||||
Samples = obj.Samples,
|
||||
Duration = taikoDuration,
|
||||
TickRate = beatmap.Difficulty.SliderTickRate == 3 ? 3 : 4
|
||||
TickRate = beatmap.Difficulty.SliderTickRate == 3 ? 3 : 4,
|
||||
SliderVelocity = obj is IHasSliderVelocity velocityData ? velocityData.SliderVelocity : 1
|
||||
};
|
||||
}
|
||||
|
||||
@ -177,15 +180,14 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
|
||||
double distance = distanceData.Distance * spans * LegacyBeatmapEncoder.LEGACY_TAIKO_VELOCITY_MULTIPLIER;
|
||||
|
||||
TimingControlPoint timingPoint = beatmap.ControlPointInfo.TimingPointAt(obj.StartTime);
|
||||
DifficultyControlPoint difficultyPoint = obj.DifficultyControlPoint;
|
||||
|
||||
double beatLength;
|
||||
#pragma warning disable 618
|
||||
if (difficultyPoint is LegacyBeatmapDecoder.LegacyDifficultyControlPoint legacyDifficultyPoint)
|
||||
#pragma warning restore 618
|
||||
beatLength = timingPoint.BeatLength * legacyDifficultyPoint.BpmMultiplier;
|
||||
if (obj.LegacyBpmMultiplier.HasValue)
|
||||
beatLength = timingPoint.BeatLength * obj.LegacyBpmMultiplier.Value;
|
||||
else if (obj is IHasSliderVelocity hasSliderVelocity)
|
||||
beatLength = timingPoint.BeatLength / hasSliderVelocity.SliderVelocity;
|
||||
else
|
||||
beatLength = timingPoint.BeatLength / difficultyPoint.SliderVelocity;
|
||||
beatLength = timingPoint.BeatLength;
|
||||
|
||||
double sliderScoringPointDistance = osu_base_scoring_distance * beatmap.Difficulty.SliderMultiplier / beatmap.Difficulty.SliderTickRate;
|
||||
|
||||
|
@ -25,6 +25,8 @@ namespace osu.Game.Rulesets.Taiko.Edit.Blueprints
|
||||
|
||||
private readonly IHasDuration spanPlacementObject;
|
||||
|
||||
protected override bool IsValidForPlacement => spanPlacementObject.Duration > 0;
|
||||
|
||||
public TaikoSpanPlacementBlueprint(HitObject hitObject)
|
||||
: base(hitObject)
|
||||
{
|
||||
@ -73,7 +75,7 @@ namespace osu.Game.Rulesets.Taiko.Edit.Blueprints
|
||||
return;
|
||||
|
||||
base.OnMouseUp(e);
|
||||
EndPlacement(spanPlacementObject.Duration > 0);
|
||||
EndPlacement(true);
|
||||
}
|
||||
|
||||
public override void UpdateTimeAndPosition(SnapResult result)
|
||||
|
@ -15,7 +15,7 @@ namespace osu.Game.Rulesets.Taiko.Mods
|
||||
public void ApplyToDrawableRuleset(DrawableRuleset<TaikoHitObject> drawableRuleset)
|
||||
{
|
||||
var drawableTaikoRuleset = (DrawableTaikoRuleset)drawableRuleset;
|
||||
drawableTaikoRuleset.LockPlayfieldMaxAspect.Value = false;
|
||||
drawableTaikoRuleset.LockPlayfieldAspectRange.Value = false;
|
||||
|
||||
var playfield = (TaikoPlayfield)drawableRuleset.Playfield;
|
||||
playfield.ClassicHitTargetPosition.Value = true;
|
||||
|
@ -118,6 +118,9 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
||||
{
|
||||
public override bool RemoveWhenNotAlive => false;
|
||||
}
|
||||
|
||||
// Most osu!taiko hitsounds are managed by the drum (see DrumSampleTriggerSource).
|
||||
public override IEnumerable<HitSampleInfo> GetSamples() => Enumerable.Empty<HitSampleInfo>();
|
||||
}
|
||||
|
||||
public abstract partial class DrawableTaikoHitObject<TObject> : DrawableTaikoHitObject
|
||||
@ -157,9 +160,6 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
||||
Content.Add(MainPiece = CreateMainPiece());
|
||||
}
|
||||
|
||||
// Most osu!taiko hitsounds are managed by the drum (see DrumSampleMapping).
|
||||
public override IEnumerable<HitSampleInfo> GetSamples() => Enumerable.Empty<HitSampleInfo>();
|
||||
|
||||
protected abstract SkinnableDrawable CreateMainPiece();
|
||||
}
|
||||
}
|
||||
|
@ -5,6 +5,7 @@
|
||||
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
using System.Threading;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Beatmaps.Formats;
|
||||
@ -15,7 +16,7 @@ using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Taiko.Objects
|
||||
{
|
||||
public class DrumRoll : TaikoStrongableHitObject, IHasPath
|
||||
public class DrumRoll : TaikoStrongableHitObject, IHasPath, IHasSliderVelocity
|
||||
{
|
||||
/// <summary>
|
||||
/// Drum roll distance that results in a duration of 1 speed-adjusted beat length.
|
||||
@ -35,6 +36,19 @@ namespace osu.Game.Rulesets.Taiko.Objects
|
||||
/// </summary>
|
||||
public double Velocity { get; private set; }
|
||||
|
||||
public BindableNumber<double> SliderVelocityBindable { get; } = new BindableDouble(1)
|
||||
{
|
||||
Precision = 0.01,
|
||||
MinValue = 0.1,
|
||||
MaxValue = 10
|
||||
};
|
||||
|
||||
public double SliderVelocity
|
||||
{
|
||||
get => SliderVelocityBindable.Value;
|
||||
set => SliderVelocityBindable.Value = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Numer of ticks per beat length.
|
||||
/// </summary>
|
||||
@ -52,7 +66,7 @@ namespace osu.Game.Rulesets.Taiko.Objects
|
||||
|
||||
TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(StartTime);
|
||||
|
||||
double scoringDistance = base_distance * difficulty.SliderMultiplier * DifficultyControlPoint.SliderVelocity;
|
||||
double scoringDistance = base_distance * difficulty.SliderMultiplier * SliderVelocity;
|
||||
Velocity = scoringDistance / timingPoint.BeatLength;
|
||||
|
||||
tickSpacing = timingPoint.BeatLength / TickRate;
|
||||
@ -81,7 +95,8 @@ namespace osu.Game.Rulesets.Taiko.Objects
|
||||
FirstTick = first,
|
||||
TickSpacing = tickSpacing,
|
||||
StartTime = t,
|
||||
IsStrong = IsStrong
|
||||
IsStrong = IsStrong,
|
||||
Samples = Samples
|
||||
});
|
||||
|
||||
first = false;
|
||||
@ -92,7 +107,11 @@ namespace osu.Game.Rulesets.Taiko.Objects
|
||||
|
||||
protected override HitWindows CreateHitWindows() => HitWindows.Empty;
|
||||
|
||||
protected override StrongNestedHitObject CreateStrongNestedHit(double startTime) => new StrongNestedHit { StartTime = startTime };
|
||||
protected override StrongNestedHitObject CreateStrongNestedHit(double startTime) => new StrongNestedHit
|
||||
{
|
||||
StartTime = startTime,
|
||||
Samples = Samples
|
||||
};
|
||||
|
||||
public class StrongNestedHit : StrongNestedHitObject
|
||||
{
|
||||
|
@ -33,7 +33,11 @@ namespace osu.Game.Rulesets.Taiko.Objects
|
||||
|
||||
public override double MaximumJudgementOffset => HitWindow;
|
||||
|
||||
protected override StrongNestedHitObject CreateStrongNestedHit(double startTime) => new StrongNestedHit { StartTime = startTime };
|
||||
protected override StrongNestedHitObject CreateStrongNestedHit(double startTime) => new StrongNestedHit
|
||||
{
|
||||
StartTime = startTime,
|
||||
Samples = Samples
|
||||
};
|
||||
|
||||
public class StrongNestedHit : StrongNestedHitObject
|
||||
{
|
||||
|
@ -63,7 +63,7 @@ namespace osu.Game.Rulesets.Taiko.Objects
|
||||
if (isRimType != rimSamples.Any())
|
||||
{
|
||||
if (isRimType)
|
||||
Samples.Add(new HitSampleInfo(HitSampleInfo.HIT_CLAP));
|
||||
Samples.Add(CreateHitSampleInfo(HitSampleInfo.HIT_CLAP));
|
||||
else
|
||||
{
|
||||
foreach (var sample in rimSamples)
|
||||
@ -72,7 +72,11 @@ namespace osu.Game.Rulesets.Taiko.Objects
|
||||
}
|
||||
}
|
||||
|
||||
protected override StrongNestedHitObject CreateStrongNestedHit(double startTime) => new StrongNestedHit { StartTime = startTime };
|
||||
protected override StrongNestedHitObject CreateStrongNestedHit(double startTime) => new StrongNestedHit
|
||||
{
|
||||
StartTime = startTime,
|
||||
Samples = Samples
|
||||
};
|
||||
|
||||
public class StrongNestedHit : StrongNestedHitObject
|
||||
{
|
||||
|
@ -33,7 +33,10 @@ namespace osu.Game.Rulesets.Taiko.Objects
|
||||
for (int i = 0; i < RequiredHits; i++)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
AddNested(new SwellTick());
|
||||
AddNested(new SwellTick
|
||||
{
|
||||
Samples = Samples
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -56,7 +56,7 @@ namespace osu.Game.Rulesets.Taiko.Objects
|
||||
if (IsStrongBindable.Value != strongSamples.Any())
|
||||
{
|
||||
if (IsStrongBindable.Value)
|
||||
Samples.Add(new HitSampleInfo(HitSampleInfo.HIT_FINISH));
|
||||
Samples.Add(CreateHitSampleInfo(HitSampleInfo.HIT_FINISH));
|
||||
else
|
||||
{
|
||||
foreach (var sample in strongSamples)
|
||||
|
@ -31,7 +31,7 @@ namespace osu.Game.Rulesets.Taiko.UI
|
||||
{
|
||||
public new BindableDouble TimeRange => base.TimeRange;
|
||||
|
||||
public readonly BindableBool LockPlayfieldMaxAspect = new BindableBool(true);
|
||||
public readonly BindableBool LockPlayfieldAspectRange = new BindableBool(true);
|
||||
|
||||
public new TaikoInputManager KeyBindingInputManager => (TaikoInputManager)base.KeyBindingInputManager;
|
||||
|
||||
@ -69,7 +69,9 @@ namespace osu.Game.Rulesets.Taiko.UI
|
||||
const float scroll_rate = 10;
|
||||
|
||||
// Since the time range will depend on a positional value, it is referenced to the x480 pixel space.
|
||||
float ratio = DrawHeight / 480;
|
||||
// Width is used because it defines how many notes fit on the playfield.
|
||||
// We clamp the ratio to the maximum aspect ratio to keep scroll speed consistent on widths lower than the default.
|
||||
float ratio = Math.Max(DrawSize.X / 768f, TaikoPlayfieldAdjustmentContainer.MAXIMUM_ASPECT);
|
||||
|
||||
TimeRange.Value = (Playfield.HitObjectContainer.DrawWidth / ratio) * scroll_rate;
|
||||
}
|
||||
@ -92,7 +94,7 @@ namespace osu.Game.Rulesets.Taiko.UI
|
||||
|
||||
public override PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer() => new TaikoPlayfieldAdjustmentContainer
|
||||
{
|
||||
LockPlayfieldMaxAspect = { BindTarget = LockPlayfieldMaxAspect }
|
||||
LockPlayfieldAspectRange = { BindTarget = LockPlayfieldAspectRange }
|
||||
};
|
||||
|
||||
protected override PassThroughInputManager CreateInputManager() => new TaikoInputManager(Ruleset.RulesetInfo);
|
||||
|
@ -2,6 +2,7 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using osu.Game.Audio;
|
||||
using osu.Game.Rulesets.Taiko.Objects;
|
||||
using osu.Game.Rulesets.UI;
|
||||
@ -17,12 +18,12 @@ namespace osu.Game.Rulesets.Taiko.UI
|
||||
|
||||
public void Play(HitType hitType)
|
||||
{
|
||||
var hitObject = GetMostValidObject();
|
||||
var hitSample = GetMostValidObject()?.Samples?.FirstOrDefault(o => o.Name == HitSampleInfo.HIT_NORMAL);
|
||||
|
||||
if (hitObject == null)
|
||||
if (hitSample == null)
|
||||
return;
|
||||
|
||||
PlaySamples(new ISampleInfo[] { hitObject.SampleControlPoint.GetSampleInfo(hitType == HitType.Rim ? HitSampleInfo.HIT_CLAP : HitSampleInfo.HIT_NORMAL) });
|
||||
PlaySamples(new ISampleInfo[] { new HitSampleInfo(hitType == HitType.Rim ? HitSampleInfo.HIT_CLAP : HitSampleInfo.HIT_NORMAL, hitSample.Bank, volume: hitSample.Volume) });
|
||||
}
|
||||
|
||||
public override void Play() => throw new InvalidOperationException(@"Use override with HitType parameter instead");
|
||||
|
@ -11,9 +11,11 @@ namespace osu.Game.Rulesets.Taiko.UI
|
||||
public partial class TaikoPlayfieldAdjustmentContainer : PlayfieldAdjustmentContainer
|
||||
{
|
||||
private const float default_relative_height = TaikoPlayfield.DEFAULT_HEIGHT / 768;
|
||||
private const float default_aspect = 16f / 9f;
|
||||
|
||||
public readonly IBindable<bool> LockPlayfieldMaxAspect = new BindableBool(true);
|
||||
public const float MAXIMUM_ASPECT = 16f / 9f;
|
||||
public const float MINIMUM_ASPECT = 5f / 4f;
|
||||
|
||||
public readonly IBindable<bool> LockPlayfieldAspectRange = new BindableBool(true);
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
@ -26,12 +28,22 @@ namespace osu.Game.Rulesets.Taiko.UI
|
||||
//
|
||||
// As a middle-ground, the aspect ratio can still be adjusted in the downwards direction but has a maximum limit.
|
||||
// This is still a bit weird, because readability changes with window size, but it is what it is.
|
||||
if (LockPlayfieldMaxAspect.Value && Parent.ChildSize.X / Parent.ChildSize.Y > default_aspect)
|
||||
height *= Math.Clamp(Parent.ChildSize.X / Parent.ChildSize.Y, 0.4f, 4) / default_aspect;
|
||||
if (LockPlayfieldAspectRange.Value)
|
||||
{
|
||||
float currentAspect = Parent.ChildSize.X / Parent.ChildSize.Y;
|
||||
|
||||
if (currentAspect > MAXIMUM_ASPECT)
|
||||
height *= currentAspect / MAXIMUM_ASPECT;
|
||||
else if (currentAspect < MINIMUM_ASPECT)
|
||||
height *= currentAspect / MINIMUM_ASPECT;
|
||||
}
|
||||
|
||||
// Limit the maximum relative height of the playfield to one-third of available area to avoid it masking out on extreme resolutions.
|
||||
height = Math.Min(height, 1f / 3f);
|
||||
Height = height;
|
||||
|
||||
// Position the taiko playfield exactly one playfield from the top of the screen.
|
||||
// Position the taiko playfield exactly one playfield from the top of the screen, if there is enough space for it.
|
||||
// Note that the relative height cannot exceed one-third - if that limit is hit, the playfield will be exactly centered.
|
||||
RelativePositionAxes = Axes.Y;
|
||||
Y = height;
|
||||
}
|
||||
|
@ -253,17 +253,17 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
||||
|
||||
var soundPoint = controlPoints.SamplePointAt(0);
|
||||
Assert.AreEqual(956, soundPoint.Time);
|
||||
Assert.AreEqual("soft", soundPoint.SampleBank);
|
||||
Assert.AreEqual(HitSampleInfo.BANK_SOFT, soundPoint.SampleBank);
|
||||
Assert.AreEqual(60, soundPoint.SampleVolume);
|
||||
|
||||
soundPoint = controlPoints.SamplePointAt(53373);
|
||||
Assert.AreEqual(53373, soundPoint.Time);
|
||||
Assert.AreEqual("soft", soundPoint.SampleBank);
|
||||
Assert.AreEqual(HitSampleInfo.BANK_SOFT, soundPoint.SampleBank);
|
||||
Assert.AreEqual(60, soundPoint.SampleVolume);
|
||||
|
||||
soundPoint = controlPoints.SamplePointAt(119637);
|
||||
Assert.AreEqual(119637, soundPoint.Time);
|
||||
Assert.AreEqual("soft", soundPoint.SampleBank);
|
||||
Assert.AreEqual(HitSampleInfo.BANK_SOFT, soundPoint.SampleBank);
|
||||
Assert.AreEqual(80, soundPoint.SampleVolume);
|
||||
|
||||
var effectPoint = controlPoints.EffectPointAt(0);
|
||||
@ -305,10 +305,10 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
||||
Assert.That(controlPoints.EffectPointAt(2500).KiaiMode, Is.False);
|
||||
Assert.That(controlPoints.EffectPointAt(3500).KiaiMode, Is.True);
|
||||
|
||||
Assert.That(controlPoints.SamplePointAt(500).SampleBank, Is.EqualTo("drum"));
|
||||
Assert.That(controlPoints.SamplePointAt(1500).SampleBank, Is.EqualTo("drum"));
|
||||
Assert.That(controlPoints.SamplePointAt(2500).SampleBank, Is.EqualTo("normal"));
|
||||
Assert.That(controlPoints.SamplePointAt(3500).SampleBank, Is.EqualTo("drum"));
|
||||
Assert.That(controlPoints.SamplePointAt(500).SampleBank, Is.EqualTo(HitSampleInfo.BANK_DRUM));
|
||||
Assert.That(controlPoints.SamplePointAt(1500).SampleBank, Is.EqualTo(HitSampleInfo.BANK_DRUM));
|
||||
Assert.That(controlPoints.SamplePointAt(2500).SampleBank, Is.EqualTo(HitSampleInfo.BANK_NORMAL));
|
||||
Assert.That(controlPoints.SamplePointAt(3500).SampleBank, Is.EqualTo(HitSampleInfo.BANK_DRUM));
|
||||
|
||||
Assert.That(controlPoints.TimingPointAt(500).BeatLength, Is.EqualTo(500).Within(0.1));
|
||||
Assert.That(controlPoints.TimingPointAt(1500).BeatLength, Is.EqualTo(500).Within(0.1));
|
||||
@ -510,7 +510,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
||||
Assert.AreEqual("Gameplay/soft-hitnormal8", getTestableSampleInfo(hitObjects[4]).LookupNames.First());
|
||||
}
|
||||
|
||||
static HitSampleInfo getTestableSampleInfo(HitObject hitObject) => hitObject.SampleControlPoint.ApplyTo(hitObject.Samples[0]);
|
||||
static HitSampleInfo getTestableSampleInfo(HitObject hitObject) => hitObject.Samples[0];
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -528,7 +528,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
||||
Assert.AreEqual("Gameplay/normal-hitnormal3", getTestableSampleInfo(hitObjects[2]).LookupNames.First());
|
||||
}
|
||||
|
||||
static HitSampleInfo getTestableSampleInfo(HitObject hitObject) => hitObject.SampleControlPoint.ApplyTo(hitObject.Samples[0]);
|
||||
static HitSampleInfo getTestableSampleInfo(HitObject hitObject) => hitObject.Samples[0];
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -548,7 +548,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
||||
Assert.AreEqual(70, getTestableSampleInfo(hitObjects[3]).Volume);
|
||||
}
|
||||
|
||||
static HitSampleInfo getTestableSampleInfo(HitObject hitObject) => hitObject.SampleControlPoint.ApplyTo(hitObject.Samples[0]);
|
||||
static HitSampleInfo getTestableSampleInfo(HitObject hitObject) => hitObject.Samples[0];
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
@ -95,6 +95,27 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestLoopWithoutExplicitFadeOut()
|
||||
{
|
||||
var decoder = new LegacyStoryboardDecoder();
|
||||
|
||||
using (var resStream = TestResources.OpenResource("animation-loop-no-explicit-end-time.osb"))
|
||||
using (var stream = new LineBufferedReader(resStream))
|
||||
{
|
||||
var storyboard = decoder.Decode(stream);
|
||||
|
||||
StoryboardLayer background = storyboard.Layers.Single(l => l.Depth == 3);
|
||||
Assert.AreEqual(1, background.Elements.Count);
|
||||
|
||||
Assert.AreEqual(2000, background.Elements[0].StartTime);
|
||||
Assert.AreEqual(2000, (background.Elements[0] as StoryboardAnimation)?.EarliestTransformTime);
|
||||
|
||||
Assert.AreEqual(3000, (background.Elements[0] as StoryboardAnimation)?.GetEndTime());
|
||||
Assert.AreEqual(12000, (background.Elements[0] as StoryboardAnimation)?.EndTimeForDisplay);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestCorrectAnimationStartTime()
|
||||
{
|
||||
|
@ -1,19 +1,26 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.Extensions.ObjectExtensions;
|
||||
using osu.Framework.Platform;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.Overlays.Notifications;
|
||||
using Realms;
|
||||
|
||||
namespace osu.Game.Tests.Database
|
||||
{
|
||||
[TestFixture]
|
||||
public class LegacyExporterTest
|
||||
public class LegacyModelExporterTest
|
||||
{
|
||||
private TestLegacyExporter legacyExporter = null!;
|
||||
private TestLegacyModelExporter legacyExporter = null!;
|
||||
private TemporaryNativeStorage storage = null!;
|
||||
|
||||
private const string short_filename = "normal file name";
|
||||
@ -25,15 +32,15 @@ namespace osu.Game.Tests.Database
|
||||
public void SetUp()
|
||||
{
|
||||
storage = new TemporaryNativeStorage("export-storage");
|
||||
legacyExporter = new TestLegacyExporter(storage);
|
||||
legacyExporter = new TestLegacyModelExporter(storage);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ExportFileWithNormalNameTest()
|
||||
{
|
||||
var item = new TestPathInfo(short_filename);
|
||||
var item = new TestModel(short_filename);
|
||||
|
||||
Assert.That(item.Filename.Length, Is.LessThan(TestLegacyExporter.MAX_FILENAME_LENGTH));
|
||||
Assert.That(item.Filename.Length, Is.LessThan(TestLegacyModelExporter.MAX_FILENAME_LENGTH));
|
||||
|
||||
exportItemAndAssert(item, short_filename);
|
||||
}
|
||||
@ -41,9 +48,9 @@ namespace osu.Game.Tests.Database
|
||||
[Test]
|
||||
public void ExportFileWithNormalNameMultipleTimesTest()
|
||||
{
|
||||
var item = new TestPathInfo(short_filename);
|
||||
var item = new TestModel(short_filename);
|
||||
|
||||
Assert.That(item.Filename.Length, Is.LessThan(TestLegacyExporter.MAX_FILENAME_LENGTH));
|
||||
Assert.That(item.Filename.Length, Is.LessThan(TestLegacyModelExporter.MAX_FILENAME_LENGTH));
|
||||
|
||||
//Export multiple times
|
||||
for (int i = 0; i < 100; i++)
|
||||
@ -56,24 +63,24 @@ namespace osu.Game.Tests.Database
|
||||
[Test]
|
||||
public void ExportFileWithSuperLongNameTest()
|
||||
{
|
||||
int expectedLength = TestLegacyExporter.MAX_FILENAME_LENGTH - (legacyExporter.GetExtension().Length);
|
||||
int expectedLength = TestLegacyModelExporter.MAX_FILENAME_LENGTH - (legacyExporter.GetExtension().Length);
|
||||
string expectedName = long_filename.Remove(expectedLength);
|
||||
|
||||
var item = new TestPathInfo(long_filename);
|
||||
var item = new TestModel(long_filename);
|
||||
|
||||
Assert.That(item.Filename.Length, Is.GreaterThan(TestLegacyExporter.MAX_FILENAME_LENGTH));
|
||||
Assert.That(item.Filename.Length, Is.GreaterThan(TestLegacyModelExporter.MAX_FILENAME_LENGTH));
|
||||
exportItemAndAssert(item, expectedName);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ExportFileWithSuperLongNameMultipleTimesTest()
|
||||
{
|
||||
int expectedLength = TestLegacyExporter.MAX_FILENAME_LENGTH - (legacyExporter.GetExtension().Length);
|
||||
int expectedLength = TestLegacyModelExporter.MAX_FILENAME_LENGTH - (legacyExporter.GetExtension().Length);
|
||||
string expectedName = long_filename.Remove(expectedLength);
|
||||
|
||||
var item = new TestPathInfo(long_filename);
|
||||
var item = new TestModel(long_filename);
|
||||
|
||||
Assert.That(item.Filename.Length, Is.GreaterThan(TestLegacyExporter.MAX_FILENAME_LENGTH));
|
||||
Assert.That(item.Filename.Length, Is.GreaterThan(TestLegacyModelExporter.MAX_FILENAME_LENGTH));
|
||||
|
||||
//Export multiple times
|
||||
for (int i = 0; i < 100; i++)
|
||||
@ -83,9 +90,12 @@ namespace osu.Game.Tests.Database
|
||||
}
|
||||
}
|
||||
|
||||
private void exportItemAndAssert(IHasNamedFiles item, string expectedName)
|
||||
private void exportItemAndAssert(TestModel item, string expectedName)
|
||||
{
|
||||
Assert.DoesNotThrow(() => legacyExporter.Export(item));
|
||||
Assert.DoesNotThrow(() =>
|
||||
{
|
||||
Task.Run(() => legacyExporter.ExportAsync(new RealmLiveUnmanaged<TestModel>(item))).WaitSafely();
|
||||
});
|
||||
Assert.That(storage.Exists($"exports/{expectedName}{legacyExporter.GetExtension()}"), Is.True);
|
||||
}
|
||||
|
||||
@ -96,30 +106,36 @@ namespace osu.Game.Tests.Database
|
||||
storage.Dispose();
|
||||
}
|
||||
|
||||
private class TestPathInfo : IHasNamedFiles
|
||||
private class TestLegacyModelExporter : LegacyExporter<TestModel>
|
||||
{
|
||||
public string Filename { get; }
|
||||
|
||||
public IEnumerable<INamedFileUsage> Files { get; } = new List<INamedFileUsage>();
|
||||
|
||||
public TestPathInfo(string filename)
|
||||
{
|
||||
Filename = filename;
|
||||
}
|
||||
|
||||
public override string ToString() => Filename;
|
||||
}
|
||||
|
||||
private class TestLegacyExporter : LegacyExporter<IHasNamedFiles>
|
||||
{
|
||||
public TestLegacyExporter(Storage storage)
|
||||
public TestLegacyModelExporter(Storage storage)
|
||||
: base(storage)
|
||||
{
|
||||
}
|
||||
|
||||
public string GetExtension() => FileExtension;
|
||||
|
||||
public override void ExportToStream(TestModel model, Stream outputStream, ProgressNotification? notification, CancellationToken cancellationToken = default)
|
||||
{
|
||||
}
|
||||
|
||||
protected override string FileExtension => ".test";
|
||||
}
|
||||
|
||||
private class TestModel : RealmObject, IHasNamedFiles, IHasGuidPrimaryKey
|
||||
{
|
||||
public Guid ID => Guid.Empty;
|
||||
|
||||
public string Filename { get; }
|
||||
|
||||
public IEnumerable<INamedFileUsage> Files { get; } = new List<INamedFileUsage>();
|
||||
|
||||
public TestModel(string filename)
|
||||
{
|
||||
Filename = filename;
|
||||
}
|
||||
|
||||
public override string ToString() => Filename;
|
||||
}
|
||||
}
|
||||
}
|
@ -37,45 +37,6 @@ namespace osu.Game.Tests.Editing.Checks
|
||||
cpi.Add(2000, new SampleControlPoint { SampleVolume = volume_muted });
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestNormalControlPointVolume()
|
||||
{
|
||||
var hitCircle = new HitCircle
|
||||
{
|
||||
StartTime = 0,
|
||||
Samples = new List<HitSampleInfo> { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) }
|
||||
};
|
||||
hitCircle.ApplyDefaults(cpi, new BeatmapDifficulty());
|
||||
|
||||
assertOk(new List<HitObject> { hitCircle });
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestLowControlPointVolume()
|
||||
{
|
||||
var hitCircle = new HitCircle
|
||||
{
|
||||
StartTime = 1000,
|
||||
Samples = new List<HitSampleInfo> { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) }
|
||||
};
|
||||
hitCircle.ApplyDefaults(cpi, new BeatmapDifficulty());
|
||||
|
||||
assertLowVolume(new List<HitObject> { hitCircle });
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestMutedControlPointVolume()
|
||||
{
|
||||
var hitCircle = new HitCircle
|
||||
{
|
||||
StartTime = 2000,
|
||||
Samples = new List<HitSampleInfo> { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) }
|
||||
};
|
||||
hitCircle.ApplyDefaults(cpi, new BeatmapDifficulty());
|
||||
|
||||
assertMuted(new List<HitObject> { hitCircle });
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestNormalSampleVolume()
|
||||
{
|
||||
@ -122,7 +83,7 @@ namespace osu.Game.Tests.Editing.Checks
|
||||
var sliderHead = new SliderHeadCircle
|
||||
{
|
||||
StartTime = 0,
|
||||
Samples = new List<HitSampleInfo> { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) }
|
||||
Samples = new List<HitSampleInfo> { new HitSampleInfo(HitSampleInfo.HIT_NORMAL, volume: volume_regular) }
|
||||
};
|
||||
sliderHead.ApplyDefaults(cpi, new BeatmapDifficulty());
|
||||
|
||||
@ -135,7 +96,7 @@ namespace osu.Game.Tests.Editing.Checks
|
||||
|
||||
var slider = new MockNestableHitObject(new List<HitObject> { sliderHead, sliderTick, }, startTime: 0, endTime: 500)
|
||||
{
|
||||
Samples = new List<HitSampleInfo> { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) }
|
||||
Samples = new List<HitSampleInfo> { new HitSampleInfo(HitSampleInfo.HIT_NORMAL, volume: volume_regular) }
|
||||
};
|
||||
slider.ApplyDefaults(cpi, new BeatmapDifficulty());
|
||||
|
||||
@ -155,13 +116,13 @@ namespace osu.Game.Tests.Editing.Checks
|
||||
var sliderTick = new SliderTick
|
||||
{
|
||||
StartTime = 250,
|
||||
Samples = new List<HitSampleInfo> { new HitSampleInfo("slidertick") }
|
||||
Samples = new List<HitSampleInfo> { new HitSampleInfo("slidertick", volume: volume_regular) }
|
||||
};
|
||||
sliderTick.ApplyDefaults(cpi, new BeatmapDifficulty());
|
||||
|
||||
var slider = new MockNestableHitObject(new List<HitObject> { sliderHead, sliderTick, }, startTime: 0, endTime: 500)
|
||||
{
|
||||
Samples = new List<HitSampleInfo> { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) } // Applies to the tail.
|
||||
Samples = new List<HitSampleInfo> { new HitSampleInfo(HitSampleInfo.HIT_NORMAL, volume: volume_regular) } // Applies to the tail.
|
||||
};
|
||||
slider.ApplyDefaults(cpi, new BeatmapDifficulty());
|
||||
|
||||
@ -174,14 +135,14 @@ namespace osu.Game.Tests.Editing.Checks
|
||||
var sliderHead = new SliderHeadCircle
|
||||
{
|
||||
StartTime = 0,
|
||||
Samples = new List<HitSampleInfo> { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) }
|
||||
Samples = new List<HitSampleInfo> { new HitSampleInfo(HitSampleInfo.HIT_NORMAL, volume: volume_regular) }
|
||||
};
|
||||
sliderHead.ApplyDefaults(cpi, new BeatmapDifficulty());
|
||||
|
||||
var sliderTick = new SliderTick
|
||||
{
|
||||
StartTime = 250,
|
||||
Samples = new List<HitSampleInfo> { new HitSampleInfo("slidertick") }
|
||||
Samples = new List<HitSampleInfo> { new HitSampleInfo("slidertick", volume: volume_regular) }
|
||||
};
|
||||
sliderTick.ApplyDefaults(cpi, new BeatmapDifficulty());
|
||||
|
||||
@ -194,59 +155,6 @@ namespace osu.Game.Tests.Editing.Checks
|
||||
assertMutedPassive(new List<HitObject> { slider });
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestMutedControlPointVolumeSliderHead()
|
||||
{
|
||||
var sliderHead = new SliderHeadCircle
|
||||
{
|
||||
StartTime = 2000,
|
||||
Samples = new List<HitSampleInfo> { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) }
|
||||
};
|
||||
sliderHead.ApplyDefaults(cpi, new BeatmapDifficulty());
|
||||
|
||||
var sliderTick = new SliderTick
|
||||
{
|
||||
StartTime = 2250,
|
||||
Samples = new List<HitSampleInfo> { new HitSampleInfo("slidertick") }
|
||||
};
|
||||
sliderTick.ApplyDefaults(cpi, new BeatmapDifficulty());
|
||||
|
||||
var slider = new MockNestableHitObject(new List<HitObject> { sliderHead, sliderTick, }, startTime: 2000, endTime: 2500)
|
||||
{
|
||||
Samples = new List<HitSampleInfo> { new HitSampleInfo(HitSampleInfo.HIT_NORMAL, volume: volume_regular) }
|
||||
};
|
||||
slider.ApplyDefaults(cpi, new BeatmapDifficulty());
|
||||
|
||||
assertMuted(new List<HitObject> { slider });
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestMutedControlPointVolumeSliderTail()
|
||||
{
|
||||
var sliderHead = new SliderHeadCircle
|
||||
{
|
||||
StartTime = 0,
|
||||
Samples = new List<HitSampleInfo> { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) }
|
||||
};
|
||||
sliderHead.ApplyDefaults(cpi, new BeatmapDifficulty());
|
||||
|
||||
var sliderTick = new SliderTick
|
||||
{
|
||||
StartTime = 250,
|
||||
Samples = new List<HitSampleInfo> { new HitSampleInfo("slidertick") }
|
||||
};
|
||||
sliderTick.ApplyDefaults(cpi, new BeatmapDifficulty());
|
||||
|
||||
// Ends after the 5% control point.
|
||||
var slider = new MockNestableHitObject(new List<HitObject> { sliderHead, sliderTick, }, startTime: 0, endTime: 2500)
|
||||
{
|
||||
Samples = new List<HitSampleInfo> { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) }
|
||||
};
|
||||
slider.ApplyDefaults(cpi, new BeatmapDifficulty());
|
||||
|
||||
assertMutedPassive(new List<HitObject> { slider });
|
||||
}
|
||||
|
||||
private void assertOk(List<HitObject> hitObjects)
|
||||
{
|
||||
Assert.That(check.Run(getContext(hitObjects)), Is.Empty);
|
||||
|
@ -14,6 +14,7 @@ using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Rulesets.Osu.Beatmaps;
|
||||
using osu.Game.Rulesets.Osu.Edit;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Screens.Edit;
|
||||
using osu.Game.Tests.Visual;
|
||||
|
||||
@ -74,12 +75,9 @@ namespace osu.Game.Tests.Editing
|
||||
[TestCase(2)]
|
||||
public void TestSpeedMultiplierDoesNotChangeDistanceSnap(float multiplier)
|
||||
{
|
||||
assertSnapDistance(100, new HitObject
|
||||
assertSnapDistance(100, new Slider
|
||||
{
|
||||
DifficultyControlPoint = new DifficultyControlPoint
|
||||
{
|
||||
SliderVelocity = multiplier
|
||||
}
|
||||
SliderVelocity = multiplier
|
||||
}, false);
|
||||
}
|
||||
|
||||
@ -87,12 +85,9 @@ namespace osu.Game.Tests.Editing
|
||||
[TestCase(2)]
|
||||
public void TestSpeedMultiplierDoesChangeDistanceSnap(float multiplier)
|
||||
{
|
||||
assertSnapDistance(100 * multiplier, new HitObject
|
||||
assertSnapDistance(100 * multiplier, new Slider
|
||||
{
|
||||
DifficultyControlPoint = new DifficultyControlPoint
|
||||
{
|
||||
SliderVelocity = multiplier
|
||||
}
|
||||
SliderVelocity = multiplier
|
||||
}, true);
|
||||
}
|
||||
|
||||
@ -114,12 +109,9 @@ namespace osu.Game.Tests.Editing
|
||||
const float base_distance = 100;
|
||||
const float slider_velocity = 1.2f;
|
||||
|
||||
var referenceObject = new HitObject
|
||||
var referenceObject = new Slider
|
||||
{
|
||||
DifficultyControlPoint = new DifficultyControlPoint
|
||||
{
|
||||
SliderVelocity = slider_velocity
|
||||
}
|
||||
SliderVelocity = slider_velocity
|
||||
};
|
||||
|
||||
assertSnapDistance(base_distance * slider_velocity, referenceObject, true);
|
||||
|
@ -49,5 +49,31 @@ namespace osu.Game.Tests.Mods
|
||||
Assert.That(mod3, Is.EqualTo(mod2));
|
||||
Assert.That(doubleConvertedMod3, Is.EqualTo(doubleConvertedMod2));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestModWithMultipleSettings()
|
||||
{
|
||||
var ruleset = new OsuRuleset();
|
||||
|
||||
var mod1 = new OsuModDifficultyAdjust { OverallDifficulty = { Value = 10 }, CircleSize = { Value = 0 } };
|
||||
var mod2 = new OsuModDifficultyAdjust { OverallDifficulty = { Value = 10 }, CircleSize = { Value = 6 } };
|
||||
var mod3 = new OsuModDifficultyAdjust { OverallDifficulty = { Value = 10 }, CircleSize = { Value = 6 } };
|
||||
|
||||
var doubleConvertedMod1 = new APIMod(mod1).ToMod(ruleset);
|
||||
var doubleConvertedMod2 = new APIMod(mod2).ToMod(ruleset);
|
||||
var doubleConvertedMod3 = new APIMod(mod3).ToMod(ruleset);
|
||||
|
||||
Assert.That(mod1, Is.Not.EqualTo(mod2));
|
||||
Assert.That(doubleConvertedMod1, Is.Not.EqualTo(doubleConvertedMod2));
|
||||
|
||||
Assert.That(mod2, Is.EqualTo(mod2));
|
||||
Assert.That(doubleConvertedMod2, Is.EqualTo(doubleConvertedMod2));
|
||||
|
||||
Assert.That(mod2, Is.EqualTo(mod3));
|
||||
Assert.That(doubleConvertedMod2, Is.EqualTo(doubleConvertedMod3));
|
||||
|
||||
Assert.That(mod3, Is.EqualTo(mod2));
|
||||
Assert.That(doubleConvertedMod3, Is.EqualTo(doubleConvertedMod2));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,9 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Osu.Mods;
|
||||
|
||||
@ -10,7 +13,7 @@ namespace osu.Game.Tests.Mods
|
||||
public class ModSettingsTest
|
||||
{
|
||||
[Test]
|
||||
public void TestModSettingsUnboundWhenCopied()
|
||||
public void TestModSettingsUnboundWhenCloned()
|
||||
{
|
||||
var original = new OsuModDoubleTime();
|
||||
var copy = (OsuModDoubleTime)original.DeepClone();
|
||||
@ -22,7 +25,7 @@ namespace osu.Game.Tests.Mods
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestMultiModSettingsUnboundWhenCopied()
|
||||
public void TestMultiModSettingsUnboundWhenCloned()
|
||||
{
|
||||
var original = new MultiMod(new OsuModDoubleTime());
|
||||
var copy = (MultiMod)original.DeepClone();
|
||||
@ -32,5 +35,67 @@ namespace osu.Game.Tests.Mods
|
||||
Assert.That(((OsuModDoubleTime)original.Mods[0]).SpeedChange.Value, Is.EqualTo(2.0));
|
||||
Assert.That(((OsuModDoubleTime)copy.Mods[0]).SpeedChange.Value, Is.EqualTo(1.5));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestDifferentTypeSettingsKeptWhenCopied()
|
||||
{
|
||||
const double setting_change = 50.4;
|
||||
|
||||
var modDouble = new TestNonMatchingSettingTypeModDouble { TestSetting = { Value = setting_change } };
|
||||
var modBool = new TestNonMatchingSettingTypeModBool { TestSetting = { Default = false, Value = true } };
|
||||
var modInt = new TestNonMatchingSettingTypeModInt { TestSetting = { Value = (int)setting_change / 2 } };
|
||||
|
||||
modDouble.CopyCommonSettingsFrom(modBool);
|
||||
modDouble.CopyCommonSettingsFrom(modInt);
|
||||
modBool.CopyCommonSettingsFrom(modDouble);
|
||||
modBool.CopyCommonSettingsFrom(modInt);
|
||||
modInt.CopyCommonSettingsFrom(modDouble);
|
||||
modInt.CopyCommonSettingsFrom(modBool);
|
||||
|
||||
Assert.That(modDouble.TestSetting.Value, Is.EqualTo(setting_change));
|
||||
Assert.That(modBool.TestSetting.Value, Is.EqualTo(true));
|
||||
Assert.That(modInt.TestSetting.Value, Is.EqualTo((int)setting_change / 2));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestDefaultValueKeptWhenCopied()
|
||||
{
|
||||
var modBoolTrue = new TestNonMatchingSettingTypeModBool { TestSetting = { Default = true, Value = false } };
|
||||
var modBoolFalse = new TestNonMatchingSettingTypeModBool { TestSetting = { Default = false, Value = true } };
|
||||
|
||||
modBoolFalse.CopyCommonSettingsFrom(modBoolTrue);
|
||||
|
||||
Assert.That(modBoolFalse.TestSetting.Default, Is.EqualTo(false));
|
||||
Assert.That(modBoolFalse.TestSetting.Value, Is.EqualTo(modBoolTrue.TestSetting.Value));
|
||||
}
|
||||
|
||||
private class TestNonMatchingSettingTypeModDouble : TestNonMatchingSettingTypeMod
|
||||
{
|
||||
public override string Acronym => "NMD";
|
||||
public override BindableNumber<double> TestSetting { get; } = new BindableDouble();
|
||||
}
|
||||
|
||||
private class TestNonMatchingSettingTypeModInt : TestNonMatchingSettingTypeMod
|
||||
{
|
||||
public override string Acronym => "NMI";
|
||||
public override BindableNumber<int> TestSetting { get; } = new BindableInt();
|
||||
}
|
||||
|
||||
private class TestNonMatchingSettingTypeModBool : TestNonMatchingSettingTypeMod
|
||||
{
|
||||
public override string Acronym => "NMB";
|
||||
public override Bindable<bool> TestSetting { get; } = new BindableBool();
|
||||
}
|
||||
|
||||
private abstract class TestNonMatchingSettingTypeMod : Mod
|
||||
{
|
||||
public override string Name => "Non-matching setting type mod";
|
||||
public override LocalisableString Description => "Description";
|
||||
public override double ScoreMultiplier => 1;
|
||||
public override ModType Type => ModType.Conversion;
|
||||
|
||||
[SettingSource("Test setting")]
|
||||
public abstract IBindable TestSetting { get; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,11 +1,14 @@
|
||||
#include "sh_Utils.h"
|
||||
#define HIGH_PRECISION_VERTEX
|
||||
|
||||
varying mediump vec2 v_TexCoord;
|
||||
varying mediump vec4 v_TexRect;
|
||||
#include "sh_Utils.h"
|
||||
#include "sh_Masking.h"
|
||||
|
||||
layout(location = 2) in highp vec2 v_TexCoord;
|
||||
|
||||
layout(location = 0) out vec4 o_Colour;
|
||||
|
||||
void main(void)
|
||||
{
|
||||
float hueValue = v_TexCoord.x / (v_TexRect[2] - v_TexRect[0]);
|
||||
gl_FragColor = hsv2rgb(vec4(hueValue, 1, 1, 1));
|
||||
highp float hueValue = v_TexCoord.x / (v_TexRect[2] - v_TexRect[0]);
|
||||
o_Colour = getRoundedColor(hsv2rgb(vec4(hueValue, 1, 1, 1)), v_TexCoord);
|
||||
}
|
||||
|
||||
|
@ -1,31 +1,25 @@
|
||||
#include "sh_Utils.h"
|
||||
layout(location = 0) in highp vec2 m_Position;
|
||||
layout(location = 1) in lowp vec4 m_Colour;
|
||||
layout(location = 2) in highp vec2 m_TexCoord;
|
||||
layout(location = 3) in highp vec4 m_TexRect;
|
||||
layout(location = 4) in mediump vec2 m_BlendRange;
|
||||
|
||||
attribute highp vec2 m_Position;
|
||||
attribute lowp vec4 m_Colour;
|
||||
attribute mediump vec2 m_TexCoord;
|
||||
attribute mediump vec4 m_TexRect;
|
||||
attribute mediump vec2 m_BlendRange;
|
||||
|
||||
varying highp vec2 v_MaskingPosition;
|
||||
varying lowp vec4 v_Colour;
|
||||
varying mediump vec2 v_TexCoord;
|
||||
varying mediump vec4 v_TexRect;
|
||||
varying mediump vec2 v_BlendRange;
|
||||
|
||||
uniform highp mat4 g_ProjMatrix;
|
||||
uniform highp mat3 g_ToMaskingSpace;
|
||||
layout(location = 0) out highp vec2 v_MaskingPosition;
|
||||
layout(location = 1) out lowp vec4 v_Colour;
|
||||
layout(location = 2) out highp vec2 v_TexCoord;
|
||||
layout(location = 3) out highp vec4 v_TexRect;
|
||||
layout(location = 4) out mediump vec2 v_BlendRange;
|
||||
|
||||
void main(void)
|
||||
{
|
||||
// Transform from screen space to masking space.
|
||||
highp vec3 maskingPos = g_ToMaskingSpace * vec3(m_Position, 1.0);
|
||||
v_MaskingPosition = maskingPos.xy / maskingPos.z;
|
||||
// Transform from screen space to masking space.
|
||||
highp vec3 maskingPos = g_ToMaskingSpace * vec3(m_Position, 1.0);
|
||||
v_MaskingPosition = maskingPos.xy / maskingPos.z;
|
||||
|
||||
v_Colour = m_Colour;
|
||||
v_TexCoord = m_TexCoord;
|
||||
v_TexRect = m_TexRect;
|
||||
v_BlendRange = m_BlendRange;
|
||||
v_Colour = m_Colour;
|
||||
v_TexCoord = m_TexCoord;
|
||||
v_TexRect = m_TexRect;
|
||||
v_BlendRange = m_BlendRange;
|
||||
|
||||
gl_Position = gProjMatrix * vec4(m_Position, 1.0, 1.0);
|
||||
gl_Position = g_ProjMatrix * vec4(m_Position, 1.0, 1.0);
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,6 @@
|
||||
[Events]
|
||||
//Storyboard Layer 0 (Background)
|
||||
Animation,Background,Centre,"img.jpg",320,240,2,150,LoopForever
|
||||
F,0,2000,,0,1
|
||||
L,2000,10
|
||||
F,18,0,1000,1,0
|
31
osu.Game.Tests/Resources/storyboard_only_video.osu
Normal file
31
osu.Game.Tests/Resources/storyboard_only_video.osu
Normal file
@ -0,0 +1,31 @@
|
||||
osu file format v14
|
||||
|
||||
[Events]
|
||||
//Background and Video events
|
||||
0,0,"BG.jpg",0,0
|
||||
Video,0,"video.avi"
|
||||
//Break Periods
|
||||
//Storyboard Layer 0 (Background)
|
||||
//Storyboard Layer 1 (Fail)
|
||||
//Storyboard Layer 2 (Pass)
|
||||
//Storyboard Layer 3 (Foreground)
|
||||
//Storyboard Layer 4 (Overlay)
|
||||
//Storyboard Sound Samples
|
||||
|
||||
[TimingPoints]
|
||||
1674,333.333333333333,4,2,1,70,1,0
|
||||
1674,-100,4,2,1,70,0,0
|
||||
3340,-100,4,2,1,70,0,0
|
||||
3507,-100,4,2,1,70,0,0
|
||||
3673,-100,4,2,1,70,0,0
|
||||
|
||||
[Colours]
|
||||
Combo1 : 240,80,80
|
||||
Combo2 : 171,252,203
|
||||
Combo3 : 128,128,255
|
||||
Combo4 : 249,254,186
|
||||
|
||||
[HitObjects]
|
||||
148,303,1674,5,6,3:2:0:0:
|
||||
378,252,1840,1,0,0:0:0:0:
|
||||
389,270,2340,5,2,0:1:0:0:
|
@ -10,7 +10,6 @@ using System.Runtime.CompilerServices;
|
||||
using System.Threading.Tasks;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.Platform;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.Extensions;
|
||||
@ -120,10 +119,7 @@ namespace osu.Game.Tests.Skins.IO
|
||||
var import1 = await loadSkinIntoOsu(osu, new ImportTask(createOskWithIni("name 1", "author 1"), "custom.osk"));
|
||||
assertCorrectMetadata(import1, "name 1 [custom]", "author 1", osu);
|
||||
|
||||
import1.PerformRead(s =>
|
||||
{
|
||||
new LegacySkinExporter(osu.Dependencies.Get<Storage>()).ExportModelTo(s, exportStream);
|
||||
});
|
||||
await new LegacySkinExporter(osu.Dependencies.Get<Storage>()).ExportToStreamAsync(import1, exportStream);
|
||||
|
||||
string exportFilename = import1.GetDisplayString();
|
||||
|
||||
@ -141,10 +137,7 @@ namespace osu.Game.Tests.Skins.IO
|
||||
var import1 = await loadSkinIntoOsu(osu, new ImportTask(createOskWithIni("name 『1』", "author 1"), "custom.osk"));
|
||||
assertCorrectMetadata(import1, "name 『1』 [custom]", "author 1", osu);
|
||||
|
||||
import1.PerformRead(s =>
|
||||
{
|
||||
new LegacySkinExporter(osu.Dependencies.Get<Storage>()).ExportModelTo(s, exportStream);
|
||||
});
|
||||
await new LegacySkinExporter(osu.Dependencies.Get<Storage>()).ExportToStreamAsync(import1, exportStream);
|
||||
|
||||
string exportFilename = import1.GetDisplayString().GetValidFilename();
|
||||
|
||||
@ -208,7 +201,7 @@ namespace osu.Game.Tests.Skins.IO
|
||||
});
|
||||
|
||||
[Test]
|
||||
public Task TestExportThenImportDefaultSkin() => runSkinTest(osu =>
|
||||
public Task TestExportThenImportDefaultSkin() => runSkinTest(async osu =>
|
||||
{
|
||||
var skinManager = osu.Dependencies.Get<SkinManager>();
|
||||
|
||||
@ -218,30 +211,28 @@ namespace osu.Game.Tests.Skins.IO
|
||||
|
||||
Guid originalSkinId = skinManager.CurrentSkinInfo.Value.ID;
|
||||
|
||||
skinManager.CurrentSkinInfo.Value.PerformRead(s =>
|
||||
await skinManager.CurrentSkinInfo.Value.PerformRead(async s =>
|
||||
{
|
||||
Assert.IsFalse(s.Protected);
|
||||
Assert.AreEqual(typeof(ArgonSkin), s.CreateInstance(skinManager).GetType());
|
||||
|
||||
new LegacySkinExporter(osu.Dependencies.Get<Storage>()).ExportModelTo(s, exportStream);
|
||||
await new LegacySkinExporter(osu.Dependencies.Get<Storage>()).ExportToStreamAsync(skinManager.CurrentSkinInfo.Value, exportStream);
|
||||
|
||||
Assert.Greater(exportStream.Length, 0);
|
||||
});
|
||||
|
||||
var imported = skinManager.Import(new ImportTask(exportStream, "exported.osk"));
|
||||
var imported = await skinManager.Import(new ImportTask(exportStream, "exported.osk"));
|
||||
|
||||
imported.GetResultSafely().PerformRead(s =>
|
||||
imported.PerformRead(s =>
|
||||
{
|
||||
Assert.IsFalse(s.Protected);
|
||||
Assert.AreNotEqual(originalSkinId, s.ID);
|
||||
Assert.AreEqual(typeof(ArgonSkin), s.CreateInstance(skinManager).GetType());
|
||||
});
|
||||
|
||||
return Task.CompletedTask;
|
||||
});
|
||||
|
||||
[Test]
|
||||
public Task TestExportThenImportClassicSkin() => runSkinTest(osu =>
|
||||
public Task TestExportThenImportClassicSkin() => runSkinTest(async osu =>
|
||||
{
|
||||
var skinManager = osu.Dependencies.Get<SkinManager>();
|
||||
|
||||
@ -253,26 +244,24 @@ namespace osu.Game.Tests.Skins.IO
|
||||
|
||||
Guid originalSkinId = skinManager.CurrentSkinInfo.Value.ID;
|
||||
|
||||
skinManager.CurrentSkinInfo.Value.PerformRead(s =>
|
||||
await skinManager.CurrentSkinInfo.Value.PerformRead(async s =>
|
||||
{
|
||||
Assert.IsFalse(s.Protected);
|
||||
Assert.AreEqual(typeof(DefaultLegacySkin), s.CreateInstance(skinManager).GetType());
|
||||
|
||||
new LegacySkinExporter(osu.Dependencies.Get<Storage>()).ExportModelTo(s, exportStream);
|
||||
await new LegacySkinExporter(osu.Dependencies.Get<Storage>()).ExportToStreamAsync(skinManager.CurrentSkinInfo.Value, exportStream);
|
||||
|
||||
Assert.Greater(exportStream.Length, 0);
|
||||
});
|
||||
|
||||
var imported = skinManager.Import(new ImportTask(exportStream, "exported.osk"));
|
||||
var imported = await skinManager.Import(new ImportTask(exportStream, "exported.osk"));
|
||||
|
||||
imported.GetResultSafely().PerformRead(s =>
|
||||
imported.PerformRead(s =>
|
||||
{
|
||||
Assert.IsFalse(s.Protected);
|
||||
Assert.AreNotEqual(originalSkinId, s.ID);
|
||||
Assert.AreEqual(typeof(DefaultLegacySkin), s.CreateInstance(skinManager).GetType());
|
||||
});
|
||||
|
||||
return Task.CompletedTask;
|
||||
});
|
||||
|
||||
#endregion
|
||||
|
@ -53,6 +53,8 @@ namespace osu.Game.Tests.Testing
|
||||
{
|
||||
Dependencies.Get<ShaderManager>().GetRawData(@"sh_TestVertex.vs");
|
||||
Dependencies.Get<ShaderManager>().GetRawData(@"sh_TestFragment.fs");
|
||||
Dependencies.Get<ShaderManager>().Load(@"TestVertex", @"TestFragment");
|
||||
Dependencies.Get<ShaderManager>().Load(VertexShaderDescriptor.TEXTURE_2, @"TestFragment");
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -311,6 +311,7 @@ namespace osu.Game.Tests.Visual.Background
|
||||
public bool IsDrawable => true;
|
||||
public double StartTime => double.MinValue;
|
||||
public double EndTime => double.MaxValue;
|
||||
public double EndTimeForDisplay => double.MaxValue;
|
||||
|
||||
public Drawable CreateDrawable() => new DrawableTestStoryboardElement();
|
||||
}
|
||||
|
@ -42,7 +42,8 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Size = new Vector2(90, 90)
|
||||
Size = new Vector2(90, 90),
|
||||
Scale = new Vector2(3),
|
||||
}
|
||||
};
|
||||
});
|
||||
@ -64,17 +65,24 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
InputManager.MoveMouseTo(tickMarkerHead.ScreenSpaceDrawQuad.Centre);
|
||||
InputManager.PressButton(MouseButton.Left);
|
||||
});
|
||||
AddStep("move to 8 and release", () =>
|
||||
AddStep("move to 1", () => InputManager.MoveMouseTo(getPositionForDivisor(1)));
|
||||
AddStep("move to 16 and release", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(tickSliderBar.ScreenSpaceDrawQuad.Centre);
|
||||
InputManager.MoveMouseTo(getPositionForDivisor(16));
|
||||
InputManager.ReleaseButton(MouseButton.Left);
|
||||
});
|
||||
AddAssert("divisor is 8", () => bindableBeatDivisor.Value == 8);
|
||||
AddAssert("divisor is 16", () => bindableBeatDivisor.Value == 16);
|
||||
AddStep("hold marker", () => InputManager.PressButton(MouseButton.Left));
|
||||
AddStep("move to 16", () => InputManager.MoveMouseTo(getPositionForDivisor(16)));
|
||||
AddStep("move to ~10 and release", () =>
|
||||
AddStep("move to ~6 and release", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(getPositionForDivisor(6));
|
||||
InputManager.ReleaseButton(MouseButton.Left);
|
||||
});
|
||||
AddAssert("divisor clamped to 8", () => bindableBeatDivisor.Value == 8);
|
||||
AddStep("move to ~10 and click", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(getPositionForDivisor(10));
|
||||
InputManager.PressButton(MouseButton.Left);
|
||||
InputManager.ReleaseButton(MouseButton.Left);
|
||||
});
|
||||
AddAssert("divisor clamped to 8", () => bindableBeatDivisor.Value == 8);
|
||||
@ -82,12 +90,11 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
|
||||
private Vector2 getPositionForDivisor(int divisor)
|
||||
{
|
||||
float relativePosition = (float)Math.Clamp(divisor, 0, 16) / 16;
|
||||
var sliderDrawQuad = tickSliderBar.ScreenSpaceDrawQuad;
|
||||
return new Vector2(
|
||||
sliderDrawQuad.TopLeft.X + sliderDrawQuad.Width * relativePosition,
|
||||
sliderDrawQuad.Centre.Y
|
||||
);
|
||||
float localX = (1 - 1 / (float)divisor) * tickSliderBar.UsableWidth + tickSliderBar.RangePadding;
|
||||
return tickSliderBar.ToScreenSpace(new Vector2(
|
||||
localX,
|
||||
tickSliderBar.DrawHeight / 2
|
||||
));
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
@ -187,7 +187,7 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
|
||||
private class SnapProvider : IDistanceSnapProvider
|
||||
{
|
||||
public SnapResult FindSnappedPositionAndTime(Vector2 screenSpacePosition, SnapType snapType = SnapType.Grids) => new SnapResult(screenSpacePosition, 0);
|
||||
public SnapResult FindSnappedPositionAndTime(Vector2 screenSpacePosition, SnapType snapType = SnapType.AllGrids) => new SnapResult(screenSpacePosition, 0);
|
||||
|
||||
public Bindable<double> DistanceSpacingMultiplier { get; } = new BindableDouble(1);
|
||||
|
||||
|
@ -6,7 +6,6 @@
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
@ -95,10 +94,6 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
var path = slider.Path;
|
||||
return path.ControlPoints.Count == 2 && path.ControlPoints.SequenceEqual(addedObject.Path.ControlPoints);
|
||||
});
|
||||
|
||||
// see `HitObject.control_point_leniency`.
|
||||
AddAssert("sample control point has correct time", () => Precision.AlmostEquals(slider.SampleControlPoint.Time, slider.GetEndTime(), 1));
|
||||
AddAssert("difficulty control point has correct time", () => slider.DifficultyControlPoint.Time == slider.StartTime);
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
@ -1,57 +0,0 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||
using osu.Framework.Extensions.ObjectExtensions;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.Rulesets.Mania;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Screens.Edit;
|
||||
using osu.Game.Screens.Edit.GameplayTest;
|
||||
using osu.Game.Screens.Select;
|
||||
using osu.Game.Tests.Resources;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Editing
|
||||
{
|
||||
public partial class TestSceneEditorNavigation : OsuGameTestScene
|
||||
{
|
||||
[Test]
|
||||
public void TestEditorGameplayTestAlwaysUsesOriginalRuleset()
|
||||
{
|
||||
BeatmapSetInfo beatmapSet = null!;
|
||||
|
||||
AddStep("import test beatmap", () => Game.BeatmapManager.Import(TestResources.GetTestBeatmapForImport()).WaitSafely());
|
||||
AddStep("retrieve beatmap", () => beatmapSet = Game.BeatmapManager.QueryBeatmapSet(set => !set.Protected).AsNonNull().Value.Detach());
|
||||
|
||||
AddStep("present beatmap", () => Game.PresentBeatmap(beatmapSet));
|
||||
AddUntilStep("wait for song select",
|
||||
() => Game.Beatmap.Value.BeatmapSetInfo.Equals(beatmapSet)
|
||||
&& Game.ScreenStack.CurrentScreen is PlaySongSelect songSelect
|
||||
&& songSelect.IsLoaded);
|
||||
AddStep("switch ruleset", () => Game.Ruleset.Value = new ManiaRuleset().RulesetInfo);
|
||||
|
||||
AddStep("open editor", () => ((PlaySongSelect)Game.ScreenStack.CurrentScreen).Edit(beatmapSet.Beatmaps.First(beatmap => beatmap.Ruleset.OnlineID == 0)));
|
||||
AddUntilStep("wait for editor open", () => Game.ScreenStack.CurrentScreen is Editor editor && editor.ReadyForUse);
|
||||
AddStep("test gameplay", () => ((Editor)Game.ScreenStack.CurrentScreen).TestGameplay());
|
||||
|
||||
AddUntilStep("wait for player", () =>
|
||||
{
|
||||
// notifications may fire at almost any inopportune time and cause annoying test failures.
|
||||
// relentlessly attempt to dismiss any and all interfering overlays, which includes notifications.
|
||||
// this is theoretically not foolproof, but it's the best that can be done here.
|
||||
Game.CloseAllOverlays();
|
||||
return Game.ScreenStack.CurrentScreen is EditorPlayer editorPlayer && editorPlayer.IsLoaded;
|
||||
});
|
||||
|
||||
AddAssert("current ruleset is osu!", () => Game.Ruleset.Value.Equals(new OsuRuleset().RulesetInfo));
|
||||
|
||||
AddStep("exit to song select", () => Game.PerformFromScreen(_ => { }, typeof(PlaySongSelect).Yield()));
|
||||
AddUntilStep("wait for song select", () => Game.ScreenStack.CurrentScreen is PlaySongSelect);
|
||||
AddAssert("previous ruleset restored", () => Game.Ruleset.Value.Equals(new ManiaRuleset().RulesetInfo));
|
||||
}
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user