mirror of
https://github.com/ppy/osu.git
synced 2024-11-11 08:27:49 +08:00
Merge branch 'master' into scorev2
This commit is contained in:
commit
fee3d43596
14
.github/workflows/ci.yml
vendored
14
.github/workflows/ci.yml
vendored
@ -121,12 +121,24 @@ jobs:
|
|||||||
|
|
||||||
build-only-ios:
|
build-only-ios:
|
||||||
name: 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
|
timeout-minutes: 60
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v3
|
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
|
- name: Install .NET 6.0.x
|
||||||
uses: actions/setup-dotnet@v3
|
uses: actions/setup-dotnet@v3
|
||||||
with:
|
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.Sprites;
|
||||||
using osu.Framework.Graphics.Textures;
|
using osu.Framework.Graphics.Textures;
|
||||||
using osu.Game.Audio;
|
using osu.Game.Audio;
|
||||||
using osu.Game.Beatmaps.ControlPoints;
|
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
@ -44,7 +43,7 @@ namespace osu.Game.Rulesets.Pippidon.Objects.Drawables
|
|||||||
|
|
||||||
public override IEnumerable<HitSampleInfo> GetSamples() => new[]
|
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)
|
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.Sprites;
|
||||||
using osu.Framework.Graphics.Textures;
|
using osu.Framework.Graphics.Textures;
|
||||||
using osu.Game.Audio;
|
using osu.Game.Audio;
|
||||||
using osu.Game.Beatmaps.ControlPoints;
|
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.Pippidon.UI;
|
using osu.Game.Rulesets.Pippidon.UI;
|
||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
@ -44,7 +43,7 @@ namespace osu.Game.Rulesets.Pippidon.Objects.Drawables
|
|||||||
|
|
||||||
public override IEnumerable<HitSampleInfo> GetSamples() => new[]
|
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)
|
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>
|
<AndroidManifestMerger>manifestmerger.jar</AndroidManifestMerger>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2023.506.0" />
|
<PackageReference Include="ppy.osu.Framework.Android" Version="2023.521.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<AndroidManifestOverlay Include="$(MSBuildThisFileDirectory)osu.Android\Properties\AndroidManifestOverlay.xml" />
|
<AndroidManifestOverlay Include="$(MSBuildThisFileDirectory)osu.Android\Properties\AndroidManifestOverlay.xml" />
|
||||||
|
@ -9,6 +9,7 @@ using osu.Framework.Logging;
|
|||||||
using osu.Game;
|
using osu.Game;
|
||||||
using osu.Game.Overlays;
|
using osu.Game.Overlays;
|
||||||
using osu.Game.Overlays.Notifications;
|
using osu.Game.Overlays.Notifications;
|
||||||
|
using osu.Game.Screens.Play;
|
||||||
using Squirrel;
|
using Squirrel;
|
||||||
using Squirrel.SimpleSplat;
|
using Squirrel.SimpleSplat;
|
||||||
using LogLevel = Squirrel.SimpleSplat.LogLevel;
|
using LogLevel = Squirrel.SimpleSplat.LogLevel;
|
||||||
@ -36,6 +37,9 @@ namespace osu.Desktop.Updater
|
|||||||
[Resolved]
|
[Resolved]
|
||||||
private OsuGameBase game { get; set; } = null!;
|
private OsuGameBase game { get; set; } = null!;
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private ILocalUserPlayInfo? localUserInfo { get; set; }
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(INotificationOverlay notifications)
|
private void load(INotificationOverlay notifications)
|
||||||
{
|
{
|
||||||
@ -55,6 +59,10 @@ namespace osu.Desktop.Updater
|
|||||||
|
|
||||||
try
|
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");
|
updateManager ??= new GithubUpdateManager(@"https://github.com/ppy/osu", false, github_token, @"osulazer");
|
||||||
|
|
||||||
var info = await updateManager.CheckForUpdate(!useDeltaPatching).ConfigureAwait(false);
|
var info = await updateManager.CheckForUpdate(!useDeltaPatching).ConfigureAwait(false);
|
||||||
|
@ -45,7 +45,7 @@ namespace osu.Game.Rulesets.Catch.Tests
|
|||||||
NewCombo = i % 8 == 0,
|
NewCombo = i % 8 == 0,
|
||||||
Samples = new List<HitSampleInfo>(new[]
|
Samples = new List<HitSampleInfo>(new[]
|
||||||
{
|
{
|
||||||
new HitSampleInfo(HitSampleInfo.HIT_NORMAL, "normal", volume: 100)
|
new HitSampleInfo(HitSampleInfo.HIT_NORMAL)
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,8 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints
|
|||||||
private double placementStartTime;
|
private double placementStartTime;
|
||||||
private double placementEndTime;
|
private double placementEndTime;
|
||||||
|
|
||||||
|
protected override bool IsValidForPlacement => HitObject.Duration > 0;
|
||||||
|
|
||||||
public BananaShowerPlacementBlueprint()
|
public BananaShowerPlacementBlueprint()
|
||||||
{
|
{
|
||||||
InternalChild = outline = new TimeSpanOutline();
|
InternalChild = outline = new TimeSpanOutline();
|
||||||
@ -49,7 +51,7 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints
|
|||||||
case PlacementState.Active:
|
case PlacementState.Active:
|
||||||
if (e.Button != MouseButton.Right) break;
|
if (e.Button != MouseButton.Right) break;
|
||||||
|
|
||||||
EndPlacement(HitObject.Duration > 0);
|
EndPlacement(true);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -24,6 +24,8 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints
|
|||||||
|
|
||||||
private InputManager inputManager = null!;
|
private InputManager inputManager = null!;
|
||||||
|
|
||||||
|
protected override bool IsValidForPlacement => HitObject.Duration > 0;
|
||||||
|
|
||||||
public JuiceStreamPlacementBlueprint()
|
public JuiceStreamPlacementBlueprint()
|
||||||
{
|
{
|
||||||
InternalChildren = new Drawable[]
|
InternalChildren = new Drawable[]
|
||||||
@ -70,7 +72,7 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints
|
|||||||
return true;
|
return true;
|
||||||
|
|
||||||
case MouseButton.Right:
|
case MouseButton.Right:
|
||||||
EndPlacement(HitObject.Duration > 0);
|
EndPlacement(true);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -58,7 +58,7 @@ namespace osu.Game.Rulesets.Catch.Objects
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
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));
|
=> new BananaHitSampleInfo(newVolume.GetOr(Volume));
|
||||||
|
|
||||||
public bool Equals(BananaHitSampleInfo? other)
|
public bool Equals(BananaHitSampleInfo? other)
|
||||||
|
@ -41,7 +41,7 @@ namespace osu.Game.Rulesets.Catch.Objects
|
|||||||
{
|
{
|
||||||
StartTime = time,
|
StartTime = time,
|
||||||
BananaIndex = i,
|
BananaIndex = i,
|
||||||
Samples = new List<HitSampleInfo> { new Banana.BananaHitSampleInfo(GetSampleInfo().Volume) }
|
Samples = new List<HitSampleInfo> { new Banana.BananaHitSampleInfo(CreateHitSampleInfo().Volume) }
|
||||||
});
|
});
|
||||||
|
|
||||||
time += spacing;
|
time += spacing;
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
@ -40,7 +38,7 @@ namespace osu.Game.Rulesets.Mania.Tests
|
|||||||
private const double time_tail = 4000;
|
private const double time_tail = 4000;
|
||||||
private const double time_after_tail = 5250;
|
private const double time_after_tail = 5250;
|
||||||
|
|
||||||
private List<JudgementResult> judgementResults;
|
private List<JudgementResult> judgementResults = new List<JudgementResult>();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// -----[ ]-----
|
/// -----[ ]-----
|
||||||
@ -61,6 +59,44 @@ namespace osu.Game.Rulesets.Mania.Tests
|
|||||||
assertNoteJudgement(HitResult.IgnoreMiss);
|
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>
|
/// <summary>
|
||||||
/// -----[ ]-----
|
/// -----[ ]-----
|
||||||
/// x o
|
/// x o
|
||||||
@ -521,9 +557,9 @@ namespace osu.Game.Rulesets.Mania.Tests
|
|||||||
private void assertLastTickJudgement(HitResult result)
|
private void assertLastTickJudgement(HitResult result)
|
||||||
=> AddAssert($"last tick judged as {result}", () => judgementResults.Last(j => j.HitObject is HoldNoteTick).Type, () => Is.EqualTo(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)
|
if (beatmap == null)
|
||||||
{
|
{
|
||||||
@ -569,15 +605,13 @@ namespace osu.Game.Rulesets.Mania.Tests
|
|||||||
AddUntilStep("Beatmap at 0", () => Beatmap.Value.Track.CurrentTime == 0);
|
AddUntilStep("Beatmap at 0", () => Beatmap.Value.Track.CurrentTime == 0);
|
||||||
AddUntilStep("Wait until player is loaded", () => currentPlayer.IsCurrentScreen());
|
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
|
private partial class ScoreAccessibleReplayPlayer : ReplayPlayer
|
||||||
{
|
{
|
||||||
public new ScoreProcessor ScoreProcessor => base.ScoreProcessor;
|
public new ScoreProcessor ScoreProcessor => base.ScoreProcessor;
|
||||||
|
|
||||||
public new GameplayClockContainer GameplayClockContainer => base.GameplayClockContainer;
|
|
||||||
|
|
||||||
protected override bool PauseOnFocusLost => false;
|
protected override bool PauseOnFocusLost => false;
|
||||||
|
|
||||||
public ScoreAccessibleReplayPlayer(Score score)
|
public ScoreAccessibleReplayPlayer(Score score)
|
||||||
|
@ -25,6 +25,8 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
|
|||||||
[Resolved]
|
[Resolved]
|
||||||
private IScrollingInfo scrollingInfo { get; set; }
|
private IScrollingInfo scrollingInfo { get; set; }
|
||||||
|
|
||||||
|
protected override bool IsValidForPlacement => HitObject.Duration > 0;
|
||||||
|
|
||||||
public HoldNotePlacementBlueprint()
|
public HoldNotePlacementBlueprint()
|
||||||
: base(new HoldNote())
|
: base(new HoldNote())
|
||||||
{
|
{
|
||||||
@ -75,7 +77,7 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
base.OnMouseUp(e);
|
base.OnMouseUp(e);
|
||||||
EndPlacement(HitObject.Duration > 0);
|
EndPlacement(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private double originalStartTime;
|
private double originalStartTime;
|
||||||
|
@ -27,7 +27,7 @@ namespace osu.Game.Rulesets.Mania.Edit.Setup
|
|||||||
specialStyle = new LabelledSwitchButton
|
specialStyle = new LabelledSwitchButton
|
||||||
{
|
{
|
||||||
Label = "Use special (N+1) style",
|
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 }
|
Current = { Value = Beatmap.BeatmapInfo.SpecialStyle }
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -3,7 +3,6 @@
|
|||||||
|
|
||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
@ -219,6 +218,9 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
|||||||
if (Time.Current < releaseTime)
|
if (Time.Current < releaseTime)
|
||||||
releaseTime = null;
|
releaseTime = null;
|
||||||
|
|
||||||
|
if (Time.Current < HoldStartTime)
|
||||||
|
endHold();
|
||||||
|
|
||||||
// Pad the full size container so its contents (i.e. the masking container) reach under the tail.
|
// 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.
|
// 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
|
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.
|
// 2. The head note will move along with the new "head position" in the container.
|
||||||
if (Head.IsHit && releaseTime == null && DrawHeight > 0)
|
if (Head.IsHit && releaseTime == null && DrawHeight > 0)
|
||||||
{
|
{
|
||||||
// How far past the hit target this hold note is. Always a positive value.
|
// How far past the hit target this hold note is.
|
||||||
float yOffset = Math.Max(0, Direction.Value == ScrollingDirection.Up ? -Y : Y);
|
float yOffset = Direction.Value == ScrollingDirection.Up ? -Y : Y;
|
||||||
sizingContainer.Height = Math.Clamp(1 - yOffset / DrawHeight, 0, 1);
|
sizingContainer.Height = 1 - yOffset / DrawHeight;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -322,14 +324,14 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
|||||||
if (e.Action != Action.Value)
|
if (e.Action != Action.Value)
|
||||||
return;
|
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
|
// Make sure a hold was started
|
||||||
if (HoldStartTime == null)
|
if (HoldStartTime == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
// do not run any of this logic when rewinding, as it inverts order of presses/releases.
|
||||||
|
if (Time.Elapsed < 0)
|
||||||
|
return;
|
||||||
|
|
||||||
Tail.UpdateResult();
|
Tail.UpdateResult();
|
||||||
endHold();
|
endHold();
|
||||||
|
|
||||||
|
@ -74,7 +74,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
EndTime = Time.Current + delay + length,
|
EndTime = Time.Current + delay + length,
|
||||||
Samples = new List<HitSampleInfo>
|
Samples = new List<HitSampleInfo>
|
||||||
{
|
{
|
||||||
new HitSampleInfo("hitnormal")
|
new HitSampleInfo(HitSampleInfo.HIT_NORMAL)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -41,6 +41,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
[Resolved(CanBeNull = true)]
|
[Resolved(CanBeNull = true)]
|
||||||
private IDistanceSnapProvider snapProvider { get; set; }
|
private IDistanceSnapProvider snapProvider { get; set; }
|
||||||
|
|
||||||
|
protected override bool IsValidForPlacement => HitObject.Path.HasValidLength;
|
||||||
|
|
||||||
public SliderPlacementBlueprint()
|
public SliderPlacementBlueprint()
|
||||||
: base(new Slider())
|
: base(new Slider())
|
||||||
{
|
{
|
||||||
@ -150,7 +152,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
private void endCurve()
|
private void endCurve()
|
||||||
{
|
{
|
||||||
updateSlider();
|
updateSlider();
|
||||||
EndPlacement(HitObject.Path.HasValidLength);
|
EndPlacement(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Update()
|
protected override void Update()
|
||||||
|
@ -1,10 +1,7 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using JetBrains.Annotations;
|
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
@ -24,9 +21,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners
|
|||||||
|
|
||||||
private bool isPlacingEnd;
|
private bool isPlacingEnd;
|
||||||
|
|
||||||
[Resolved(CanBeNull = true)]
|
[Resolved]
|
||||||
[CanBeNull]
|
private IBeatSnapProvider? beatSnapProvider { get; set; }
|
||||||
private IBeatSnapProvider beatSnapProvider { get; set; }
|
|
||||||
|
|
||||||
public SpinnerPlacementBlueprint()
|
public SpinnerPlacementBlueprint()
|
||||||
: base(new Spinner { Position = OsuPlayfield.BASE_SIZE / 2 })
|
: base(new Spinner { Position = OsuPlayfield.BASE_SIZE / 2 })
|
||||||
|
@ -9,7 +9,6 @@ using osu.Framework.Localisation;
|
|||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Configuration;
|
using osu.Game.Configuration;
|
||||||
using osu.Game.Overlays.Settings;
|
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.Osu.Beatmaps;
|
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();
|
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)
|
public BindableFloat AngleSharpness { get; } = new BindableFloat(7)
|
||||||
{
|
{
|
||||||
MinValue = 1,
|
MinValue = 1,
|
||||||
|
@ -98,6 +98,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
ComboOffset = original.ComboOffset;
|
ComboOffset = original.ComboOffset;
|
||||||
LegacyLastTickOffset = original.LegacyLastTickOffset;
|
LegacyLastTickOffset = original.LegacyLastTickOffset;
|
||||||
TickDistanceMultiplier = original.TickDistanceMultiplier;
|
TickDistanceMultiplier = original.TickDistanceMultiplier;
|
||||||
|
SliderVelocity = original.SliderVelocity;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void CreateNestedHitObjects(CancellationToken cancellationToken)
|
protected override void CreateNestedHitObjects(CancellationToken cancellationToken)
|
||||||
|
@ -72,7 +72,7 @@ namespace osu.Game.Rulesets.Osu.Objects
|
|||||||
|
|
||||||
AddNested(i < SpinsRequired
|
AddNested(i < SpinsRequired
|
||||||
? new SpinnerTick { StartTime = startTime, SpinnerDuration = Duration }
|
? new SpinnerTick { StartTime = startTime, SpinnerDuration = Duration }
|
||||||
: new SpinnerBonusTick { StartTime = startTime, SpinnerDuration = Duration, Samples = new[] { GetSampleInfo("spinnerbonus") } });
|
: new SpinnerBonusTick { StartTime = startTime, SpinnerDuration = Duration, Samples = new[] { CreateHitSampleInfo("spinnerbonus") } });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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, "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, "soft");
|
||||||
|
checkSound(HitType.Rim, HitSampleInfo.HIT_CLAP, "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, "soft");
|
||||||
|
checkSound(HitType.Rim, HitSampleInfo.HIT_CLAP, "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, "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, "soft");
|
||||||
|
checkSound(HitType.Rim, HitSampleInfo.HIT_CLAP, "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, "soft");
|
||||||
|
checkSound(HitType.Rim, HitSampleInfo.HIT_CLAP, "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, "soft");
|
||||||
|
checkSound(HitType.Rim, HitSampleInfo.HIT_CLAP, "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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -25,6 +25,8 @@ namespace osu.Game.Rulesets.Taiko.Edit.Blueprints
|
|||||||
|
|
||||||
private readonly IHasDuration spanPlacementObject;
|
private readonly IHasDuration spanPlacementObject;
|
||||||
|
|
||||||
|
protected override bool IsValidForPlacement => spanPlacementObject.Duration > 0;
|
||||||
|
|
||||||
public TaikoSpanPlacementBlueprint(HitObject hitObject)
|
public TaikoSpanPlacementBlueprint(HitObject hitObject)
|
||||||
: base(hitObject)
|
: base(hitObject)
|
||||||
{
|
{
|
||||||
@ -73,7 +75,7 @@ namespace osu.Game.Rulesets.Taiko.Edit.Blueprints
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
base.OnMouseUp(e);
|
base.OnMouseUp(e);
|
||||||
EndPlacement(spanPlacementObject.Duration > 0);
|
EndPlacement(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void UpdateTimeAndPosition(SnapResult result)
|
public override void UpdateTimeAndPosition(SnapResult result)
|
||||||
|
@ -118,6 +118,9 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
|||||||
{
|
{
|
||||||
public override bool RemoveWhenNotAlive => false;
|
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
|
public abstract partial class DrawableTaikoHitObject<TObject> : DrawableTaikoHitObject
|
||||||
@ -157,9 +160,6 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
|||||||
Content.Add(MainPiece = CreateMainPiece());
|
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();
|
protected abstract SkinnableDrawable CreateMainPiece();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,11 +3,9 @@
|
|||||||
|
|
||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
||||||
using System.Linq;
|
|
||||||
using osu.Game.Rulesets.Objects.Types;
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Game.Audio;
|
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Beatmaps.ControlPoints;
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
using osu.Game.Beatmaps.Formats;
|
using osu.Game.Beatmaps.Formats;
|
||||||
@ -98,7 +96,7 @@ namespace osu.Game.Rulesets.Taiko.Objects
|
|||||||
TickSpacing = tickSpacing,
|
TickSpacing = tickSpacing,
|
||||||
StartTime = t,
|
StartTime = t,
|
||||||
IsStrong = IsStrong,
|
IsStrong = IsStrong,
|
||||||
Samples = Samples.Where(s => s.Name == HitSampleInfo.HIT_FINISH).ToList()
|
Samples = Samples
|
||||||
});
|
});
|
||||||
|
|
||||||
first = false;
|
first = false;
|
||||||
@ -109,7 +107,11 @@ namespace osu.Game.Rulesets.Taiko.Objects
|
|||||||
|
|
||||||
protected override HitWindows CreateHitWindows() => HitWindows.Empty;
|
protected override HitWindows CreateHitWindows() => HitWindows.Empty;
|
||||||
|
|
||||||
protected override StrongNestedHitObject CreateStrongNestedHit(double startTime) => new StrongNestedHit(this) { StartTime = startTime };
|
protected override StrongNestedHitObject CreateStrongNestedHit(double startTime) => new StrongNestedHit(this)
|
||||||
|
{
|
||||||
|
StartTime = startTime,
|
||||||
|
Samples = Samples
|
||||||
|
};
|
||||||
|
|
||||||
public class StrongNestedHit : StrongNestedHitObject
|
public class StrongNestedHit : StrongNestedHitObject
|
||||||
{
|
{
|
||||||
|
@ -33,7 +33,11 @@ namespace osu.Game.Rulesets.Taiko.Objects
|
|||||||
|
|
||||||
public override double MaximumJudgementOffset => HitWindow;
|
public override double MaximumJudgementOffset => HitWindow;
|
||||||
|
|
||||||
protected override StrongNestedHitObject CreateStrongNestedHit(double startTime) => new StrongNestedHit(this) { StartTime = startTime };
|
protected override StrongNestedHitObject CreateStrongNestedHit(double startTime) => new StrongNestedHit(this)
|
||||||
|
{
|
||||||
|
StartTime = startTime,
|
||||||
|
Samples = Samples
|
||||||
|
};
|
||||||
|
|
||||||
public class StrongNestedHit : StrongNestedHitObject
|
public class StrongNestedHit : StrongNestedHitObject
|
||||||
{
|
{
|
||||||
|
@ -63,7 +63,7 @@ namespace osu.Game.Rulesets.Taiko.Objects
|
|||||||
if (isRimType != rimSamples.Any())
|
if (isRimType != rimSamples.Any())
|
||||||
{
|
{
|
||||||
if (isRimType)
|
if (isRimType)
|
||||||
Samples.Add(new HitSampleInfo(HitSampleInfo.HIT_CLAP));
|
Samples.Add(CreateHitSampleInfo(HitSampleInfo.HIT_CLAP));
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
foreach (var sample in rimSamples)
|
foreach (var sample in rimSamples)
|
||||||
@ -72,7 +72,11 @@ namespace osu.Game.Rulesets.Taiko.Objects
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override StrongNestedHitObject CreateStrongNestedHit(double startTime) => new StrongNestedHit(this) { StartTime = startTime };
|
protected override StrongNestedHitObject CreateStrongNestedHit(double startTime) => new StrongNestedHit(this)
|
||||||
|
{
|
||||||
|
StartTime = startTime,
|
||||||
|
Samples = Samples
|
||||||
|
};
|
||||||
|
|
||||||
public class StrongNestedHit : StrongNestedHitObject
|
public class StrongNestedHit : StrongNestedHitObject
|
||||||
{
|
{
|
||||||
|
@ -33,7 +33,10 @@ namespace osu.Game.Rulesets.Taiko.Objects
|
|||||||
for (int i = 0; i < RequiredHits; i++)
|
for (int i = 0; i < RequiredHits; i++)
|
||||||
{
|
{
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
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 != strongSamples.Any())
|
||||||
{
|
{
|
||||||
if (IsStrongBindable.Value)
|
if (IsStrongBindable.Value)
|
||||||
Samples.Add(GetSampleInfo(HitSampleInfo.HIT_FINISH));
|
Samples.Add(CreateHitSampleInfo(HitSampleInfo.HIT_FINISH));
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
foreach (var sample in strongSamples)
|
foreach (var sample in strongSamples)
|
||||||
|
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:
|
@ -92,6 +92,20 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
hitObjectHasVelocity(1, 5);
|
hitObjectHasVelocity(1, 5);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestUndo()
|
||||||
|
{
|
||||||
|
clickDifficultyPiece(1);
|
||||||
|
velocityPopoverHasSingleValue(2);
|
||||||
|
|
||||||
|
setVelocityViaPopover(5);
|
||||||
|
hitObjectHasVelocity(1, 5);
|
||||||
|
dismissPopover();
|
||||||
|
|
||||||
|
AddStep("undo", () => Editor.Undo());
|
||||||
|
hitObjectHasVelocity(1, 2);
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestMultipleSelectionWithSameSliderVelocity()
|
public void TestMultipleSelectionWithSameSliderVelocity()
|
||||||
{
|
{
|
||||||
|
@ -24,7 +24,7 @@ using osuTK.Input;
|
|||||||
|
|
||||||
namespace osu.Game.Tests.Visual.Editing
|
namespace osu.Game.Tests.Visual.Editing
|
||||||
{
|
{
|
||||||
public partial class TestSceneHitObjectSamplePointAdjustments : EditorTestScene
|
public partial class TestSceneHitObjectSampleAdjustments : EditorTestScene
|
||||||
{
|
{
|
||||||
protected override Ruleset CreateEditorRuleset() => new OsuRuleset();
|
protected override Ruleset CreateEditorRuleset() => new OsuRuleset();
|
||||||
|
|
||||||
@ -42,7 +42,7 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
Position = (OsuPlayfield.BASE_SIZE - new Vector2(100, 0)) / 2,
|
Position = (OsuPlayfield.BASE_SIZE - new Vector2(100, 0)) / 2,
|
||||||
Samples = new List<HitSampleInfo>
|
Samples = new List<HitSampleInfo>
|
||||||
{
|
{
|
||||||
new HitSampleInfo(HitSampleInfo.HIT_NORMAL, "normal", volume: 80)
|
new HitSampleInfo(HitSampleInfo.HIT_NORMAL, volume: 80)
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -58,6 +58,26 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestAddSampleAddition()
|
||||||
|
{
|
||||||
|
AddStep("select both objects", () => EditorBeatmap.SelectedHitObjects.AddRange(EditorBeatmap.HitObjects));
|
||||||
|
|
||||||
|
AddStep("add clap addition", () => InputManager.Key(Key.R));
|
||||||
|
|
||||||
|
hitObjectHasSampleBank(0, "normal");
|
||||||
|
hitObjectHasSamples(0, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_CLAP);
|
||||||
|
hitObjectHasSampleBank(1, "soft");
|
||||||
|
hitObjectHasSamples(1, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_CLAP);
|
||||||
|
|
||||||
|
AddStep("remove clap addition", () => InputManager.Key(Key.R));
|
||||||
|
|
||||||
|
hitObjectHasSampleBank(0, "normal");
|
||||||
|
hitObjectHasSamples(0, HitSampleInfo.HIT_NORMAL);
|
||||||
|
hitObjectHasSampleBank(1, "soft");
|
||||||
|
hitObjectHasSamples(1, HitSampleInfo.HIT_NORMAL);
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestPopoverHasFocus()
|
public void TestPopoverHasFocus()
|
||||||
{
|
{
|
||||||
@ -89,6 +109,21 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
hitObjectHasSampleBank(1, "drum");
|
hitObjectHasSampleBank(1, "drum");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestUndo()
|
||||||
|
{
|
||||||
|
clickSamplePiece(1);
|
||||||
|
samplePopoverHasSingleBank("soft");
|
||||||
|
samplePopoverHasSingleVolume(60);
|
||||||
|
|
||||||
|
setVolumeViaPopover(90);
|
||||||
|
hitObjectHasSampleVolume(1, 90);
|
||||||
|
dismissPopover();
|
||||||
|
|
||||||
|
AddStep("undo", () => Editor.Undo());
|
||||||
|
hitObjectHasSampleVolume(1, 60);
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestMultipleSelectionWithSameSampleVolume()
|
public void TestMultipleSelectionWithSameSampleVolume()
|
||||||
{
|
{
|
||||||
@ -271,6 +306,12 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
InputManager.Key(Key.Enter);
|
InputManager.Key(Key.Enter);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
private void hitObjectHasSamples(int objectIndex, params string[] samples) => AddAssert($"{objectIndex.ToOrdinalWords()} has samples {string.Join(',', samples)}", () =>
|
||||||
|
{
|
||||||
|
var h = EditorBeatmap.HitObjects.ElementAt(objectIndex);
|
||||||
|
return h.Samples.Select(s => s.Name).SequenceEqual(samples);
|
||||||
|
});
|
||||||
|
|
||||||
private void hitObjectHasSampleBank(int objectIndex, string bank) => AddAssert($"{objectIndex.ToOrdinalWords()} has bank {bank}", () =>
|
private void hitObjectHasSampleBank(int objectIndex, string bank) => AddAssert($"{objectIndex.ToOrdinalWords()} has bank {bank}", () =>
|
||||||
{
|
{
|
||||||
var h = EditorBeatmap.HitObjects.ElementAt(objectIndex);
|
var h = EditorBeatmap.HitObjects.ElementAt(objectIndex);
|
106
osu.Game.Tests/Visual/Editing/TestScenePlacementBlueprint.cs
Normal file
106
osu.Game.Tests/Visual/Editing/TestScenePlacementBlueprint.cs
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
// 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.Screens;
|
||||||
|
using osu.Framework.Testing;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Input.Bindings;
|
||||||
|
using osu.Game.Rulesets;
|
||||||
|
using osu.Game.Rulesets.Edit;
|
||||||
|
using osu.Game.Rulesets.Osu;
|
||||||
|
using osu.Game.Rulesets.UI;
|
||||||
|
using osu.Game.Screens.Edit.Compose.Components;
|
||||||
|
using osu.Game.Tests.Beatmaps;
|
||||||
|
using osuTK.Input;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Visual.Editing
|
||||||
|
{
|
||||||
|
public partial class TestScenePlacementBlueprint : EditorTestScene
|
||||||
|
{
|
||||||
|
protected override Ruleset CreateEditorRuleset() => new OsuRuleset();
|
||||||
|
|
||||||
|
protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new TestBeatmap(ruleset, false);
|
||||||
|
|
||||||
|
private GlobalActionContainer globalActionContainer => this.ChildrenOfType<GlobalActionContainer>().Single();
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestCommitPlacementViaGlobalAction()
|
||||||
|
{
|
||||||
|
Playfield playfield = null!;
|
||||||
|
|
||||||
|
AddStep("select slider placement tool", () => InputManager.Key(Key.Number3));
|
||||||
|
AddStep("move mouse to top left of playfield", () =>
|
||||||
|
{
|
||||||
|
playfield = this.ChildrenOfType<Playfield>().Single();
|
||||||
|
var location = (3 * playfield.ScreenSpaceDrawQuad.TopLeft + playfield.ScreenSpaceDrawQuad.BottomRight) / 4;
|
||||||
|
InputManager.MoveMouseTo(location);
|
||||||
|
});
|
||||||
|
AddStep("begin placement", () => InputManager.Click(MouseButton.Left));
|
||||||
|
AddStep("move mouse to bottom right of playfield", () =>
|
||||||
|
{
|
||||||
|
var location = (playfield.ScreenSpaceDrawQuad.TopLeft + 3 * playfield.ScreenSpaceDrawQuad.BottomRight) / 4;
|
||||||
|
InputManager.MoveMouseTo(location);
|
||||||
|
});
|
||||||
|
AddStep("confirm via global action", () =>
|
||||||
|
{
|
||||||
|
globalActionContainer.TriggerPressed(GlobalAction.Select);
|
||||||
|
globalActionContainer.TriggerReleased(GlobalAction.Select);
|
||||||
|
});
|
||||||
|
AddAssert("slider placed", () => EditorBeatmap.HitObjects.Count, () => Is.EqualTo(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestAbortPlacementViaGlobalAction()
|
||||||
|
{
|
||||||
|
Playfield playfield = null!;
|
||||||
|
|
||||||
|
AddStep("select slider placement tool", () => InputManager.Key(Key.Number3));
|
||||||
|
AddStep("move mouse to top left of playfield", () =>
|
||||||
|
{
|
||||||
|
playfield = this.ChildrenOfType<Playfield>().Single();
|
||||||
|
var location = (3 * playfield.ScreenSpaceDrawQuad.TopLeft + playfield.ScreenSpaceDrawQuad.BottomRight) / 4;
|
||||||
|
InputManager.MoveMouseTo(location);
|
||||||
|
});
|
||||||
|
AddStep("begin placement", () => InputManager.Click(MouseButton.Left));
|
||||||
|
AddStep("move mouse to bottom right of playfield", () =>
|
||||||
|
{
|
||||||
|
var location = (playfield.ScreenSpaceDrawQuad.TopLeft + 3 * playfield.ScreenSpaceDrawQuad.BottomRight) / 4;
|
||||||
|
InputManager.MoveMouseTo(location);
|
||||||
|
});
|
||||||
|
AddStep("abort via global action", () =>
|
||||||
|
{
|
||||||
|
globalActionContainer.TriggerPressed(GlobalAction.Back);
|
||||||
|
globalActionContainer.TriggerReleased(GlobalAction.Back);
|
||||||
|
});
|
||||||
|
AddAssert("editor is still current", () => Editor.IsCurrentScreen());
|
||||||
|
AddAssert("slider not placed", () => EditorBeatmap.HitObjects.Count, () => Is.EqualTo(0));
|
||||||
|
AddAssert("no active placement", () => this.ChildrenOfType<ComposeBlueprintContainer>().Single().CurrentPlacement.PlacementActive,
|
||||||
|
() => Is.EqualTo(PlacementBlueprint.PlacementState.Waiting));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestCommitPlacementViaToolChange()
|
||||||
|
{
|
||||||
|
Playfield playfield = null!;
|
||||||
|
|
||||||
|
AddStep("select slider placement tool", () => InputManager.Key(Key.Number3));
|
||||||
|
AddStep("move mouse to top left of playfield", () =>
|
||||||
|
{
|
||||||
|
playfield = this.ChildrenOfType<Playfield>().Single();
|
||||||
|
var location = (3 * playfield.ScreenSpaceDrawQuad.TopLeft + playfield.ScreenSpaceDrawQuad.BottomRight) / 4;
|
||||||
|
InputManager.MoveMouseTo(location);
|
||||||
|
});
|
||||||
|
AddStep("begin placement", () => InputManager.Click(MouseButton.Left));
|
||||||
|
AddStep("move mouse to bottom right of playfield", () =>
|
||||||
|
{
|
||||||
|
var location = (playfield.ScreenSpaceDrawQuad.TopLeft + 3 * playfield.ScreenSpaceDrawQuad.BottomRight) / 4;
|
||||||
|
InputManager.MoveMouseTo(location);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("change tool to circle", () => InputManager.Key(Key.Number2));
|
||||||
|
AddAssert("slider placed", () => EditorBeatmap.HitObjects.Count, () => Is.EqualTo(1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,7 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
@ -8,6 +9,7 @@ using osu.Framework.Graphics;
|
|||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
using osu.Framework.Timing;
|
using osu.Framework.Timing;
|
||||||
|
using osu.Framework.Utils;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Beatmaps.Formats;
|
using osu.Game.Beatmaps.Formats;
|
||||||
using osu.Game.IO;
|
using osu.Game.IO;
|
||||||
@ -42,6 +44,18 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
AddStep("Load storyboard with missing video", () => loadStoryboard("storyboard_no_video.osu"));
|
AddStep("Load storyboard with missing video", () => loadStoryboard("storyboard_no_video.osu"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestVideoSize()
|
||||||
|
{
|
||||||
|
AddStep("load storyboard with only video", () =>
|
||||||
|
{
|
||||||
|
// LegacyStoryboardDecoder doesn't parse WidescreenStoryboard, so it is set manually
|
||||||
|
loadStoryboard("storyboard_only_video.osu", s => s.BeatmapInfo.WidescreenStoryboard = false);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddAssert("storyboard is correct width", () => Precision.AlmostEquals(storyboard?.Width ?? 0f, 480 * 16 / 9f));
|
||||||
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load()
|
||||||
{
|
{
|
||||||
@ -102,7 +116,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
decoupledClock.ChangeSource(Beatmap.Value.Track);
|
decoupledClock.ChangeSource(Beatmap.Value.Track);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void loadStoryboard(string filename)
|
private void loadStoryboard(string filename, Action<Storyboard>? setUpStoryboard = null)
|
||||||
{
|
{
|
||||||
Storyboard loaded;
|
Storyboard loaded;
|
||||||
|
|
||||||
@ -113,6 +127,8 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
loaded = decoder.Decode(bfr);
|
loaded = decoder.Decode(bfr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setUpStoryboard?.Invoke(loaded);
|
||||||
|
|
||||||
loadStoryboard(loaded);
|
loadStoryboard(loaded);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
@ -20,7 +18,7 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
{
|
{
|
||||||
private DummyAPIAccess dummyAPI => (DummyAPIAccess)API;
|
private DummyAPIAccess dummyAPI => (DummyAPIAccess)API;
|
||||||
|
|
||||||
private WikiOverlay wiki;
|
private WikiOverlay wiki = null!;
|
||||||
|
|
||||||
[SetUp]
|
[SetUp]
|
||||||
public void SetUp() => Schedule(() => Child = wiki = new WikiOverlay());
|
public void SetUp() => Schedule(() => Child = wiki = new WikiOverlay());
|
||||||
@ -29,13 +27,13 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
public void TestMainPage()
|
public void TestMainPage()
|
||||||
{
|
{
|
||||||
setUpWikiResponse(responseMainPage);
|
setUpWikiResponse(responseMainPage);
|
||||||
AddStep("Show main page", () => wiki.Show());
|
AddStep("Show main page", () => wiki.ShowPage());
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestCancellationDoesntShowError()
|
public void TestCancellationDoesntShowError()
|
||||||
{
|
{
|
||||||
AddStep("Show main page", () => wiki.Show());
|
AddStep("Show main page", () => wiki.ShowPage());
|
||||||
AddStep("Show another page", () => wiki.ShowPage("Article_styling_criteria/Formatting"));
|
AddStep("Show another page", () => wiki.ShowPage("Article_styling_criteria/Formatting"));
|
||||||
|
|
||||||
AddUntilStep("Current path is not error", () => wiki.CurrentPath != "error");
|
AddUntilStep("Current path is not error", () => wiki.CurrentPath != "error");
|
||||||
@ -73,7 +71,23 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
AddUntilStep("Error message correct", () => wiki.ChildrenOfType<SpriteText>().Any(text => text.Text == "\"This_page_will_error_out\"."));
|
AddUntilStep("Error message correct", () => wiki.ChildrenOfType<SpriteText>().Any(text => text.Text == "\"This_page_will_error_out\"."));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setUpWikiResponse(APIWikiPage r, string redirectionPath = null)
|
[Test]
|
||||||
|
public void TestReturnAfterErrorPage()
|
||||||
|
{
|
||||||
|
setUpWikiResponse(responseArticlePage);
|
||||||
|
|
||||||
|
AddStep("Show article page", () => wiki.ShowPage("Article_styling_criteria/Formatting"));
|
||||||
|
AddUntilStep("Wait for non-error page", () => wiki.CurrentPath == "Article_styling_criteria/Formatting");
|
||||||
|
|
||||||
|
AddStep("Show nonexistent page", () => wiki.ShowPage("This_page_will_error_out"));
|
||||||
|
AddUntilStep("Wait for error page", () => wiki.CurrentPath == "error");
|
||||||
|
|
||||||
|
AddStep("Show article page", () => wiki.ShowPage("Article_styling_criteria/Formatting"));
|
||||||
|
AddUntilStep("Wait for non-error page", () => wiki.CurrentPath == "Article_styling_criteria/Formatting");
|
||||||
|
AddUntilStep("Error message not displayed", () => wiki.ChildrenOfType<SpriteText>().All(text => text.Text != "\"This_page_will_error_out\"."));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setUpWikiResponse(APIWikiPage r, string? redirectionPath = null)
|
||||||
=> AddStep("set up response", () =>
|
=> AddStep("set up response", () =>
|
||||||
{
|
{
|
||||||
dummyAPI.HandleRequest = request =>
|
dummyAPI.HandleRequest = request =>
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
using osu.Game.Utils;
|
using osu.Game.Utils;
|
||||||
|
|
||||||
namespace osu.Game.Audio
|
namespace osu.Game.Audio
|
||||||
@ -32,7 +33,7 @@ namespace osu.Game.Audio
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// The bank to load the sample from.
|
/// The bank to load the sample from.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public readonly string? Bank;
|
public readonly string Bank;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// An optional suffix to provide priority lookup. Falls back to non-suffixed <see cref="Name"/>.
|
/// An optional suffix to provide priority lookup. Falls back to non-suffixed <see cref="Name"/>.
|
||||||
@ -44,7 +45,7 @@ namespace osu.Game.Audio
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public int Volume { get; }
|
public int Volume { get; }
|
||||||
|
|
||||||
public HitSampleInfo(string name, string? bank = null, string? suffix = null, int volume = 0)
|
public HitSampleInfo(string name, string bank = SampleControlPoint.DEFAULT_BANK, string? suffix = null, int volume = 100)
|
||||||
{
|
{
|
||||||
Name = name;
|
Name = name;
|
||||||
Bank = bank;
|
Bank = bank;
|
||||||
@ -75,7 +76,7 @@ namespace osu.Game.Audio
|
|||||||
/// <param name="newSuffix">An optional new lookup suffix.</param>
|
/// <param name="newSuffix">An optional new lookup suffix.</param>
|
||||||
/// <param name="newVolume">An optional new volume.</param>
|
/// <param name="newVolume">An optional new volume.</param>
|
||||||
/// <returns>The new <see cref="HitSampleInfo"/>.</returns>
|
/// <returns>The new <see cref="HitSampleInfo"/>.</returns>
|
||||||
public virtual HitSampleInfo With(Optional<string> newName = default, Optional<string?> newBank = default, Optional<string?> newSuffix = default, Optional<int> newVolume = default)
|
public virtual HitSampleInfo With(Optional<string> newName = default, Optional<string> newBank = default, Optional<string?> newSuffix = default, Optional<int> newVolume = default)
|
||||||
=> new HitSampleInfo(newName.GetOr(Name), newBank.GetOr(Bank), newSuffix.GetOr(Suffix), newVolume.GetOr(Volume));
|
=> new HitSampleInfo(newName.GetOr(Name), newBank.GetOr(Bank), newSuffix.GetOr(Suffix), newVolume.GetOr(Volume));
|
||||||
|
|
||||||
public bool Equals(HitSampleInfo? other)
|
public bool Equals(HitSampleInfo? other)
|
||||||
|
@ -107,9 +107,12 @@ namespace osu.Game.Beatmaps
|
|||||||
// Aggregate durations into a set of (beatLength, duration) tuples for each beat length
|
// Aggregate durations into a set of (beatLength, duration) tuples for each beat length
|
||||||
.GroupBy(t => Math.Round(t.beatLength * 1000) / 1000)
|
.GroupBy(t => Math.Round(t.beatLength * 1000) / 1000)
|
||||||
.Select(g => (beatLength: g.Key, duration: g.Sum(t => t.duration)))
|
.Select(g => (beatLength: g.Key, duration: g.Sum(t => t.duration)))
|
||||||
// Get the most common one, or 0 as a suitable default
|
// Get the most common one, or 0 as a suitable default (see handling below)
|
||||||
.OrderByDescending(i => i.duration).FirstOrDefault();
|
.OrderByDescending(i => i.duration).FirstOrDefault();
|
||||||
|
|
||||||
|
if (mostCommon.beatLength == 0)
|
||||||
|
return TimingControlPoint.DEFAULT_BEAT_LENGTH;
|
||||||
|
|
||||||
return mostCommon.beatLength;
|
return mostCommon.beatLength;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Game.Rulesets.Objects.Types;
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
|
|
||||||
@ -22,34 +20,17 @@ namespace osu.Game.Beatmaps
|
|||||||
|
|
||||||
public virtual void PreProcess()
|
public virtual void PreProcess()
|
||||||
{
|
{
|
||||||
IHasComboInformation lastObj = null;
|
IHasComboInformation? lastObj = null;
|
||||||
|
|
||||||
bool isFirst = true;
|
|
||||||
|
|
||||||
foreach (var obj in Beatmap.HitObjects.OfType<IHasComboInformation>())
|
foreach (var obj in Beatmap.HitObjects.OfType<IHasComboInformation>())
|
||||||
{
|
{
|
||||||
if (isFirst)
|
if (lastObj == null)
|
||||||
{
|
{
|
||||||
obj.NewCombo = true;
|
|
||||||
|
|
||||||
// first hitobject should always be marked as a new combo for sanity.
|
// first hitobject should always be marked as a new combo for sanity.
|
||||||
isFirst = false;
|
obj.NewCombo = true;
|
||||||
}
|
|
||||||
|
|
||||||
obj.ComboIndex = lastObj?.ComboIndex ?? 0;
|
|
||||||
obj.ComboIndexWithOffsets = lastObj?.ComboIndexWithOffsets ?? 0;
|
|
||||||
obj.IndexInCurrentCombo = (lastObj?.IndexInCurrentCombo + 1) ?? 0;
|
|
||||||
|
|
||||||
if (obj.NewCombo)
|
|
||||||
{
|
|
||||||
obj.IndexInCurrentCombo = 0;
|
|
||||||
obj.ComboIndex++;
|
|
||||||
obj.ComboIndexWithOffsets += obj.ComboOffset + 1;
|
|
||||||
|
|
||||||
if (lastObj != null)
|
|
||||||
lastObj.LastInCombo = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
obj.UpdateComboInformation(lastObj);
|
||||||
lastObj = obj;
|
lastObj = obj;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -69,7 +69,7 @@ namespace osu.Game.Beatmaps.ControlPoints
|
|||||||
/// <param name="hitSampleInfo">The <see cref="HitSampleInfo"/>. This will not be modified.</param>
|
/// <param name="hitSampleInfo">The <see cref="HitSampleInfo"/>. This will not be modified.</param>
|
||||||
/// <returns>The modified <see cref="HitSampleInfo"/>. This does not share a reference with <paramref name="hitSampleInfo"/>.</returns>
|
/// <returns>The modified <see cref="HitSampleInfo"/>. This does not share a reference with <paramref name="hitSampleInfo"/>.</returns>
|
||||||
public virtual HitSampleInfo ApplyTo(HitSampleInfo hitSampleInfo)
|
public virtual HitSampleInfo ApplyTo(HitSampleInfo hitSampleInfo)
|
||||||
=> hitSampleInfo.With(newBank: hitSampleInfo.Bank ?? SampleBank, newVolume: hitSampleInfo.Volume > 0 ? hitSampleInfo.Volume : SampleVolume);
|
=> hitSampleInfo.With(newBank: hitSampleInfo.Bank, newVolume: hitSampleInfo.Volume > 0 ? hitSampleInfo.Volume : SampleVolume);
|
||||||
|
|
||||||
public override bool IsRedundant(ControlPoint? existing)
|
public override bool IsRedundant(ControlPoint? existing)
|
||||||
=> existing is SampleControlPoint existingSample
|
=> existing is SampleControlPoint existingSample
|
||||||
|
@ -384,11 +384,11 @@ namespace osu.Game.Beatmaps.Formats
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case @"SliderMultiplier":
|
case @"SliderMultiplier":
|
||||||
difficulty.SliderMultiplier = Parsing.ParseDouble(pair.Value);
|
difficulty.SliderMultiplier = Math.Clamp(Parsing.ParseDouble(pair.Value), 0.4, 3.6);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case @"SliderTickRate":
|
case @"SliderTickRate":
|
||||||
difficulty.SliderTickRate = Parsing.ParseDouble(pair.Value);
|
difficulty.SliderTickRate = Math.Clamp(Parsing.ParseDouble(pair.Value), 0.5, 8);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -226,12 +226,16 @@ namespace osu.Game.Beatmaps.Formats
|
|||||||
|
|
||||||
public override HitSampleInfo ApplyTo(HitSampleInfo hitSampleInfo)
|
public override HitSampleInfo ApplyTo(HitSampleInfo hitSampleInfo)
|
||||||
{
|
{
|
||||||
var baseInfo = base.ApplyTo(hitSampleInfo);
|
if (hitSampleInfo is ConvertHitObjectParser.LegacyHitSampleInfo legacy)
|
||||||
|
{
|
||||||
|
return legacy.With(
|
||||||
|
newCustomSampleBank: legacy.CustomSampleBank > 0 ? legacy.CustomSampleBank : CustomSampleBank,
|
||||||
|
newVolume: hitSampleInfo.Volume > 0 ? hitSampleInfo.Volume : SampleVolume,
|
||||||
|
newBank: legacy.BankSpecified ? legacy.Bank : SampleBank
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (baseInfo is ConvertHitObjectParser.LegacyHitSampleInfo legacy && legacy.CustomSampleBank == 0)
|
return base.ApplyTo(hitSampleInfo);
|
||||||
return legacy.With(newCustomSampleBank: CustomSampleBank);
|
|
||||||
|
|
||||||
return baseInfo;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool IsRedundant(ControlPoint? existing)
|
public override bool IsRedundant(ControlPoint? existing)
|
||||||
|
@ -34,7 +34,8 @@ namespace osu.Game.Beatmaps
|
|||||||
float ApproachRate { get; }
|
float ApproachRate { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The slider multiplier of the associated beatmap.
|
/// The base slider velocity of the associated beatmap.
|
||||||
|
/// This was known as "SliderMultiplier" in the .osu format and stable editor.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
double SliderMultiplier { get; }
|
double SliderMultiplier { get; }
|
||||||
|
|
||||||
|
@ -77,10 +77,10 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
|
|
||||||
private void updateState()
|
private void updateState()
|
||||||
{
|
{
|
||||||
hoverClickSounds.Enabled.Value = !Item.Action.Disabled;
|
hoverClickSounds.Enabled.Value = IsActionable;
|
||||||
Alpha = Item.Action.Disabled ? 0.2f : 1;
|
Alpha = IsActionable ? 1 : 0.2f;
|
||||||
|
|
||||||
if (IsHovered && !Item.Action.Disabled)
|
if (IsHovered && IsActionable)
|
||||||
{
|
{
|
||||||
text.BoldText.FadeIn(transition_length, Easing.OutQuint);
|
text.BoldText.FadeIn(transition_length, Easing.OutQuint);
|
||||||
text.NormalText.FadeOut(transition_length, Easing.OutQuint);
|
text.NormalText.FadeOut(transition_length, Easing.OutQuint);
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
||||||
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Input;
|
using osu.Framework.Input;
|
||||||
using osuTK.Input;
|
using osuTK.Input;
|
||||||
|
|
||||||
@ -10,6 +11,10 @@ namespace osu.Game.Input
|
|||||||
{
|
{
|
||||||
public partial class OsuUserInputManager : UserInputManager
|
public partial class OsuUserInputManager : UserInputManager
|
||||||
{
|
{
|
||||||
|
protected override bool AllowRightClickFromLongTouch => !LocalUserPlaying.Value;
|
||||||
|
|
||||||
|
public readonly BindableBool LocalUserPlaying = new BindableBool();
|
||||||
|
|
||||||
internal OsuUserInputManager()
|
internal OsuUserInputManager()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
@ -42,8 +42,7 @@ namespace osu.Game.Localisation
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// "If enabled, an "Are you ready? 3, 2, 1, GO!" countdown will be inserted at the beginning of the beatmap, assuming there is enough time to do so."
|
/// "If enabled, an "Are you ready? 3, 2, 1, GO!" countdown will be inserted at the beginning of the beatmap, assuming there is enough time to do so."
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static LocalisableString CountdownDescription => new TranslatableString(getKey(@"countdown_description"),
|
public static LocalisableString CountdownDescription => new TranslatableString(getKey(@"countdown_description"), @"If enabled, an ""Are you ready? 3, 2, 1, GO!"" countdown will be inserted at the beginning of the beatmap, assuming there is enough time to do so.");
|
||||||
@"If enabled, an ""Are you ready? 3, 2, 1, GO!"" countdown will be inserted at the beginning of the beatmap, assuming there is enough time to do so.");
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// "Countdown speed"
|
/// "Countdown speed"
|
||||||
@ -53,8 +52,7 @@ namespace osu.Game.Localisation
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// "If the countdown sounds off-time, use this to make it appear one or more beats early."
|
/// "If the countdown sounds off-time, use this to make it appear one or more beats early."
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static LocalisableString CountdownOffsetDescription =>
|
public static LocalisableString CountdownOffsetDescription => new TranslatableString(getKey(@"countdown_offset_description"), @"If the countdown sounds off-time, use this to make it appear one or more beats early.");
|
||||||
new TranslatableString(getKey(@"countdown_offset_description"), @"If the countdown sounds off-time, use this to make it appear one or more beats early.");
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// "Countdown offset"
|
/// "Countdown offset"
|
||||||
@ -69,8 +67,7 @@ namespace osu.Game.Localisation
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// "Allows storyboards to use the full screen space, rather than be confined to a 4:3 area."
|
/// "Allows storyboards to use the full screen space, rather than be confined to a 4:3 area."
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static LocalisableString WidescreenSupportDescription =>
|
public static LocalisableString WidescreenSupportDescription => new TranslatableString(getKey(@"widescreen_support_description"), @"Allows storyboards to use the full screen space, rather than be confined to a 4:3 area.");
|
||||||
new TranslatableString(getKey(@"widescreen_support_description"), @"Allows storyboards to use the full screen space, rather than be confined to a 4:3 area.");
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// "Epilepsy warning"
|
/// "Epilepsy warning"
|
||||||
@ -80,8 +77,7 @@ namespace osu.Game.Localisation
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// "Recommended if the storyboard or video contain scenes with rapidly flashing colours."
|
/// "Recommended if the storyboard or video contain scenes with rapidly flashing colours."
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static LocalisableString EpilepsyWarningDescription =>
|
public static LocalisableString EpilepsyWarningDescription => new TranslatableString(getKey(@"epilepsy_warning_description"), @"Recommended if the storyboard or video contain scenes with rapidly flashing colours.");
|
||||||
new TranslatableString(getKey(@"epilepsy_warning_description"), @"Recommended if the storyboard or video contain scenes with rapidly flashing colours.");
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// "Letterbox during breaks"
|
/// "Letterbox during breaks"
|
||||||
@ -91,8 +87,7 @@ namespace osu.Game.Localisation
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// "Adds horizontal letterboxing to give a cinematic look during breaks."
|
/// "Adds horizontal letterboxing to give a cinematic look during breaks."
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static LocalisableString LetterboxDuringBreaksDescription =>
|
public static LocalisableString LetterboxDuringBreaksDescription => new TranslatableString(getKey(@"letterbox_during_breaks_description"), @"Adds horizontal letterboxing to give a cinematic look during breaks.");
|
||||||
new TranslatableString(getKey(@"letterbox_during_breaks_description"), @"Adds horizontal letterboxing to give a cinematic look during breaks.");
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// "Samples match playback rate"
|
/// "Samples match playback rate"
|
||||||
@ -102,8 +97,7 @@ namespace osu.Game.Localisation
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// "When enabled, all samples will speed up or slow down when rate-changing mods are enabled."
|
/// "When enabled, all samples will speed up or slow down when rate-changing mods are enabled."
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static LocalisableString SamplesMatchPlaybackRateDescription => new TranslatableString(getKey(@"samples_match_playback_rate_description"),
|
public static LocalisableString SamplesMatchPlaybackRateDescription => new TranslatableString(getKey(@"samples_match_playback_rate_description"), @"When enabled, all samples will speed up or slow down when rate-changing mods are enabled.");
|
||||||
@"When enabled, all samples will speed up or slow down when rate-changing mods are enabled.");
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// "The size of all hit objects"
|
/// "The size of all hit objects"
|
||||||
@ -123,8 +117,27 @@ namespace osu.Game.Localisation
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// "The harshness of hit windows and difficulty of special objects (ie. spinners)"
|
/// "The harshness of hit windows and difficulty of special objects (ie. spinners)"
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static LocalisableString OverallDifficultyDescription =>
|
public static LocalisableString OverallDifficultyDescription => new TranslatableString(getKey(@"overall_difficulty_description"), @"The harshness of hit windows and difficulty of special objects (ie. spinners)");
|
||||||
new TranslatableString(getKey(@"overall_difficulty_description"), @"The harshness of hit windows and difficulty of special objects (ie. spinners)");
|
|
||||||
|
/// <summary>
|
||||||
|
/// "Tick Rate"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString TickRate => new TranslatableString(getKey(@"tick_rate"), @"Tick Rate");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "Determines how many "ticks" are generated within long hit objects. A tick rate of 1 will generate ticks on each beat, 2 would be twice per beat, etc."
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString TickRateDescription => new TranslatableString(getKey(@"tick_rate_description"), @"Determines how many ""ticks"" are generated within long hit objects. A tick rate of 1 will generate ticks on each beat, 2 would be twice per beat, etc.");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "Base Velocity"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString BaseVelocity => new TranslatableString(getKey(@"base_velocity"), @"Base Velocity");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "The base velocity of the beatmap, affecting things like slider velocity and scroll speed in some rulesets."
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString BaseVelocityDescription => new TranslatableString(getKey(@"base_velocity_description"), @"The base velocity of the beatmap, affecting things like slider velocity and scroll speed in some rulesets.");
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// "Metadata"
|
/// "Metadata"
|
||||||
|
@ -12,6 +12,7 @@ using osu.Framework.Allocation;
|
|||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
using osu.Framework.Platform;
|
||||||
using osu.Game.Configuration;
|
using osu.Game.Configuration;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Online.API;
|
using osu.Game.Online.API;
|
||||||
@ -35,6 +36,9 @@ namespace osu.Game.Online.Chat
|
|||||||
[Resolved]
|
[Resolved]
|
||||||
private ChannelManager channelManager { get; set; }
|
private ChannelManager channelManager { get; set; }
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private GameHost host { get; set; }
|
||||||
|
|
||||||
private Bindable<bool> notifyOnUsername;
|
private Bindable<bool> notifyOnUsername;
|
||||||
private Bindable<bool> notifyOnPrivateMessage;
|
private Bindable<bool> notifyOnPrivateMessage;
|
||||||
|
|
||||||
@ -89,8 +93,8 @@ namespace osu.Game.Online.Chat
|
|||||||
if (channel == null)
|
if (channel == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// Only send notifications, if ChatOverlay and the target channel aren't visible.
|
// Only send notifications if ChatOverlay or the target channel aren't visible, or if the window is unfocused
|
||||||
if (chatOverlay.IsPresent && channelManager.CurrentChannel.Value == channel)
|
if (chatOverlay.IsPresent && channelManager.CurrentChannel.Value == channel && host.IsActive.Value)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
foreach (var message in messages.OrderByDescending(m => m.Id))
|
foreach (var message in messages.OrderByDescending(m => m.Id))
|
||||||
@ -99,6 +103,7 @@ namespace osu.Game.Online.Chat
|
|||||||
if (message.Id <= channel.LastReadId)
|
if (message.Id <= channel.LastReadId)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
// ignore notifications triggered by local user's own chat messages
|
||||||
if (message.Sender.Id == localUser.Value.Id)
|
if (message.Sender.Id == localUser.Value.Id)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
@ -269,6 +269,13 @@ namespace osu.Game
|
|||||||
if (hideToolbar) Toolbar.Hide();
|
if (hideToolbar) Toolbar.Hide();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override UserInputManager CreateUserInputManager()
|
||||||
|
{
|
||||||
|
var userInputManager = base.CreateUserInputManager();
|
||||||
|
(userInputManager as OsuUserInputManager)?.LocalUserPlaying.BindTo(LocalUserPlaying);
|
||||||
|
return userInputManager;
|
||||||
|
}
|
||||||
|
|
||||||
private DependencyContainer dependencies;
|
private DependencyContainer dependencies;
|
||||||
|
|
||||||
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) =>
|
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) =>
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
@ -13,7 +11,6 @@ using osu.Framework.Graphics.Containers;
|
|||||||
using osu.Framework.Graphics.Cursor;
|
using osu.Framework.Graphics.Cursor;
|
||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
using osu.Framework.Platform;
|
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Graphics.Containers;
|
using osu.Game.Graphics.Containers;
|
||||||
using osu.Game.Graphics.Sprites;
|
using osu.Game.Graphics.Sprites;
|
||||||
@ -27,8 +24,8 @@ namespace osu.Game.Overlays.News
|
|||||||
|
|
||||||
private readonly APINewsPost post;
|
private readonly APINewsPost post;
|
||||||
|
|
||||||
private Box background;
|
private Box background = null!;
|
||||||
private TextFlowContainer main;
|
private TextFlowContainer main = null!;
|
||||||
|
|
||||||
public NewsCard(APINewsPost post)
|
public NewsCard(APINewsPost post)
|
||||||
{
|
{
|
||||||
@ -41,12 +38,12 @@ namespace osu.Game.Overlays.News
|
|||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(OverlayColourProvider colourProvider, GameHost host)
|
private void load(OverlayColourProvider colourProvider, OsuGame? game)
|
||||||
{
|
{
|
||||||
if (post.Slug != null)
|
if (post.Slug != null)
|
||||||
{
|
{
|
||||||
TooltipText = "view in browser";
|
TooltipText = "view in browser";
|
||||||
Action = () => host.OpenUrlExternally("https://osu.ppy.sh/home/news/" + post.Slug);
|
Action = () => game?.OpenUrlExternally(@"/home/news/" + post.Slug);
|
||||||
}
|
}
|
||||||
|
|
||||||
AddRange(new Drawable[]
|
AddRange(new Drawable[]
|
||||||
|
@ -20,7 +20,7 @@ using System.Diagnostics;
|
|||||||
using osu.Framework.Audio;
|
using osu.Framework.Audio;
|
||||||
using osu.Framework.Audio.Sample;
|
using osu.Framework.Audio.Sample;
|
||||||
using osu.Framework.Extensions.LocalisationExtensions;
|
using osu.Framework.Extensions.LocalisationExtensions;
|
||||||
using osu.Framework.Platform;
|
using osu.Game.Online.Chat;
|
||||||
|
|
||||||
namespace osu.Game.Overlays.News.Sidebar
|
namespace osu.Game.Overlays.News.Sidebar
|
||||||
{
|
{
|
||||||
@ -59,7 +59,7 @@ namespace osu.Game.Overlays.News.Sidebar
|
|||||||
new PostsContainer
|
new PostsContainer
|
||||||
{
|
{
|
||||||
Expanded = { BindTarget = Expanded },
|
Expanded = { BindTarget = Expanded },
|
||||||
Children = posts.Select(p => new PostButton(p)).ToArray()
|
Children = posts.Select(p => new PostLink(p)).ToArray()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -123,35 +123,14 @@ namespace osu.Game.Overlays.News.Sidebar
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private partial class PostButton : OsuHoverContainer
|
private partial class PostLink : LinkFlowContainer
|
||||||
{
|
{
|
||||||
protected override IEnumerable<Drawable> EffectTargets => new[] { text };
|
public PostLink(APINewsPost post)
|
||||||
|
: base(t => t.Font = OsuFont.GetFont(size: 12))
|
||||||
private readonly TextFlowContainer text;
|
|
||||||
private readonly APINewsPost post;
|
|
||||||
|
|
||||||
public PostButton(APINewsPost post)
|
|
||||||
{
|
{
|
||||||
this.post = post;
|
|
||||||
|
|
||||||
RelativeSizeAxes = Axes.X;
|
RelativeSizeAxes = Axes.X;
|
||||||
AutoSizeAxes = Axes.Y;
|
AutoSizeAxes = Axes.Y;
|
||||||
Child = text = new TextFlowContainer(t => t.Font = OsuFont.GetFont(size: 12))
|
AddLink(post.Title, LinkAction.External, @"/home/news/" + post.Slug, "view in browser");
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.X,
|
|
||||||
AutoSizeAxes = Axes.Y,
|
|
||||||
Text = post.Title
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
|
||||||
private void load(OverlayColourProvider overlayColours, GameHost host)
|
|
||||||
{
|
|
||||||
IdleColour = overlayColours.Light2;
|
|
||||||
HoverColour = overlayColours.Light1;
|
|
||||||
|
|
||||||
TooltipText = "view in browser";
|
|
||||||
Action = () => host.OpenUrlExternally("https://osu.ppy.sh/home/news/" + post.Slug);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -77,6 +77,14 @@ namespace osu.Game.Overlays
|
|||||||
base.Content.Add(mainContent);
|
base.Content.Add(mainContent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
// Ensure the scroll-to-top button is displayed above the fixed header.
|
||||||
|
AddInternal(ScrollFlow.Button.CreateProxy());
|
||||||
|
}
|
||||||
|
|
||||||
protected override void UpdateAfterChildren()
|
protected override void UpdateAfterChildren()
|
||||||
{
|
{
|
||||||
base.UpdateAfterChildren();
|
base.UpdateAfterChildren();
|
||||||
|
@ -31,7 +31,7 @@ namespace osu.Game.Overlays
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private const int button_scroll_position = 200;
|
private const int button_scroll_position = 200;
|
||||||
|
|
||||||
protected ScrollBackButton Button;
|
public ScrollBackButton Button { get; private set; }
|
||||||
|
|
||||||
private readonly Bindable<float?> lastScrollTarget = new Bindable<float?>();
|
private readonly Bindable<float?> lastScrollTarget = new Bindable<float?>();
|
||||||
|
|
||||||
|
@ -34,7 +34,7 @@ namespace osu.Game.Overlays.Settings.Sections
|
|||||||
new SettingsButton
|
new SettingsButton
|
||||||
{
|
{
|
||||||
Text = GeneralSettingsStrings.RunSetupWizard,
|
Text = GeneralSettingsStrings.RunSetupWizard,
|
||||||
Keywords = new[] { @"first run", @"initial", @"getting started" },
|
Keywords = new[] { @"first run", @"initial", @"getting started", @"import", @"tutorial", @"recommended beatmaps" },
|
||||||
TooltipText = FirstRunSetupOverlayStrings.FirstRunSetupDescription,
|
TooltipText = FirstRunSetupOverlayStrings.FirstRunSetupDescription,
|
||||||
Action = () => firstRunSetupOverlay?.Show(),
|
Action = () => firstRunSetupOverlay?.Show(),
|
||||||
},
|
},
|
||||||
|
@ -193,17 +193,18 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
|
|||||||
|
|
||||||
currentDisplay.BindValueChanged(display => Schedule(() =>
|
currentDisplay.BindValueChanged(display => Schedule(() =>
|
||||||
{
|
{
|
||||||
resolutions.RemoveRange(1, resolutions.Count - 1);
|
if (display.NewValue == null)
|
||||||
|
|
||||||
if (display.NewValue != null)
|
|
||||||
{
|
{
|
||||||
resolutions.AddRange(display.NewValue.DisplayModes
|
resolutions.Clear();
|
||||||
.Where(m => m.Size.Width >= 800 && m.Size.Height >= 600)
|
return;
|
||||||
.OrderByDescending(m => Math.Max(m.Size.Height, m.Size.Width))
|
|
||||||
.Select(m => m.Size)
|
|
||||||
.Distinct());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
resolutions.ReplaceRange(1, resolutions.Count - 1, display.NewValue.DisplayModes
|
||||||
|
.Where(m => m.Size.Width >= 800 && m.Size.Height >= 600)
|
||||||
|
.OrderByDescending(m => Math.Max(m.Size.Height, m.Size.Width))
|
||||||
|
.Select(m => m.Size)
|
||||||
|
.Distinct());
|
||||||
|
|
||||||
updateDisplaySettingsVisibility();
|
updateDisplaySettingsVisibility();
|
||||||
}), true);
|
}), true);
|
||||||
|
|
||||||
@ -244,7 +245,8 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
|
|||||||
{
|
{
|
||||||
Scheduler.AddOnce(d =>
|
Scheduler.AddOnce(d =>
|
||||||
{
|
{
|
||||||
displayDropdown.Items = d;
|
if (!displayDropdown.Items.SequenceEqual(d, DisplayListComparer.DEFAULT))
|
||||||
|
displayDropdown.Items = d;
|
||||||
updateDisplaySettingsVisibility();
|
updateDisplaySettingsVisibility();
|
||||||
}, displays);
|
}, displays);
|
||||||
}
|
}
|
||||||
@ -376,5 +378,43 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Contrary to <see cref="Display.Equals(osu.Framework.Platform.Display?)"/>, this comparer disregards the value of <see cref="Display.Bounds"/>.
|
||||||
|
/// We want to just show a list of displays, and for the purposes of settings we don't care about their bounds when it comes to the list.
|
||||||
|
/// However, <see cref="IWindow.DisplaysChanged"/> fires even if only the resolution of the current display was changed
|
||||||
|
/// (because it causes the bounds of all displays to also change).
|
||||||
|
/// We're not interested in those changes, so compare only the rest that we actually care about.
|
||||||
|
/// This helps to avoid a bindable/event feedback loop, in which a resolution change
|
||||||
|
/// would trigger a display "change", which would in turn reset resolution again.
|
||||||
|
/// </summary>
|
||||||
|
private class DisplayListComparer : IEqualityComparer<Display>
|
||||||
|
{
|
||||||
|
public static readonly DisplayListComparer DEFAULT = new DisplayListComparer();
|
||||||
|
|
||||||
|
public bool Equals(Display? x, Display? y)
|
||||||
|
{
|
||||||
|
if (ReferenceEquals(x, y)) return true;
|
||||||
|
if (ReferenceEquals(x, null)) return false;
|
||||||
|
if (ReferenceEquals(y, null)) return false;
|
||||||
|
|
||||||
|
return x.Index == y.Index
|
||||||
|
&& x.Name == y.Name
|
||||||
|
&& x.DisplayModes.SequenceEqual(y.DisplayModes);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int GetHashCode(Display obj)
|
||||||
|
{
|
||||||
|
var hashCode = new HashCode();
|
||||||
|
|
||||||
|
hashCode.Add(obj.Index);
|
||||||
|
hashCode.Add(obj.Name);
|
||||||
|
hashCode.Add(obj.DisplayModes.Length);
|
||||||
|
foreach (var displayMode in obj.DisplayModes)
|
||||||
|
hashCode.Add(displayMode);
|
||||||
|
|
||||||
|
return hashCode.ToHashCode();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,8 @@
|
|||||||
|
|
||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using osu.Framework;
|
using osu.Framework;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
@ -23,6 +25,8 @@ namespace osu.Game.Overlays.Settings.Sections.Input
|
|||||||
{
|
{
|
||||||
public partial class TabletSettings : SettingsSubsection
|
public partial class TabletSettings : SettingsSubsection
|
||||||
{
|
{
|
||||||
|
public override IEnumerable<LocalisableString> FilterTerms => base.FilterTerms.Concat(new LocalisableString[] { "area" });
|
||||||
|
|
||||||
public TabletAreaSelection AreaSelection { get; private set; }
|
public TabletAreaSelection AreaSelection { get; private set; }
|
||||||
|
|
||||||
private readonly ITabletHandler tabletHandler;
|
private readonly ITabletHandler tabletHandler;
|
||||||
|
@ -249,12 +249,14 @@ namespace osu.Game.Overlays
|
|||||||
|
|
||||||
private partial class ProfileSectionsContainer : SectionsContainer<ProfileSection>
|
private partial class ProfileSectionsContainer : SectionsContainer<ProfileSection>
|
||||||
{
|
{
|
||||||
|
private OverlayScrollContainer scroll = null!;
|
||||||
|
|
||||||
public ProfileSectionsContainer()
|
public ProfileSectionsContainer()
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both;
|
RelativeSizeAxes = Axes.Both;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override UserTrackingScrollContainer CreateScrollContainer() => new OverlayScrollContainer();
|
protected override UserTrackingScrollContainer CreateScrollContainer() => scroll = new OverlayScrollContainer();
|
||||||
|
|
||||||
// Reverse child ID is required so expanding beatmap panels can appear above sections below them.
|
// Reverse child ID is required so expanding beatmap panels can appear above sections below them.
|
||||||
// This can also be done by setting Depth when adding new sections above if using ReverseChildID turns out to have any issues.
|
// This can also be done by setting Depth when adding new sections above if using ReverseChildID turns out to have any issues.
|
||||||
@ -267,6 +269,14 @@ namespace osu.Game.Overlays
|
|||||||
Padding = new MarginPadding { Horizontal = 10 },
|
Padding = new MarginPadding { Horizontal = 10 },
|
||||||
Margin = new MarginPadding { Bottom = 10 },
|
Margin = new MarginPadding { Bottom = 10 },
|
||||||
};
|
};
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
// Ensure the scroll-to-top button is displayed above the fixed header.
|
||||||
|
AddInternal(scroll.Button.CreateProxy());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -157,7 +157,9 @@ namespace osu.Game.Overlays
|
|||||||
|
|
||||||
private void onFail(string originalPath)
|
private void onFail(string originalPath)
|
||||||
{
|
{
|
||||||
|
wikiData.Value = null;
|
||||||
path.Value = "error";
|
path.Value = "error";
|
||||||
|
|
||||||
LoadDisplay(articlePage = new WikiArticlePage($@"{api.WebsiteRootUrl}/wiki/",
|
LoadDisplay(articlePage = new WikiArticlePage($@"{api.WebsiteRootUrl}/wiki/",
|
||||||
$"Something went wrong when trying to fetch page \"{originalPath}\".\n\n[Return to the main page](Main_Page)."));
|
$"Something went wrong when trying to fetch page \"{originalPath}\".\n\n[Return to the main page](Main_Page)."));
|
||||||
}
|
}
|
||||||
|
@ -1,19 +1,20 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Input.Bindings;
|
||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
using osu.Game.Audio;
|
using osu.Game.Audio;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Beatmaps.ControlPoints;
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
|
using osu.Game.Input.Bindings;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
using osu.Game.Screens.Edit;
|
using osu.Game.Screens.Edit;
|
||||||
using osu.Game.Screens.Edit.Compose;
|
using osu.Game.Screens.Edit.Compose;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
@ -24,7 +25,7 @@ namespace osu.Game.Rulesets.Edit
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// A blueprint which governs the creation of a new <see cref="HitObject"/> to actualisation.
|
/// A blueprint which governs the creation of a new <see cref="HitObject"/> to actualisation.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract partial class PlacementBlueprint : CompositeDrawable
|
public abstract partial class PlacementBlueprint : CompositeDrawable, IKeyBindingHandler<GlobalAction>
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether the <see cref="HitObject"/> is currently mid-placement, but has not necessarily finished being placed.
|
/// Whether the <see cref="HitObject"/> is currently mid-placement, but has not necessarily finished being placed.
|
||||||
@ -36,23 +37,34 @@ namespace osu.Game.Rulesets.Edit
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public readonly HitObject HitObject;
|
public readonly HitObject HitObject;
|
||||||
|
|
||||||
[Resolved(canBeNull: true)]
|
[Resolved]
|
||||||
protected EditorClock EditorClock { get; private set; }
|
protected EditorClock EditorClock { get; private set; } = null!;
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private EditorBeatmap beatmap { get; set; }
|
private EditorBeatmap beatmap { get; set; } = null!;
|
||||||
|
|
||||||
private Bindable<double> startTimeBindable;
|
private Bindable<double> startTimeBindable = null!;
|
||||||
|
|
||||||
|
private HitObject? getPreviousHitObject() => beatmap.HitObjects.TakeWhile(h => h.StartTime <= startTimeBindable.Value).LastOrDefault();
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private IPlacementHandler placementHandler { get; set; }
|
private IPlacementHandler placementHandler { get; set; } = null!;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether this blueprint is currently in a state that can be committed.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Override this with any preconditions that should be double-checked on committing.
|
||||||
|
/// If <c>false</c> is returned and a commit is attempted, the blueprint will be destroyed instead.
|
||||||
|
/// </remarks>
|
||||||
|
protected virtual bool IsValidForPlacement => true;
|
||||||
|
|
||||||
protected PlacementBlueprint(HitObject hitObject)
|
protected PlacementBlueprint(HitObject hitObject)
|
||||||
{
|
{
|
||||||
HitObject = hitObject;
|
HitObject = hitObject;
|
||||||
|
|
||||||
// adding the default hit sample should be the case regardless of the ruleset.
|
// adding the default hit sample should be the case regardless of the ruleset.
|
||||||
HitObject.Samples.Add(new HitSampleInfo(HitSampleInfo.HIT_NORMAL, SampleControlPoint.DEFAULT_BANK, volume: 100));
|
HitObject.Samples.Add(new HitSampleInfo(HitSampleInfo.HIT_NORMAL));
|
||||||
|
|
||||||
RelativeSizeAxes = Axes.Both;
|
RelativeSizeAxes = Axes.Both;
|
||||||
|
|
||||||
@ -75,7 +87,7 @@ namespace osu.Game.Rulesets.Edit
|
|||||||
protected void BeginPlacement(bool commitStart = false)
|
protected void BeginPlacement(bool commitStart = false)
|
||||||
{
|
{
|
||||||
// Take the hitnormal sample of the last hit object
|
// Take the hitnormal sample of the last hit object
|
||||||
var lastHitNormal = beatmap.HitObjects.LastOrDefault(h => h.GetEndTime() < HitObject.StartTime)?.Samples?.FirstOrDefault(o => o.Name == HitSampleInfo.HIT_NORMAL);
|
var lastHitNormal = getPreviousHitObject()?.Samples?.FirstOrDefault(o => o.Name == HitSampleInfo.HIT_NORMAL);
|
||||||
if (lastHitNormal != null)
|
if (lastHitNormal != null)
|
||||||
HitObject.Samples[0] = lastHitNormal;
|
HitObject.Samples[0] = lastHitNormal;
|
||||||
|
|
||||||
@ -88,7 +100,7 @@ namespace osu.Game.Rulesets.Edit
|
|||||||
/// Signals that the placement of <see cref="HitObject"/> has finished.
|
/// Signals that the placement of <see cref="HitObject"/> has finished.
|
||||||
/// This will destroy this <see cref="PlacementBlueprint"/>, and add the HitObject.StartTime to the <see cref="Beatmap"/>.
|
/// This will destroy this <see cref="PlacementBlueprint"/>, and add the HitObject.StartTime to the <see cref="Beatmap"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="commit">Whether the object should be committed.</param>
|
/// <param name="commit">Whether the object should be committed. Note that a commit may fail if <see cref="IsValidForPlacement"/> is <c>false</c>.</param>
|
||||||
public void EndPlacement(bool commit)
|
public void EndPlacement(bool commit)
|
||||||
{
|
{
|
||||||
switch (PlacementActive)
|
switch (PlacementActive)
|
||||||
@ -102,10 +114,34 @@ namespace osu.Game.Rulesets.Edit
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
placementHandler.EndPlacement(HitObject, commit);
|
placementHandler.EndPlacement(HitObject, IsValidForPlacement && commit);
|
||||||
PlacementActive = PlacementState.Finished;
|
PlacementActive = PlacementState.Finished;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool OnPressed(KeyBindingPressEvent<GlobalAction> e)
|
||||||
|
{
|
||||||
|
if (PlacementActive == PlacementState.Waiting)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
switch (e.Action)
|
||||||
|
{
|
||||||
|
case GlobalAction.Select:
|
||||||
|
EndPlacement(true);
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case GlobalAction.Back:
|
||||||
|
EndPlacement(false);
|
||||||
|
return true;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnReleased(KeyBindingReleaseEvent<GlobalAction> e)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Updates the time and position of this <see cref="PlacementBlueprint"/> based on the provided snap information.
|
/// Updates the time and position of this <see cref="PlacementBlueprint"/> based on the provided snap information.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -113,7 +149,12 @@ namespace osu.Game.Rulesets.Edit
|
|||||||
public virtual void UpdateTimeAndPosition(SnapResult result)
|
public virtual void UpdateTimeAndPosition(SnapResult result)
|
||||||
{
|
{
|
||||||
if (PlacementActive == PlacementState.Waiting)
|
if (PlacementActive == PlacementState.Waiting)
|
||||||
HitObject.StartTime = result.Time ?? EditorClock?.CurrentTime ?? Time.Current;
|
{
|
||||||
|
HitObject.StartTime = result.Time ?? EditorClock.CurrentTime;
|
||||||
|
|
||||||
|
if (HitObject is IHasComboInformation comboInformation)
|
||||||
|
comboInformation.UpdateComboInformation(getPreviousHitObject() as IHasComboInformation);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -30,7 +30,7 @@ using osuTK.Graphics;
|
|||||||
namespace osu.Game.Rulesets.Objects.Drawables
|
namespace osu.Game.Rulesets.Objects.Drawables
|
||||||
{
|
{
|
||||||
[Cached(typeof(DrawableHitObject))]
|
[Cached(typeof(DrawableHitObject))]
|
||||||
public abstract partial class DrawableHitObject : PoolableDrawableWithLifetime<HitObjectLifetimeEntry>
|
public abstract partial class DrawableHitObject : PoolableDrawableWithLifetime<HitObjectLifetimeEntry>, IAnimationTimeReference
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Invoked after this <see cref="DrawableHitObject"/>'s applied <see cref="HitObject"/> has had its defaults applied.
|
/// Invoked after this <see cref="DrawableHitObject"/>'s applied <see cref="HitObject"/> has had its defaults applied.
|
||||||
@ -425,11 +425,13 @@ namespace osu.Game.Rulesets.Objects.Drawables
|
|||||||
|
|
||||||
LifetimeEnd = double.MaxValue;
|
LifetimeEnd = double.MaxValue;
|
||||||
|
|
||||||
double transformTime = HitObject.StartTime - InitialLifetimeOffset;
|
|
||||||
|
|
||||||
clearExistingStateTransforms();
|
clearExistingStateTransforms();
|
||||||
|
|
||||||
using (BeginAbsoluteSequence(transformTime))
|
double initialTransformsTime = HitObject.StartTime - InitialLifetimeOffset;
|
||||||
|
|
||||||
|
AnimationStartTime.Value = initialTransformsTime;
|
||||||
|
|
||||||
|
using (BeginAbsoluteSequence(initialTransformsTime))
|
||||||
UpdateInitialTransforms();
|
UpdateInitialTransforms();
|
||||||
|
|
||||||
using (BeginAbsoluteSequence(StateUpdateTime))
|
using (BeginAbsoluteSequence(StateUpdateTime))
|
||||||
@ -721,6 +723,8 @@ namespace osu.Game.Rulesets.Objects.Drawables
|
|||||||
if (CurrentSkin != null)
|
if (CurrentSkin != null)
|
||||||
CurrentSkin.SourceChanged -= skinSourceChanged;
|
CurrentSkin.SourceChanged -= skinSourceChanged;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Bindable<double> AnimationStartTime { get; } = new BindableDouble();
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract partial class DrawableHitObject<TObject> : DrawableHitObject
|
public abstract partial class DrawableHitObject<TObject> : DrawableHitObject
|
||||||
|
@ -206,14 +206,20 @@ namespace osu.Game.Rulesets.Objects
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Create a SampleInfo based on the sample settings of the hit normal sample in <see cref="Samples"/>.
|
/// Create a <see cref="HitSampleInfo"/> based on the sample settings of the first <see cref="HitSampleInfo.HIT_NORMAL"/> sample in <see cref="Samples"/>.
|
||||||
|
/// If no sample is available, sane default settings will be used instead.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// In the case an existing sample exists, all settings apart from the sample name will be inherited. This includes volume, bank and suffix.
|
||||||
|
/// </remarks>
|
||||||
/// <param name="sampleName">The name of the sample.</param>
|
/// <param name="sampleName">The name of the sample.</param>
|
||||||
/// <returns>A populated <see cref="HitSampleInfo"/>.</returns>
|
/// <returns>A populated <see cref="HitSampleInfo"/>.</returns>
|
||||||
protected HitSampleInfo GetSampleInfo(string sampleName = HitSampleInfo.HIT_NORMAL)
|
public HitSampleInfo CreateHitSampleInfo(string sampleName = HitSampleInfo.HIT_NORMAL)
|
||||||
{
|
{
|
||||||
var hitnormalSample = Samples.FirstOrDefault(s => s.Name == HitSampleInfo.HIT_NORMAL);
|
if (Samples.FirstOrDefault(s => s.Name == HitSampleInfo.HIT_NORMAL) is HitSampleInfo existingSample)
|
||||||
return hitnormalSample == null ? new HitSampleInfo(sampleName) : hitnormalSample.With(newName: sampleName);
|
return existingSample.With(newName: sampleName);
|
||||||
|
|
||||||
|
return new HitSampleInfo(sampleName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -14,6 +14,7 @@ using System.Linq;
|
|||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
using osu.Framework.Extensions.EnumExtensions;
|
using osu.Framework.Extensions.EnumExtensions;
|
||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
using osu.Game.Beatmaps.Legacy;
|
using osu.Game.Beatmaps.Legacy;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
using osu.Game.Utils;
|
using osu.Game.Utils;
|
||||||
@ -446,9 +447,9 @@ namespace osu.Game.Rulesets.Objects.Legacy
|
|||||||
if (string.IsNullOrEmpty(bankInfo.Filename))
|
if (string.IsNullOrEmpty(bankInfo.Filename))
|
||||||
{
|
{
|
||||||
soundTypes.Add(new LegacyHitSampleInfo(HitSampleInfo.HIT_NORMAL, bankInfo.BankForNormal, bankInfo.Volume, bankInfo.CustomSampleBank,
|
soundTypes.Add(new LegacyHitSampleInfo(HitSampleInfo.HIT_NORMAL, bankInfo.BankForNormal, bankInfo.Volume, bankInfo.CustomSampleBank,
|
||||||
// if the sound type doesn't have the Normal flag set, attach it anyway as a layered sample.
|
// if the sound type doesn't have the Normal flag set, attach it anyway as a layered sample.
|
||||||
// None also counts as a normal non-layered sample: https://osu.ppy.sh/help/wiki/osu!_File_Formats/Osu_(file_format)#hitsounds
|
// None also counts as a normal non-layered sample: https://osu.ppy.sh/help/wiki/osu!_File_Formats/Osu_(file_format)#hitsounds
|
||||||
type != LegacyHitSoundType.None && !type.HasFlagFast(LegacyHitSoundType.Normal)));
|
type != LegacyHitSoundType.None && !type.HasFlagFast(LegacyHitSoundType.Normal)));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -479,12 +480,14 @@ namespace osu.Game.Rulesets.Objects.Legacy
|
|||||||
/// The bank identifier to use for the base ("hitnormal") sample.
|
/// The bank identifier to use for the base ("hitnormal") sample.
|
||||||
/// Transferred to <see cref="HitSampleInfo.Bank"/> when appropriate.
|
/// Transferred to <see cref="HitSampleInfo.Bank"/> when appropriate.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[CanBeNull]
|
||||||
public string BankForNormal;
|
public string BankForNormal;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The bank identifier to use for additions ("hitwhistle", "hitfinish", "hitclap").
|
/// The bank identifier to use for additions ("hitwhistle", "hitfinish", "hitclap").
|
||||||
/// Transferred to <see cref="HitSampleInfo.Bank"/> when appropriate.
|
/// Transferred to <see cref="HitSampleInfo.Bank"/> when appropriate.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[CanBeNull]
|
||||||
public string BankForAdditions;
|
public string BankForAdditions;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -518,17 +521,24 @@ namespace osu.Game.Rulesets.Objects.Legacy
|
|||||||
/// </remarks>
|
/// </remarks>
|
||||||
public readonly bool IsLayered;
|
public readonly bool IsLayered;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether a bank was specified locally to the relevant hitobject.
|
||||||
|
/// If <c>false</c>, a bank will be retrieved from the closest control point.
|
||||||
|
/// </summary>
|
||||||
|
public bool BankSpecified;
|
||||||
|
|
||||||
public LegacyHitSampleInfo(string name, string? bank = null, int volume = 0, int customSampleBank = 0, bool isLayered = false)
|
public LegacyHitSampleInfo(string name, string? bank = null, int volume = 0, int customSampleBank = 0, bool isLayered = false)
|
||||||
: base(name, bank, customSampleBank >= 2 ? customSampleBank.ToString() : null, volume)
|
: base(name, bank ?? SampleControlPoint.DEFAULT_BANK, customSampleBank >= 2 ? customSampleBank.ToString() : null, volume)
|
||||||
{
|
{
|
||||||
CustomSampleBank = customSampleBank;
|
CustomSampleBank = customSampleBank;
|
||||||
|
BankSpecified = !string.IsNullOrEmpty(bank);
|
||||||
IsLayered = isLayered;
|
IsLayered = isLayered;
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
||||||
=> With(newName, newBank, newVolume);
|
=> With(newName, newBank, newVolume);
|
||||||
|
|
||||||
public virtual LegacyHitSampleInfo With(Optional<string> newName = default, Optional<string?> newBank = default, Optional<int> newVolume = default,
|
public virtual LegacyHitSampleInfo With(Optional<string> newName = default, Optional<string> newBank = default, Optional<int> newVolume = default,
|
||||||
Optional<int> newCustomSampleBank = default,
|
Optional<int> newCustomSampleBank = default,
|
||||||
Optional<bool> newIsLayered = default)
|
Optional<bool> newIsLayered = default)
|
||||||
=> new LegacyHitSampleInfo(newName.GetOr(Name), newBank.GetOr(Bank), newVolume.GetOr(Volume), newCustomSampleBank.GetOr(CustomSampleBank), newIsLayered.GetOr(IsLayered));
|
=> new LegacyHitSampleInfo(newName.GetOr(Name), newBank.GetOr(Bank), newVolume.GetOr(Volume), newCustomSampleBank.GetOr(CustomSampleBank), newIsLayered.GetOr(IsLayered));
|
||||||
@ -563,7 +573,7 @@ namespace osu.Game.Rulesets.Objects.Legacy
|
|||||||
Path.ChangeExtension(Filename, null)
|
Path.ChangeExtension(Filename, null)
|
||||||
};
|
};
|
||||||
|
|
||||||
public sealed override LegacyHitSampleInfo With(Optional<string> newName = default, Optional<string?> newBank = default, Optional<int> newVolume = default,
|
public sealed override LegacyHitSampleInfo With(Optional<string> newName = default, Optional<string> newBank = default, Optional<int> newVolume = default,
|
||||||
Optional<int> newCustomSampleBank = default,
|
Optional<int> newCustomSampleBank = default,
|
||||||
Optional<bool> newIsLayered = default)
|
Optional<bool> newIsLayered = default)
|
||||||
=> new FileHitSampleInfo(Filename, newVolume.GetOr(Volume));
|
=> new FileHitSampleInfo(Filename, newVolume.GetOr(Volume));
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
@ -65,5 +63,26 @@ namespace osu.Game.Rulesets.Objects.Types
|
|||||||
{
|
{
|
||||||
return skin.GetConfig<SkinComboColourLookup, Color4>(new SkinComboColourLookup(comboIndex, combo))?.Value ?? Color4.White;
|
return skin.GetConfig<SkinComboColourLookup, Color4>(new SkinComboColourLookup(comboIndex, combo))?.Value ?? Color4.White;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Given the previous object in the beatmap, update relevant combo information.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="lastObj">The previous hitobject, or null if this is the first object in the beatmap.</param>
|
||||||
|
void UpdateComboInformation(IHasComboInformation? lastObj)
|
||||||
|
{
|
||||||
|
ComboIndex = lastObj?.ComboIndex ?? 0;
|
||||||
|
ComboIndexWithOffsets = lastObj?.ComboIndexWithOffsets ?? 0;
|
||||||
|
IndexInCurrentCombo = (lastObj?.IndexInCurrentCombo + 1) ?? 0;
|
||||||
|
|
||||||
|
if (NewCombo || lastObj == null)
|
||||||
|
{
|
||||||
|
IndexInCurrentCombo = 0;
|
||||||
|
ComboIndex++;
|
||||||
|
ComboIndexWithOffsets += ComboOffset + 1;
|
||||||
|
|
||||||
|
if (lastObj != null)
|
||||||
|
lastObj.LastInCombo = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -58,7 +58,7 @@ namespace osu.Game.Rulesets.UI
|
|||||||
PlaySamples(samples);
|
PlaySamples(samples);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void PlaySamples(ISampleInfo[] samples) => Schedule(() =>
|
protected virtual void PlaySamples(ISampleInfo[] samples) => Schedule(() =>
|
||||||
{
|
{
|
||||||
var hitSound = getNextSample();
|
var hitSound = getNextSample();
|
||||||
hitSound.Samples = samples;
|
hitSound.Samples = samples;
|
||||||
|
@ -28,6 +28,8 @@ namespace osu.Game.Rulesets.UI
|
|||||||
public abstract partial class RulesetInputManager<T> : PassThroughInputManager, ICanAttachHUDPieces, IHasReplayHandler, IHasRecordingHandler
|
public abstract partial class RulesetInputManager<T> : PassThroughInputManager, ICanAttachHUDPieces, IHasReplayHandler, IHasRecordingHandler
|
||||||
where T : struct
|
where T : struct
|
||||||
{
|
{
|
||||||
|
protected override bool AllowRightClickFromLongTouch => false;
|
||||||
|
|
||||||
public readonly KeyBindingContainer<T> KeyBindingContainer;
|
public readonly KeyBindingContainer<T> KeyBindingContainer;
|
||||||
|
|
||||||
[Resolved(CanBeNull = true)]
|
[Resolved(CanBeNull = true)]
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.UI.Scrolling.Algorithms
|
namespace osu.Game.Rulesets.UI.Scrolling.Algorithms
|
||||||
{
|
{
|
||||||
public interface IScrollAlgorithm
|
public interface IScrollAlgorithm
|
||||||
|
@ -185,7 +185,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
|
|
||||||
case TernaryState.True:
|
case TernaryState.True:
|
||||||
if (existingSample == null)
|
if (existingSample == null)
|
||||||
samples.Add(new HitSampleInfo(sampleName));
|
samples.Add(CurrentPlacement.HitObject.CreateHitSampleInfo(sampleName));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -317,12 +317,16 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void commitIfPlacementActive()
|
||||||
|
{
|
||||||
|
CurrentPlacement?.EndPlacement(CurrentPlacement.PlacementActive == PlacementBlueprint.PlacementState.Active);
|
||||||
|
removePlacement();
|
||||||
|
}
|
||||||
|
|
||||||
private void removePlacement()
|
private void removePlacement()
|
||||||
{
|
{
|
||||||
if (CurrentPlacement == null) return;
|
CurrentPlacement?.EndPlacement(false);
|
||||||
|
CurrentPlacement?.Expire();
|
||||||
CurrentPlacement.EndPlacement(false);
|
|
||||||
CurrentPlacement.Expire();
|
|
||||||
CurrentPlacement = null;
|
CurrentPlacement = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -342,7 +346,8 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
|
|
||||||
currentTool = value;
|
currentTool = value;
|
||||||
|
|
||||||
refreshTool();
|
// As per stable editor, when changing tools, we should forcefully commit any pending placement.
|
||||||
|
commitIfPlacementActive();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -122,7 +122,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
if (h.Samples.Any(s => s.Name == sampleName))
|
if (h.Samples.Any(s => s.Name == sampleName))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
h.Samples.Add(new HitSampleInfo(sampleName));
|
h.Samples.Add(h.CreateHitSampleInfo(sampleName));
|
||||||
EditorBeatmap.Update(h);
|
EditorBeatmap.Update(h);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -73,7 +73,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
if (selected is IHasSliderVelocity sliderVelocity)
|
if (selected is IHasSliderVelocity sliderVelocity)
|
||||||
{
|
{
|
||||||
AddHeader("Slider Velocity");
|
AddHeader("Slider Velocity");
|
||||||
AddValue($"{sliderVelocity.SliderVelocity:#,0.00}x");
|
AddValue($"{sliderVelocity.SliderVelocity:#,0.00}x ({sliderVelocity.SliderVelocity * EditorBeatmap.Difficulty.SliderMultiplier:#,0.00}x)");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (selected is IHasRepeats repeats)
|
if (selected is IHasRepeats repeats)
|
||||||
|
@ -96,7 +96,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
Text = "Hold shift while dragging the end of an object to adjust velocity while snapping."
|
Text = "Hold shift while dragging the end of an object to adjust velocity while snapping."
|
||||||
},
|
},
|
||||||
new SliderVelocityInspector(),
|
new SliderVelocityInspector(sliderVelocitySlider.Current),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -145,34 +145,48 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
|
|
||||||
internal partial class SliderVelocityInspector : EditorInspector
|
internal partial class SliderVelocityInspector : EditorInspector
|
||||||
{
|
{
|
||||||
|
private readonly Bindable<double?> current;
|
||||||
|
|
||||||
|
public SliderVelocityInspector(Bindable<double?> current)
|
||||||
|
{
|
||||||
|
this.current = current;
|
||||||
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load()
|
||||||
{
|
{
|
||||||
EditorBeatmap.TransactionBegan += updateInspectorText;
|
EditorBeatmap.TransactionBegan += updateInspectorText;
|
||||||
EditorBeatmap.TransactionEnded += updateInspectorText;
|
EditorBeatmap.TransactionEnded += updateInspectorText;
|
||||||
|
EditorBeatmap.BeatmapReprocessed += updateInspectorText;
|
||||||
|
current.ValueChanged += _ => updateInspectorText();
|
||||||
|
|
||||||
updateInspectorText();
|
updateInspectorText();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateInspectorText()
|
private void updateInspectorText()
|
||||||
{
|
{
|
||||||
|
double beatmapVelocity = EditorBeatmap.Difficulty.SliderMultiplier;
|
||||||
|
|
||||||
InspectorText.Clear();
|
InspectorText.Clear();
|
||||||
|
|
||||||
double[] sliderVelocities = EditorBeatmap.HitObjects.OfType<IHasSliderVelocity>().Select(sv => sv.SliderVelocity).OrderBy(v => v).ToArray();
|
double[] sliderVelocities = EditorBeatmap.HitObjects.OfType<IHasSliderVelocity>().Select(sv => sv.SliderVelocity).OrderBy(v => v).ToArray();
|
||||||
|
|
||||||
if (sliderVelocities.Length < 2)
|
AddHeader("Base velocity (from beatmap setup)");
|
||||||
return;
|
AddValue($"{beatmapVelocity:#,0.00}x");
|
||||||
|
|
||||||
double? modeSliderVelocity = sliderVelocities.GroupBy(v => v).MaxBy(v => v.Count())?.Key;
|
AddHeader("Final velocity");
|
||||||
double? medianSliderVelocity = sliderVelocities[sliderVelocities.Length / 2];
|
AddValue($"{beatmapVelocity * current.Value:#,0.00}x");
|
||||||
|
|
||||||
AddHeader("Average velocity");
|
if (sliderVelocities.First() != sliderVelocities.Last())
|
||||||
AddValue($"{medianSliderVelocity:#,0.00}x");
|
{
|
||||||
|
AddHeader("Beatmap velocity range");
|
||||||
|
|
||||||
AddHeader("Most used velocity");
|
string range = $"{sliderVelocities.First():#,0.00}x - {sliderVelocities.Last():#,0.00}x";
|
||||||
AddValue($"{modeSliderVelocity:#,0.00}x");
|
if (beatmapVelocity != 1)
|
||||||
|
range += $" ({beatmapVelocity * sliderVelocities.First():#,0.00}x - {beatmapVelocity * sliderVelocities.Last():#,0.00}x)";
|
||||||
|
|
||||||
AddHeader("Velocity range");
|
AddValue(range);
|
||||||
AddValue($"{sliderVelocities.First():#,0.00}x - {sliderVelocities.Last():#,0.00}x");
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Dispose(bool isDisposing)
|
protected override void Dispose(bool isDisposing)
|
||||||
@ -181,6 +195,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
|
|
||||||
EditorBeatmap.TransactionBegan -= updateInspectorText;
|
EditorBeatmap.TransactionBegan -= updateInspectorText;
|
||||||
EditorBeatmap.TransactionEnded -= updateInspectorText;
|
EditorBeatmap.TransactionEnded -= updateInspectorText;
|
||||||
|
EditorBeatmap.BeatmapReprocessed -= updateInspectorText;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -83,8 +83,9 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
{
|
{
|
||||||
placementBlueprint = CreateBlueprintFor(obj.NewValue).AsNonNull();
|
placementBlueprint = CreateBlueprintFor(obj.NewValue).AsNonNull();
|
||||||
|
|
||||||
placementBlueprint.Colour = Color4.MediumPurple;
|
placementBlueprint.Colour = OsuColour.Gray(0.9f);
|
||||||
|
|
||||||
|
// TODO: this is out of order, causing incorrect stacking height.
|
||||||
SelectionBlueprints.Add(placementBlueprint);
|
SelectionBlueprints.Add(placementBlueprint);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
@ -15,7 +16,9 @@ using osu.Framework.Graphics.Textures;
|
|||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Beatmaps.ControlPoints;
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
using osu.Game.Beatmaps.Formats;
|
using osu.Game.Beatmaps.Formats;
|
||||||
|
using osu.Game.Extensions;
|
||||||
using osu.Game.IO;
|
using osu.Game.IO;
|
||||||
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
using Decoder = osu.Game.Beatmaps.Formats.Decoder;
|
using Decoder = osu.Game.Beatmaps.Formats.Decoder;
|
||||||
|
|
||||||
@ -42,6 +45,7 @@ namespace osu.Game.Screens.Edit
|
|||||||
editorBeatmap.BeginChange();
|
editorBeatmap.BeginChange();
|
||||||
processHitObjects(result, () => newBeatmap ??= readBeatmap(newState));
|
processHitObjects(result, () => newBeatmap ??= readBeatmap(newState));
|
||||||
processTimingPoints(() => newBeatmap ??= readBeatmap(newState));
|
processTimingPoints(() => newBeatmap ??= readBeatmap(newState));
|
||||||
|
processHitObjectLocalData(() => newBeatmap ??= readBeatmap(newState));
|
||||||
editorBeatmap.EndChange();
|
editorBeatmap.EndChange();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -87,6 +91,41 @@ namespace osu.Game.Screens.Edit
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void processHitObjectLocalData(Func<IBeatmap> getNewBeatmap)
|
||||||
|
{
|
||||||
|
// This method handles data that are stored in control points in the legacy format,
|
||||||
|
// but were moved to the hitobjects themselves in lazer.
|
||||||
|
// Specifically, the data being referred to here consists of: slider velocity and sample information.
|
||||||
|
|
||||||
|
// For simplicity, this implementation relies on the editor beatmap already having the same hitobjects in sequence as the new beatmap.
|
||||||
|
// To guarantee that, `processHitObjects()` must be ran prior to this method for correct operation.
|
||||||
|
// This is done to avoid the necessity of reimplementing/reusing parts of LegacyBeatmapDecoder that already treat this data correctly.
|
||||||
|
|
||||||
|
var oldObjects = editorBeatmap.HitObjects;
|
||||||
|
var newObjects = getNewBeatmap().HitObjects;
|
||||||
|
|
||||||
|
Debug.Assert(oldObjects.Count == newObjects.Count);
|
||||||
|
|
||||||
|
foreach (var (oldObject, newObject) in oldObjects.Zip(newObjects))
|
||||||
|
{
|
||||||
|
// if `oldObject` and `newObject` are the same, it means that `oldObject` was inserted into `editorBeatmap` by `processHitObjects()`.
|
||||||
|
// in that case, there is nothing to do (and some of the subsequent changes may even prove destructive).
|
||||||
|
if (ReferenceEquals(oldObject, newObject))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (oldObject is IHasSliderVelocity oldWithVelocity && newObject is IHasSliderVelocity newWithVelocity)
|
||||||
|
oldWithVelocity.SliderVelocity = newWithVelocity.SliderVelocity;
|
||||||
|
|
||||||
|
oldObject.Samples = newObject.Samples;
|
||||||
|
|
||||||
|
if (oldObject is IHasRepeats oldWithRepeats && newObject is IHasRepeats newWithRepeats)
|
||||||
|
{
|
||||||
|
oldWithRepeats.NodeSamples.Clear();
|
||||||
|
oldWithRepeats.NodeSamples.AddRange(newWithRepeats.NodeSamples);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void findChangedIndices(DiffResult result, LegacyDecoder<Beatmap>.Section section, out List<int> removedIndices, out List<int> addedIndices)
|
private void findChangedIndices(DiffResult result, LegacyDecoder<Beatmap>.Section section, out List<int> removedIndices, out List<int> addedIndices)
|
||||||
{
|
{
|
||||||
removedIndices = new List<int>();
|
removedIndices = new List<int>();
|
||||||
|
@ -19,6 +19,8 @@ namespace osu.Game.Screens.Edit.Setup
|
|||||||
private LabelledSliderBar<float> healthDrainSlider = null!;
|
private LabelledSliderBar<float> healthDrainSlider = null!;
|
||||||
private LabelledSliderBar<float> approachRateSlider = null!;
|
private LabelledSliderBar<float> approachRateSlider = null!;
|
||||||
private LabelledSliderBar<float> overallDifficultySlider = null!;
|
private LabelledSliderBar<float> overallDifficultySlider = null!;
|
||||||
|
private LabelledSliderBar<double> baseVelocitySlider = null!;
|
||||||
|
private LabelledSliderBar<double> tickRateSlider = null!;
|
||||||
|
|
||||||
public override LocalisableString Title => EditorSetupStrings.DifficultyHeader;
|
public override LocalisableString Title => EditorSetupStrings.DifficultyHeader;
|
||||||
|
|
||||||
@ -79,13 +81,42 @@ namespace osu.Game.Screens.Edit.Setup
|
|||||||
Precision = 0.1f,
|
Precision = 0.1f,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
baseVelocitySlider = new LabelledSliderBar<double>
|
||||||
|
{
|
||||||
|
Label = EditorSetupStrings.BaseVelocity,
|
||||||
|
FixedLabelWidth = LABEL_WIDTH,
|
||||||
|
Description = EditorSetupStrings.BaseVelocityDescription,
|
||||||
|
Current = new BindableDouble(Beatmap.Difficulty.SliderMultiplier)
|
||||||
|
{
|
||||||
|
Default = 1,
|
||||||
|
MinValue = 0.4,
|
||||||
|
MaxValue = 3.6,
|
||||||
|
Precision = 0.01f,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
tickRateSlider = new LabelledSliderBar<double>
|
||||||
|
{
|
||||||
|
Label = EditorSetupStrings.TickRate,
|
||||||
|
FixedLabelWidth = LABEL_WIDTH,
|
||||||
|
Description = EditorSetupStrings.TickRateDescription,
|
||||||
|
Current = new BindableDouble(Beatmap.Difficulty.SliderTickRate)
|
||||||
|
{
|
||||||
|
Default = 1,
|
||||||
|
MinValue = 1,
|
||||||
|
MaxValue = 4,
|
||||||
|
Precision = 1,
|
||||||
|
}
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
foreach (var item in Children.OfType<LabelledSliderBar<float>>())
|
foreach (var item in Children.OfType<LabelledSliderBar<float>>())
|
||||||
item.Current.ValueChanged += onValueChanged;
|
item.Current.ValueChanged += _ => updateValues();
|
||||||
|
|
||||||
|
foreach (var item in Children.OfType<LabelledSliderBar<double>>())
|
||||||
|
item.Current.ValueChanged += _ => updateValues();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onValueChanged(ValueChangedEvent<float> args)
|
private void updateValues()
|
||||||
{
|
{
|
||||||
// for now, update these on commit rather than making BeatmapMetadata bindables.
|
// for now, update these on commit rather than making BeatmapMetadata bindables.
|
||||||
// after switching database engines we can reconsider if switching to bindables is a good direction.
|
// after switching database engines we can reconsider if switching to bindables is a good direction.
|
||||||
@ -93,6 +124,8 @@ namespace osu.Game.Screens.Edit.Setup
|
|||||||
Beatmap.Difficulty.DrainRate = healthDrainSlider.Current.Value;
|
Beatmap.Difficulty.DrainRate = healthDrainSlider.Current.Value;
|
||||||
Beatmap.Difficulty.ApproachRate = approachRateSlider.Current.Value;
|
Beatmap.Difficulty.ApproachRate = approachRateSlider.Current.Value;
|
||||||
Beatmap.Difficulty.OverallDifficulty = overallDifficultySlider.Current.Value;
|
Beatmap.Difficulty.OverallDifficulty = overallDifficultySlider.Current.Value;
|
||||||
|
Beatmap.Difficulty.SliderMultiplier = baseVelocitySlider.Current.Value;
|
||||||
|
Beatmap.Difficulty.SliderTickRate = tickRateSlider.Current.Value;
|
||||||
|
|
||||||
Beatmap.UpdateAllHitObjects();
|
Beatmap.UpdateAllHitObjects();
|
||||||
Beatmap.SaveState();
|
Beatmap.SaveState();
|
||||||
|
@ -15,6 +15,7 @@ using osuTK;
|
|||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Layout;
|
||||||
using osu.Framework.Threading;
|
using osu.Framework.Threading;
|
||||||
|
|
||||||
namespace osu.Game.Screens.Play
|
namespace osu.Game.Screens.Play
|
||||||
@ -51,7 +52,7 @@ namespace osu.Game.Screens.Play
|
|||||||
if (value == values) return;
|
if (value == values) return;
|
||||||
|
|
||||||
values = value;
|
values = value;
|
||||||
graphNeedsUpdate = true;
|
layout.Invalidate();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -71,23 +72,25 @@ namespace osu.Game.Screens.Play
|
|||||||
|
|
||||||
private ScheduledDelegate scheduledCreate;
|
private ScheduledDelegate scheduledCreate;
|
||||||
|
|
||||||
private bool graphNeedsUpdate;
|
private readonly LayoutValue layout = new LayoutValue(Invalidation.DrawSize | Invalidation.DrawInfo);
|
||||||
|
|
||||||
private Vector2 previousDrawSize;
|
public SquareGraph()
|
||||||
|
{
|
||||||
|
AddLayout(layout);
|
||||||
|
}
|
||||||
|
|
||||||
protected override void Update()
|
protected override void Update()
|
||||||
{
|
{
|
||||||
base.Update();
|
base.Update();
|
||||||
|
|
||||||
if (graphNeedsUpdate || (values != null && DrawSize != previousDrawSize))
|
if (!layout.IsValid)
|
||||||
{
|
{
|
||||||
columns?.FadeOut(500, Easing.OutQuint).Expire();
|
columns?.FadeOut(500, Easing.OutQuint).Expire();
|
||||||
|
|
||||||
scheduledCreate?.Cancel();
|
scheduledCreate?.Cancel();
|
||||||
scheduledCreate = Scheduler.AddDelayed(RecreateGraph, 500);
|
scheduledCreate = Scheduler.AddDelayed(RecreateGraph, 500);
|
||||||
|
|
||||||
previousDrawSize = DrawSize;
|
layout.Validate();
|
||||||
graphNeedsUpdate = false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -93,16 +93,22 @@ namespace osu.Game.Screens.Select
|
|||||||
|
|
||||||
protected override bool OnKeyDown(KeyDownEvent e)
|
protected override bool OnKeyDown(KeyDownEvent e)
|
||||||
{
|
{
|
||||||
updateText(e.ShiftPressed);
|
updateText(e);
|
||||||
return base.OnKeyDown(e);
|
return base.OnKeyDown(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnKeyUp(KeyUpEvent e)
|
protected override void OnKeyUp(KeyUpEvent e)
|
||||||
{
|
{
|
||||||
updateText(e.ShiftPressed);
|
updateText(e);
|
||||||
base.OnKeyUp(e);
|
base.OnKeyUp(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override bool OnMouseDown(MouseDownEvent e)
|
||||||
|
{
|
||||||
|
updateText(e);
|
||||||
|
return base.OnMouseDown(e);
|
||||||
|
}
|
||||||
|
|
||||||
protected override bool OnClick(ClickEvent e)
|
protected override bool OnClick(ClickEvent e)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@ -119,14 +125,15 @@ namespace osu.Game.Screens.Select
|
|||||||
|
|
||||||
protected override void OnMouseUp(MouseUpEvent e)
|
protected override void OnMouseUp(MouseUpEvent e)
|
||||||
{
|
{
|
||||||
|
base.OnMouseUp(e);
|
||||||
|
|
||||||
if (e.Button == MouseButton.Right && IsHovered)
|
if (e.Button == MouseButton.Right && IsHovered)
|
||||||
{
|
{
|
||||||
rewindSearch = true;
|
rewindSearch = true;
|
||||||
TriggerClick();
|
TriggerClick();
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
base.OnMouseUp(e);
|
updateText(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool OnPressed(KeyBindingPressEvent<GlobalAction> e)
|
public override bool OnPressed(KeyBindingPressEvent<GlobalAction> e)
|
||||||
@ -151,10 +158,12 @@ namespace osu.Game.Screens.Select
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateText(bool rewind = false)
|
private void updateText(UIEvent e)
|
||||||
{
|
{
|
||||||
randomSpriteText.Alpha = rewind ? 0 : 1;
|
bool aboutToRewind = e.ShiftPressed || e.CurrentState.Mouse.IsPressed(MouseButton.Right);
|
||||||
rewindSpriteText.Alpha = rewind ? 1 : 0;
|
|
||||||
|
randomSpriteText.Alpha = aboutToRewind ? 0 : 1;
|
||||||
|
rewindSpriteText.Alpha = aboutToRewind ? 1 : 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -69,7 +69,7 @@ namespace osu.Game.Storyboards.Drawables
|
|||||||
|
|
||||||
Size = new Vector2(640, 480);
|
Size = new Vector2(640, 480);
|
||||||
|
|
||||||
bool onlyHasVideoElements = Storyboard.Layers.SelectMany(l => l.Elements).Any(e => !(e is StoryboardVideo));
|
bool onlyHasVideoElements = Storyboard.Layers.SelectMany(l => l.Elements).All(e => e is StoryboardVideo);
|
||||||
|
|
||||||
Width = Height * (storyboard.BeatmapInfo.WidescreenStoryboard || onlyHasVideoElements ? 16 / 9f : 4 / 3f);
|
Width = Height * (storyboard.BeatmapInfo.WidescreenStoryboard || onlyHasVideoElements ? 16 / 9f : 4 / 3f);
|
||||||
|
|
||||||
|
@ -30,14 +30,14 @@
|
|||||||
<PackageReference Include="Microsoft.Data.Sqlite.Core" Version="7.0.2" />
|
<PackageReference Include="Microsoft.Data.Sqlite.Core" Version="7.0.2" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="7.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="7.0.0" />
|
||||||
<PackageReference Include="Microsoft.Toolkit.HighPerformance" Version="7.1.2" />
|
<PackageReference Include="Microsoft.Toolkit.HighPerformance" Version="7.1.2" />
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.2" />
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||||
<PackageReference Include="ppy.LocalisationAnalyser" Version="2022.809.0">
|
<PackageReference Include="ppy.LocalisationAnalyser" Version="2022.809.0">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Realm" Version="10.20.0" />
|
<PackageReference Include="Realm" Version="10.20.0" />
|
||||||
<PackageReference Include="ppy.osu.Framework" Version="2023.506.0" />
|
<PackageReference Include="ppy.osu.Framework" Version="2023.521.0" />
|
||||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2023.417.0" />
|
<PackageReference Include="ppy.osu.Game.Resources" Version="2023.510.0" />
|
||||||
<PackageReference Include="Sentry" Version="3.28.1" />
|
<PackageReference Include="Sentry" Version="3.28.1" />
|
||||||
<PackageReference Include="SharpCompress" Version="0.32.2" />
|
<PackageReference Include="SharpCompress" Version="0.32.2" />
|
||||||
<PackageReference Include="NUnit" Version="3.13.3" />
|
<PackageReference Include="NUnit" Version="3.13.3" />
|
||||||
|
@ -16,6 +16,6 @@
|
|||||||
<RuntimeIdentifier>iossimulator-x64</RuntimeIdentifier>
|
<RuntimeIdentifier>iossimulator-x64</RuntimeIdentifier>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="ppy.osu.Framework.iOS" Version="2023.506.0" />
|
<PackageReference Include="ppy.osu.Framework.iOS" Version="2023.521.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
@ -329,6 +329,7 @@
|
|||||||
<s:Boolean x:Key="/Default/CodeStyle/EncapsulateField/UseAutoProperty/@EntryValue">False</s:Boolean>
|
<s:Boolean x:Key="/Default/CodeStyle/EncapsulateField/UseAutoProperty/@EntryValue">False</s:Boolean>
|
||||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=AABB/@EntryIndexedValue">AABB</s:String>
|
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=AABB/@EntryIndexedValue">AABB</s:String>
|
||||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=API/@EntryIndexedValue">API</s:String>
|
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=API/@EntryIndexedValue">API</s:String>
|
||||||
|
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=ARGB/@EntryIndexedValue">ARGB</s:String>
|
||||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=BPM/@EntryIndexedValue">BPM</s:String>
|
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=BPM/@EntryIndexedValue">BPM</s:String>
|
||||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=EF/@EntryIndexedValue">EF</s:String>
|
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=EF/@EntryIndexedValue">EF</s:String>
|
||||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=FPS/@EntryIndexedValue">FPS</s:String>
|
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=FPS/@EntryIndexedValue">FPS</s:String>
|
||||||
@ -336,6 +337,7 @@
|
|||||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=GL/@EntryIndexedValue">GL</s:String>
|
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=GL/@EntryIndexedValue">GL</s:String>
|
||||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=GLSL/@EntryIndexedValue">GLSL</s:String>
|
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=GLSL/@EntryIndexedValue">GLSL</s:String>
|
||||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=HID/@EntryIndexedValue">HID</s:String>
|
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=HID/@EntryIndexedValue">HID</s:String>
|
||||||
|
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=HSL/@EntryIndexedValue">HSL</s:String>
|
||||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=HSPA/@EntryIndexedValue">HSPA</s:String>
|
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=HSPA/@EntryIndexedValue">HSPA</s:String>
|
||||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=HSV/@EntryIndexedValue">HSV</s:String>
|
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=HSV/@EntryIndexedValue">HSV</s:String>
|
||||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=HTML/@EntryIndexedValue">HTML</s:String>
|
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=HTML/@EntryIndexedValue">HTML</s:String>
|
||||||
@ -352,6 +354,7 @@
|
|||||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=OS/@EntryIndexedValue">OS</s:String>
|
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=OS/@EntryIndexedValue">OS</s:String>
|
||||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=PM/@EntryIndexedValue">PM</s:String>
|
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=PM/@EntryIndexedValue">PM</s:String>
|
||||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=RGB/@EntryIndexedValue">RGB</s:String>
|
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=RGB/@EntryIndexedValue">RGB</s:String>
|
||||||
|
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=RGBA/@EntryIndexedValue">RGBA</s:String>
|
||||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=RNG/@EntryIndexedValue">RNG</s:String>
|
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=RNG/@EntryIndexedValue">RNG</s:String>
|
||||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=SDL/@EntryIndexedValue">SDL</s:String>
|
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=SDL/@EntryIndexedValue">SDL</s:String>
|
||||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=SHA/@EntryIndexedValue">SHA</s:String>
|
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=SHA/@EntryIndexedValue">SHA</s:String>
|
||||||
|
Loading…
Reference in New Issue
Block a user