1
0
mirror of https://github.com/ppy/osu.git synced 2025-01-21 11:22:55 +08:00

Merge remote-tracking branch 'origin/master'

This commit is contained in:
smoogipoo 2018-07-26 21:28:31 +09:00
commit 464333cf0b
290 changed files with 2984 additions and 1671 deletions

28
.vscode/launch.json vendored
View File

@ -11,7 +11,11 @@
], ],
"cwd": "${workspaceRoot}", "cwd": "${workspaceRoot}",
"preLaunchTask": "Build tests (Debug)", "preLaunchTask": "Build tests (Debug)",
"env": {}, "linux": {
"env": {
"LD_LIBRARY_PATH": "${workspaceRoot}/osu.Game.Tests/bin/Debug/netcoreapp2.1:${env:LD_LIBRARY_PATH}"
}
},
"console": "internalConsole" "console": "internalConsole"
}, },
{ {
@ -24,7 +28,11 @@
], ],
"cwd": "${workspaceRoot}", "cwd": "${workspaceRoot}",
"preLaunchTask": "Build tests (Release)", "preLaunchTask": "Build tests (Release)",
"env": {}, "linux": {
"env": {
"LD_LIBRARY_PATH": "${workspaceRoot}/osu.Game.Tests/bin/Release/netcoreapp2.1:${env:LD_LIBRARY_PATH}"
}
},
"console": "internalConsole" "console": "internalConsole"
}, },
{ {
@ -33,11 +41,15 @@
"request": "launch", "request": "launch",
"program": "dotnet", "program": "dotnet",
"args": [ "args": [
"${workspaceRoot}/osu.Desktop/bin/Debug/netcoreapp2.1/osu!.dll", "${workspaceRoot}/osu.Desktop/bin/Debug/netcoreapp2.1/osu!.dll"
], ],
"cwd": "${workspaceRoot}", "cwd": "${workspaceRoot}",
"preLaunchTask": "Build osu! (Debug)", "preLaunchTask": "Build osu! (Debug)",
"env": {}, "linux": {
"env": {
"LD_LIBRARY_PATH": "${workspaceRoot}/osu.Desktop/bin/Debug/netcoreapp2.1:${env:LD_LIBRARY_PATH}"
}
},
"console": "internalConsole" "console": "internalConsole"
}, },
{ {
@ -46,11 +58,15 @@
"request": "launch", "request": "launch",
"program": "dotnet", "program": "dotnet",
"args": [ "args": [
"${workspaceRoot}/osu.Desktop/bin/Release/netcoreapp2.1/osu!.dll", "${workspaceRoot}/osu.Desktop/bin/Release/netcoreapp2.1/osu!.dll"
], ],
"cwd": "${workspaceRoot}", "cwd": "${workspaceRoot}",
"preLaunchTask": "Build osu! (Release)", "preLaunchTask": "Build osu! (Release)",
"env": {}, "linux": {
"env": {
"LD_LIBRARY_PATH": "${workspaceRoot}/osu.Desktop/bin/Release/netcoreapp2.1:${env:LD_LIBRARY_PATH}"
}
},
"console": "internalConsole" "console": "internalConsole"
} }
] ]

View File

@ -1,18 +1,32 @@
# osu! [![Build status](https://ci.appveyor.com/api/projects/status/u2p01nx7l6og8buh?svg=true)](https://ci.appveyor.com/project/peppy/osu) [![CodeFactor](https://www.codefactor.io/repository/github/ppy/osu/badge)](https://www.codefactor.io/repository/github/ppy/osu) [![dev chat](https://discordapp.com/api/guilds/188630481301012481/widget.png?style=shield)](https://discord.gg/ppy) # osu! [![Build status](https://ci.appveyor.com/api/projects/status/u2p01nx7l6og8buh?svg=true)](https://ci.appveyor.com/project/peppy/osu) [![CodeFactor](https://www.codefactor.io/repository/github/ppy/osu/badge)](https://www.codefactor.io/repository/github/ppy/osu) [![dev chat](https://discordapp.com/api/guilds/188630481301012481/widget.png?style=shield)](https://discord.gg/ppy)
Rhythm is just a *click* away. The future of [osu!](https://osu.ppy.sh) and the beginning of an open era! Rhythm is just a *click* away. The future of [osu!](https://osu.ppy.sh) and the beginning of an open era! Commonly known by the codename "osu!lazer". Pew pew.
# Status # Status
This is still heavily under development and is not intended for end-user use. This repository is intended for developer collaboration. You're welcome to try and use it but please do not submit bug reports without a patch. Please do not ask for help building or using this software. This project is still heavily under development, but is in a state where users are encouraged to try it out and keep it installed alongside the stable osu! client. It will continue to evolve over the coming months and hopefully bring some new unique features to the table.
We are accepting bug reports (please report with as much detail as possible). Feature requests are welcome as long as you read and understand the contribution guidelines listed below.
# Requirements # Requirements
- A desktop platform that can compile .NET 4.7.1. We recommend using [Visual Studio Community Edition](https://www.visualstudio.com/) (Windows), [Visual Studio for Mac](https://www.visualstudio.com/vs/visual-studio-mac/) (macOS) or [MonoDevelop](http://www.monodevelop.com/download/) (Linux), all of which are free. [Visual Studio Code](https://code.visualstudio.com/) may also be used but requires further setup steps which are not covered here. - A desktop platform with the [.NET Core SDK 2.1](https://www.microsoft.com/net/learn/get-started) or higher installed.
- When working with the codebase, we recommend using an IDE with intellisense and syntax highlighting, such as [Visual Studio Community Edition](https://www.visualstudio.com/) (Windows), [Visual Studio Code](https://code.visualstudio.com/) (with the C# plugin installed) or [Jetbrains Rider](https://www.jetbrains.com/rider/) (commercial).
# Getting Started # Building and running
- Clone the repository including submodules (`git clone --recurse-submodules https://github.com/ppy/osu`)
- Build in your IDE of choice (recommended IDEs automatically restore nuget packages; if you are using an alternative make sure to `nuget restore`) If you are not interested in developing the game, please head over to the [releases](https://github.com/ppy/osu/releases) to download a precompiled build with automatic updating enabled (download and run the install executable for your platform).
Clone the repository including submodules
`git clone --recurse-submodules https://github.com/ppy/osu`
Build and run
- Using Visual Studio 2017, Rider or Visual Studio Code (configurations are included)
- From command line using `dotnet run --project osu.Desktop --framework netcoreapp2.1`
If you run into issues building you may need to restore nuget packages (commonly via `dotnet restore`). Visual Studio Code users must run `Restore` task from debug tab before attempt to build.
# Contributing # Contributing

View File

@ -2,6 +2,9 @@ clone_depth: 1
version: '{branch}-{build}' version: '{branch}-{build}'
image: Visual Studio 2017 image: Visual Studio 2017
configuration: Debug configuration: Debug
cache:
- C:\ProgramData\chocolatey\bin -> appveyor.yml
- C:\ProgramData\chocolatey\lib -> appveyor.yml
install: install:
- cmd: git submodule update --init --recursive --depth=5 - cmd: git submodule update --init --recursive --depth=5
- cmd: choco install resharper-clt -y - cmd: choco install resharper-clt -y

View File

@ -12,6 +12,7 @@ using osu.Framework.Platform;
using osu.Game; using osu.Game;
using OpenTK.Input; using OpenTK.Input;
using Microsoft.Win32; using Microsoft.Win32;
using osu.Desktop.Updater;
using osu.Framework.Platform.Windows; using osu.Framework.Platform.Windows;
namespace osu.Desktop namespace osu.Desktop
@ -38,6 +39,52 @@ namespace osu.Desktop
} }
} }
protected override void LoadComplete()
{
base.LoadComplete();
if (!noVersionOverlay)
{
LoadComponentAsync(new VersionManager { Depth = int.MinValue }, v =>
{
Add(v);
v.State = Visibility.Visible;
});
#if NET_FRAMEWORK
Add(new SquirrelUpdateManager());
#else
Add(new SimpleUpdateManager());
#endif
}
}
public override void SetHost(GameHost host)
{
base.SetHost(host);
var desktopWindow = host.Window as DesktopGameWindow;
if (desktopWindow != null)
{
desktopWindow.CursorState |= CursorState.Hidden;
desktopWindow.SetIconFromStream(Assembly.GetExecutingAssembly().GetManifestResourceStream(GetType(), "lazer.ico"));
desktopWindow.Title = Name;
desktopWindow.FileDrop += fileDrop;
}
}
private void fileDrop(object sender, FileDropEventArgs e)
{
var filePaths = new[] { e.FileName };
var firstExtension = Path.GetExtension(filePaths.First());
if (filePaths.Any(f => Path.GetExtension(f) != firstExtension)) return;
Task.Factory.StartNew(() => Import(filePaths), TaskCreationOptions.LongRunning);
}
/// <summary> /// <summary>
/// A method of accessing an osu-stable install in a controlled fashion. /// A method of accessing an osu-stable install in a controlled fashion.
/// </summary> /// </summary>
@ -77,45 +124,5 @@ namespace osu.Desktop
{ {
} }
} }
protected override void LoadComplete()
{
base.LoadComplete();
if (!noVersionOverlay)
{
LoadComponentAsync(new VersionManager { Depth = int.MinValue }, v =>
{
Add(v);
v.State = Visibility.Visible;
});
}
}
public override void SetHost(GameHost host)
{
base.SetHost(host);
var desktopWindow = host.Window as DesktopGameWindow;
if (desktopWindow != null)
{
desktopWindow.CursorState |= CursorState.Hidden;
desktopWindow.SetIconFromStream(Assembly.GetExecutingAssembly().GetManifestResourceStream(GetType(), "lazer.ico"));
desktopWindow.Title = Name;
desktopWindow.FileDrop += fileDrop;
}
}
private void fileDrop(object sender, FileDropEventArgs e)
{
var filePaths = new[] { e.FileName };
var firstExtension = Path.GetExtension(filePaths.First());
if (filePaths.Any(f => Path.GetExtension(f) != firstExtension)) return;
Task.Factory.StartNew(() => Import(filePaths), TaskCreationOptions.LongRunning);
}
} }
} }

View File

@ -91,10 +91,6 @@ namespace osu.Desktop.Overlays
} }
} }
}; };
#if NET_FRAMEWORK
Add(new SquirrelUpdateManager());
#endif
} }
protected override void LoadComplete() protected override void LoadComplete()

View File

@ -7,6 +7,7 @@ using System.Linq;
using osu.Framework; using osu.Framework;
using osu.Framework.Platform; using osu.Framework.Platform;
using osu.Game.IPC; using osu.Game.IPC;
#if NET_FRAMEWORK #if NET_FRAMEWORK
using System.Runtime; using System.Runtime;
#endif #endif
@ -18,10 +19,7 @@ namespace osu.Desktop
[STAThread] [STAThread]
public static int Main(string[] args) public static int Main(string[] args)
{ {
// required to initialise native SQLite libraries on some platforms. useMultiCoreJit();
if (!RuntimeInfo.IsMono)
useMulticoreJit();
// Back up the cwd before DesktopGameHost changes it // Back up the cwd before DesktopGameHost changes it
var cwd = Environment.CurrentDirectory; var cwd = Environment.CurrentDirectory;
@ -54,7 +52,7 @@ namespace osu.Desktop
} }
} }
private static void useMulticoreJit() private static void useMultiCoreJit()
{ {
#if NET_FRAMEWORK #if NET_FRAMEWORK
var directory = Directory.CreateDirectory(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Profiles")); var directory = Directory.CreateDirectory(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Profiles"));

View File

@ -0,0 +1,103 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Newtonsoft.Json;
using osu.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics.Containers;
using osu.Framework.IO.Network;
using osu.Framework.Platform;
using osu.Game;
using osu.Game.Graphics;
using osu.Game.Overlays;
using osu.Game.Overlays.Notifications;
namespace osu.Desktop.Updater
{
/// <summary>
/// An update manager that shows notifications if a newer release is detected.
/// Installation is left up to the user.
/// </summary>
internal class SimpleUpdateManager : CompositeDrawable
{
private NotificationOverlay notificationOverlay;
private string version;
private GameHost host;
[BackgroundDependencyLoader]
private void load(NotificationOverlay notification, OsuGameBase game, GameHost host)
{
notificationOverlay = notification;
this.host = host;
version = game.Version;
if (game.IsDeployedBuild)
Schedule(() => Task.Run(() => checkForUpdateAsync()));
}
private async void checkForUpdateAsync()
{
var releases = new JsonWebRequest<GitHubRelease>("https://api.github.com/repos/ppy/osu/releases/latest");
await releases.PerformAsync();
var latest = releases.ResponseObject;
if (latest.TagName != version)
{
notificationOverlay.Post(new SimpleNotification
{
Text = $"A newer release of osu! has been found ({version} → {latest.TagName}).\n\n"
+ "Click here to download the new version, which can be installed over the top of your existing installation",
Icon = FontAwesome.fa_upload,
Activated = () =>
{
host.OpenUrlExternally(getBestUrl(latest));
return true;
}
});
}
}
private string getBestUrl(GitHubRelease release)
{
GitHubAsset bestAsset = null;
switch (RuntimeInfo.OS)
{
case RuntimeInfo.Platform.Windows:
bestAsset = release.Assets?.FirstOrDefault(f => f.Name.EndsWith(".exe"));
break;
case RuntimeInfo.Platform.MacOsx:
bestAsset = release.Assets?.FirstOrDefault(f => f.Name.EndsWith(".app.zip"));
break;
}
return bestAsset?.BrowserDownloadUrl ?? release.HtmlUrl;
}
public class GitHubRelease
{
[JsonProperty("html_url")]
public string HtmlUrl { get; set; }
[JsonProperty("tag_name")]
public string TagName { get; set; }
[JsonProperty("assets")]
public List<GitHubAsset> Assets { get; set; }
}
public class GitHubAsset
{
[JsonProperty("name")]
public string Name { get; set; }
[JsonProperty("browser_download_url")]
public string BrowserDownloadUrl { get; set; }
}
}
}

View File

@ -3,6 +3,7 @@
#if NET_FRAMEWORK #if NET_FRAMEWORK
using System; using System;
using System.Threading.Tasks;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Colour;
@ -16,7 +17,7 @@ using OpenTK;
using OpenTK.Graphics; using OpenTK.Graphics;
using Squirrel; using Squirrel;
namespace osu.Desktop.Overlays namespace osu.Desktop.Updater
{ {
public class SquirrelUpdateManager : Component public class SquirrelUpdateManager : Component
{ {
@ -35,7 +36,7 @@ namespace osu.Desktop.Overlays
notificationOverlay = notification; notificationOverlay = notification;
if (game.IsDeployedBuild) if (game.IsDeployedBuild)
Schedule(() => checkForUpdateAsync()); Schedule(() => Task.Run(() => checkForUpdateAsync()));
} }
private async void checkForUpdateAsync(bool useDeltaPatching = true, UpdateProgressNotification notification = null) private async void checkForUpdateAsync(bool useDeltaPatching = true, UpdateProgressNotification notification = null)

View File

@ -28,18 +28,20 @@ namespace osu.Game.Rulesets.Catch.Tests
protected override IEnumerable<ConvertValue> CreateConvertValue(HitObject hitObject) protected override IEnumerable<ConvertValue> CreateConvertValue(HitObject hitObject)
{ {
if (hitObject is JuiceStream stream) switch (hitObject)
{ {
case JuiceStream stream:
foreach (var nested in stream.NestedHitObjects) foreach (var nested in stream.NestedHitObjects)
yield return new ConvertValue((CatchHitObject)nested); yield return new ConvertValue((CatchHitObject)nested);
} break;
else if (hitObject is BananaShower shower) case BananaShower shower:
{
foreach (var nested in shower.NestedHitObjects) foreach (var nested in shower.NestedHitObjects)
yield return new ConvertValue((CatchHitObject)nested); yield return new ConvertValue((CatchHitObject)nested);
} break;
else default:
yield return new ConvertValue((CatchHitObject)hitObject); yield return new ConvertValue((CatchHitObject)hitObject);
break;
}
} }
protected override Ruleset CreateRuleset() => new CatchRuleset(); protected override Ruleset CreateRuleset() => new CatchRuleset();

View File

@ -55,7 +55,7 @@ namespace osu.Game.Rulesets.Catch.Tests
{ {
} }
public void ToggleHyperDash(bool status) => MovableCatcher.SetHyperdashState(status ? 2 : 1); public void ToggleHyperDash(bool status) => MovableCatcher.SetHyperDashState(status ? 2 : 1);
} }
} }
} }

View File

@ -8,9 +8,9 @@ using osu.Game.Rulesets.Catch.Objects;
namespace osu.Game.Rulesets.Catch.Tests namespace osu.Game.Rulesets.Catch.Tests
{ {
[TestFixture] [TestFixture]
public class TestCaseHyperdash : Game.Tests.Visual.TestCasePlayer public class TestCaseHyperDash : Game.Tests.Visual.TestCasePlayer
{ {
public TestCaseHyperdash() public TestCaseHyperDash()
: base(new CatchRuleset()) : base(new CatchRuleset())
{ {
} }

View File

@ -46,13 +46,16 @@ namespace osu.Game.Rulesets.Catch.Difficulty
foreach (var hitObject in beatmap.HitObjects) foreach (var hitObject in beatmap.HitObjects)
{ {
// We want to only consider fruits that contribute to the combo. Droplets are addressed as accuracy and spinners are not relevant for "skill" calculations. switch (hitObject)
if (hitObject is Fruit)
{ {
difficultyHitObjects.Add(new CatchDifficultyHitObject((CatchHitObject)hitObject, halfCatchWidth)); // We want to only consider fruits that contribute to the combo. Droplets are addressed as accuracy and spinners are not relevant for "skill" calculations.
} case Fruit fruit:
if (hitObject is JuiceStream) difficultyHitObjects.Add(new CatchDifficultyHitObject(fruit, halfCatchWidth));
break;
case JuiceStream _:
difficultyHitObjects.AddRange(hitObject.NestedHitObjects.OfType<CatchHitObject>().Where(o => !(o is TinyDroplet)).Select(o => new CatchDifficultyHitObject(o, halfCatchWidth))); difficultyHitObjects.AddRange(hitObject.NestedHitObjects.OfType<CatchHitObject>().Where(o => !(o is TinyDroplet)).Select(o => new CatchDifficultyHitObject(o, halfCatchWidth)));
break;
}
} }
difficultyHitObjects.Sort((a, b) => a.BaseHitObject.StartTime.CompareTo(b.BaseHitObject.StartTime)); difficultyHitObjects.Sort((a, b) => a.BaseHitObject.StartTime.CompareTo(b.BaseHitObject.StartTime));
@ -61,12 +64,12 @@ namespace osu.Game.Rulesets.Catch.Difficulty
return new CatchDifficultyAttributes(mods, 0); return new CatchDifficultyAttributes(mods, 0);
// this is the same as osu!, so there's potential to share the implementation... maybe // this is the same as osu!, so there's potential to share the implementation... maybe
double preEmpt = BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.ApproachRate, 1800, 1200, 450) / timeRate; double preempt = BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.ApproachRate, 1800, 1200, 450) / timeRate;
double starRating = Math.Sqrt(calculateDifficulty(difficultyHitObjects, timeRate)) * star_scaling_factor; double starRating = Math.Sqrt(calculateDifficulty(difficultyHitObjects, timeRate)) * star_scaling_factor;
return new CatchDifficultyAttributes(mods, starRating) return new CatchDifficultyAttributes(mods, starRating)
{ {
ApproachRate = preEmpt > 1200.0 ? -(preEmpt - 1800.0) / 120.0 : -(preEmpt - 1200.0) / 150.0 + 5.0, ApproachRate = preempt > 1200.0 ? -(preempt - 1800.0) / 120.0 : -(preempt - 1200.0) / 150.0 + 5.0,
MaxCombo = difficultyHitObjects.Count MaxCombo = difficultyHitObjects.Count
}; };
} }

View File

@ -2,7 +2,7 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Collections.Generic; using System.Collections.Generic;
using osu.Framework.Input; using osu.Framework.Input.StateChanges;
using osu.Framework.MathUtils; using osu.Framework.MathUtils;
using osu.Game.Rulesets.Replays; using osu.Game.Rulesets.Replays;

View File

@ -23,8 +23,10 @@ namespace osu.Game.Rulesets.Catch.UI
private readonly CatcherArea catcherArea; private readonly CatcherArea catcherArea;
public CatchPlayfield(BeatmapDifficulty difficulty, Func<CatchHitObject, DrawableHitObject<CatchHitObject>> getVisualRepresentation) public CatchPlayfield(BeatmapDifficulty difficulty, Func<CatchHitObject, DrawableHitObject<CatchHitObject>> getVisualRepresentation)
: base(ScrollingDirection.Down, BASE_WIDTH) : base(BASE_WIDTH)
{ {
Direction.Value = ScrollingDirection.Down;
Container explodingFruitContainer; Container explodingFruitContainer;
Anchor = Anchor.TopCentre; Anchor = Anchor.TopCentre;

View File

@ -255,11 +255,11 @@ namespace osu.Game.Rulesets.Catch.UI
double positionDifference = target.X * CatchPlayfield.BASE_WIDTH - catcherPosition; double positionDifference = target.X * CatchPlayfield.BASE_WIDTH - catcherPosition;
double velocity = positionDifference / Math.Max(1.0, timeDifference - 1000.0 / 60.0); double velocity = positionDifference / Math.Max(1.0, timeDifference - 1000.0 / 60.0);
SetHyperdashState(Math.Abs(velocity), target.X); SetHyperDashState(Math.Abs(velocity), target.X);
} }
else else
{ {
SetHyperdashState(); SetHyperDashState();
} }
return validCatch; return validCatch;
@ -270,18 +270,18 @@ namespace osu.Game.Rulesets.Catch.UI
private float hyperDashTargetPosition; private float hyperDashTargetPosition;
/// <summary> /// <summary>
/// Whether we are hypderdashing or not. /// Whether we are hyper-dashing or not.
/// </summary> /// </summary>
public bool HyperDashing => hyperDashModifier != 1; public bool HyperDashing => hyperDashModifier != 1;
/// <summary> /// <summary>
/// Set hyperdash state. /// Set hyper-dash state.
/// </summary> /// </summary>
/// <param name="modifier">The speed multiplier. If this is less or equals to 1, this catcher will be non-hyperdashing state.</param> /// <param name="modifier">The speed multiplier. If this is less or equals to 1, this catcher will be non-hyper-dashing state.</param>
/// <param name="targetPosition">When this catcher crosses this position, this catcher ends hyperdashing.</param> /// <param name="targetPosition">When this catcher crosses this position, this catcher ends hyper-dashing.</param>
public void SetHyperdashState(double modifier = 1, float targetPosition = -1) public void SetHyperDashState(double modifier = 1, float targetPosition = -1)
{ {
const float hyperdash_transition_length = 180; const float hyper_dash_transition_length = 180;
bool previouslyHyperDashing = HyperDashing; bool previouslyHyperDashing = HyperDashing;
if (modifier <= 1 || X == targetPosition) if (modifier <= 1 || X == targetPosition)
@ -291,8 +291,8 @@ namespace osu.Game.Rulesets.Catch.UI
if (previouslyHyperDashing) if (previouslyHyperDashing)
{ {
this.FadeColour(Color4.White, hyperdash_transition_length, Easing.OutQuint); this.FadeColour(Color4.White, hyper_dash_transition_length, Easing.OutQuint);
this.FadeTo(1, hyperdash_transition_length, Easing.OutQuint); this.FadeTo(1, hyper_dash_transition_length, Easing.OutQuint);
} }
} }
else else
@ -303,8 +303,8 @@ namespace osu.Game.Rulesets.Catch.UI
if (!previouslyHyperDashing) if (!previouslyHyperDashing)
{ {
this.FadeColour(Color4.OrangeRed, hyperdash_transition_length, Easing.OutQuint); this.FadeColour(Color4.OrangeRed, hyper_dash_transition_length, Easing.OutQuint);
this.FadeTo(0.2f, hyperdash_transition_length, Easing.OutQuint); this.FadeTo(0.2f, hyper_dash_transition_length, Easing.OutQuint);
Trail = true; Trail = true;
} }
} }
@ -370,7 +370,7 @@ namespace osu.Game.Rulesets.Catch.UI
hyperDashDirection < 0 && hyperDashTargetPosition > X) hyperDashDirection < 0 && hyperDashTargetPosition > X)
{ {
X = hyperDashTargetPosition; X = hyperDashTargetPosition;
SetHyperdashState(); SetHyperDashState();
} }
} }

View File

@ -5,6 +5,8 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.MathUtils; using osu.Framework.MathUtils;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Objects.Types;
@ -13,11 +15,10 @@ using osu.Game.Tests.Beatmaps;
namespace osu.Game.Rulesets.Mania.Tests namespace osu.Game.Rulesets.Mania.Tests
{ {
[TestFixture] [TestFixture]
public class ManiaBeatmapConversionTest : BeatmapConversionTest<ConvertValue> public class ManiaBeatmapConversionTest : BeatmapConversionTest<ManiaConvertMapping, ConvertValue>
{ {
protected override string ResourceAssembly => "osu.Game.Rulesets.Mania"; protected override string ResourceAssembly => "osu.Game.Rulesets.Mania";
[NonParallelizable]
[TestCase("basic")] [TestCase("basic")]
public new void Test(string name) public new void Test(string name)
{ {
@ -34,9 +35,35 @@ namespace osu.Game.Rulesets.Mania.Tests
}; };
} }
protected override ManiaConvertMapping CreateConvertMapping() => new ManiaConvertMapping(Converter);
protected override Ruleset CreateRuleset() => new ManiaRuleset(); protected override Ruleset CreateRuleset() => new ManiaRuleset();
} }
public class ManiaConvertMapping : ConvertMapping<ConvertValue>, IEquatable<ManiaConvertMapping>
{
public uint RandomW;
public uint RandomX;
public uint RandomY;
public uint RandomZ;
public ManiaConvertMapping()
{
}
public ManiaConvertMapping(IBeatmapConverter converter)
{
var maniaConverter = (ManiaBeatmapConverter)converter;
RandomW = maniaConverter.Random.W;
RandomX = maniaConverter.Random.X;
RandomY = maniaConverter.Random.Y;
RandomZ = maniaConverter.Random.Z;
}
public bool Equals(ManiaConvertMapping other) => other != null && RandomW == other.RandomW && RandomX == other.RandomX && RandomY == other.RandomY && RandomZ == other.RandomZ;
public override bool Equals(ConvertMapping<ConvertValue> other) => base.Equals(other) && Equals(other as ManiaConvertMapping);
}
public struct ConvertValue : IEquatable<ConvertValue> public struct ConvertValue : IEquatable<ConvertValue>
{ {
/// <summary> /// <summary>

View File

@ -21,9 +21,9 @@ namespace osu.Game.Rulesets.Mania.Tests
this.direction = direction; this.direction = direction;
} }
protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent) protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
{ {
var dependencies = new DependencyContainer(base.CreateLocalDependencies(parent)); var dependencies = new DependencyContainer(base.CreateChildDependencies(parent));
dependencies.CacheAs<IScrollingInfo>(new ScrollingInfo { Direction = { Value = direction }}); dependencies.CacheAs<IScrollingInfo>(new ScrollingInfo { Direction = { Value = direction }});
return dependencies; return dependencies;
} }

View File

@ -86,7 +86,7 @@ namespace osu.Game.Rulesets.Mania.Tests
private Drawable createColumn(ScrollingDirection direction, ManiaAction action) private Drawable createColumn(ScrollingDirection direction, ManiaAction action)
{ {
var column = new Column(direction) var column = new Column
{ {
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
Origin = Anchor.Centre, Origin = Anchor.Centre,

View File

@ -62,7 +62,7 @@ namespace osu.Game.Rulesets.Mania.Tests
return new ScrollingTestContainer(direction) return new ScrollingTestContainer(direction)
{ {
AutoSizeAxes = Axes.Both, AutoSizeAxes = Axes.Both,
Child = new NoteContainer(direction, $"note, scrolling {direction.ToString().ToLower()}") Child = new NoteContainer(direction, $"note, scrolling {direction.ToString().ToLowerInvariant()}")
{ {
Child = new DrawableNote(note) { AccentColour = Color4.OrangeRed } Child = new DrawableNote(note) { AccentColour = Color4.OrangeRed }
} }
@ -77,7 +77,7 @@ namespace osu.Game.Rulesets.Mania.Tests
return new ScrollingTestContainer(direction) return new ScrollingTestContainer(direction)
{ {
AutoSizeAxes = Axes.Both, AutoSizeAxes = Axes.Both,
Child = new NoteContainer(direction, $"hold note, scrolling {direction.ToString().ToLower()}") Child = new NoteContainer(direction, $"hold note, scrolling {direction.ToString().ToLowerInvariant()}")
{ {
Child = new DrawableHoldNote(note) Child = new DrawableHoldNote(note)
{ {
@ -137,9 +137,9 @@ namespace osu.Game.Rulesets.Mania.Tests
}; };
} }
protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent) protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
{ {
var dependencies = new DependencyContainer(base.CreateLocalDependencies(parent)); var dependencies = new DependencyContainer(base.CreateChildDependencies(parent));
dependencies.CacheAs<IBindable<ManiaAction>>(new Bindable<ManiaAction>()); dependencies.CacheAs<IBindable<ManiaAction>>(new Bindable<ManiaAction>());
return dependencies; return dependencies;
} }

View File

@ -105,7 +105,7 @@ namespace osu.Game.Rulesets.Mania.Tests
{ {
var specialAction = ManiaAction.Special1; var specialAction = ManiaAction.Special1;
var stage = new ManiaStage(direction, 0, new StageDefinition { Columns = 2 }, ref action, ref specialAction) { VisibleTimeRange = { Value = 2000 } }; var stage = new ManiaStage(0, new StageDefinition { Columns = 2 }, ref action, ref specialAction) { VisibleTimeRange = { Value = 2000 } };
stages.Add(stage); stages.Add(stage);
return new ScrollingTestContainer(direction) return new ScrollingTestContainer(direction)

View File

@ -5,6 +5,7 @@ using osu.Game.Rulesets.Mania.Objects;
using System; using System;
using System.Linq; using System.Linq;
using System.Collections.Generic; using System.Collections.Generic;
using osu.Framework.MathUtils;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Objects.Types;
@ -28,8 +29,10 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
public int TargetColumns; public int TargetColumns;
public readonly bool IsForCurrentRuleset; public readonly bool IsForCurrentRuleset;
// Internal for testing purposes
internal FastRandom Random { get; private set; }
private Pattern lastPattern = new Pattern(); private Pattern lastPattern = new Pattern();
private FastRandom random;
private ManiaBeatmap beatmap; private ManiaBeatmap beatmap;
@ -62,7 +65,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
BeatmapDifficulty difficulty = original.BeatmapInfo.BaseDifficulty; BeatmapDifficulty difficulty = original.BeatmapInfo.BaseDifficulty;
int seed = (int)Math.Round(difficulty.DrainRate + difficulty.CircleSize) * 20 + (int)(difficulty.OverallDifficulty * 41.2) + (int)Math.Round(difficulty.ApproachRate); int seed = (int)Math.Round(difficulty.DrainRate + difficulty.CircleSize) * 20 + (int)(difficulty.OverallDifficulty * 41.2) + (int)Math.Round(difficulty.ApproachRate);
random = new FastRandom(seed); Random = new FastRandom(seed);
return base.ConvertBeatmap(original); return base.ConvertBeatmap(original);
} }
@ -100,7 +103,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
private double lastTime; private double lastTime;
private Vector2 lastPosition; private Vector2 lastPosition;
private PatternType lastStair; private PatternType lastStair = PatternType.Stair;
private void recordNote(double time, Vector2 position) private void recordNote(double time, Vector2 position)
{ {
lastTime = time; lastTime = time;
@ -115,12 +118,15 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
/// <returns>The hit objects generated.</returns> /// <returns>The hit objects generated.</returns>
private IEnumerable<ManiaHitObject> generateSpecific(HitObject original, IBeatmap originalBeatmap) private IEnumerable<ManiaHitObject> generateSpecific(HitObject original, IBeatmap originalBeatmap)
{ {
var generator = new SpecificBeatmapPatternGenerator(random, original, beatmap, lastPattern, originalBeatmap); var generator = new SpecificBeatmapPatternGenerator(Random, original, beatmap, lastPattern, originalBeatmap);
Pattern newPattern = generator.Generate(); foreach (var newPattern in generator.Generate())
{
lastPattern = newPattern; lastPattern = newPattern;
return newPattern.HitObjects; foreach (var obj in newPattern.HitObjects)
yield return obj;
}
} }
/// <summary> /// <summary>
@ -138,27 +144,44 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
Patterns.PatternGenerator conversion = null; Patterns.PatternGenerator conversion = null;
if (distanceData != null) if (distanceData != null)
conversion = new DistanceObjectPatternGenerator(random, original, beatmap, lastPattern, originalBeatmap); {
var generator = new DistanceObjectPatternGenerator(Random, original, beatmap, lastPattern, originalBeatmap);
conversion = generator;
for (double time = original.StartTime; !Precision.DefinitelyBigger(time, generator.EndTime); time += generator.SegmentDuration)
{
recordNote(time, positionData?.Position ?? Vector2.Zero);
computeDensity(time);
}
}
else if (endTimeData != null) else if (endTimeData != null)
conversion = new EndTimeObjectPatternGenerator(random, original, beatmap, originalBeatmap); {
conversion = new EndTimeObjectPatternGenerator(Random, original, beatmap, originalBeatmap);
recordNote(endTimeData.EndTime, new Vector2(256, 192));
computeDensity(endTimeData.EndTime);
}
else if (positionData != null) else if (positionData != null)
{ {
computeDensity(original.StartTime); computeDensity(original.StartTime);
conversion = new HitObjectPatternGenerator(random, original, beatmap, lastPattern, lastTime, lastPosition, density, lastStair, originalBeatmap); conversion = new HitObjectPatternGenerator(Random, original, beatmap, lastPattern, lastTime, lastPosition, density, lastStair, originalBeatmap);
recordNote(original.StartTime, positionData.Position); recordNote(original.StartTime, positionData.Position);
} }
if (conversion == null) if (conversion == null)
return null; yield break;
Pattern newPattern = conversion.Generate();
foreach (var newPattern in conversion.Generate())
{
lastPattern = conversion is EndTimeObjectPatternGenerator ? lastPattern : newPattern; lastPattern = conversion is EndTimeObjectPatternGenerator ? lastPattern : newPattern;
lastStair = (conversion as HitObjectPatternGenerator)?.StairType ?? lastStair; lastStair = (conversion as HitObjectPatternGenerator)?.StairType ?? lastStair;
return newPattern.HitObjects; foreach (var obj in newPattern.HitObjects)
yield return obj;
}
} }
/// <summary> /// <summary>
@ -171,7 +194,12 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
{ {
} }
public override Pattern Generate() public override IEnumerable<Pattern> Generate()
{
yield return generate();
}
private Pattern generate()
{ {
var endTimeData = HitObject as IHasEndTime; var endTimeData = HitObject as IHasEndTime;
var positionData = HitObject as IHasXPosition; var positionData = HitObject as IHasXPosition;

View File

@ -4,6 +4,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using osu.Framework.MathUtils;
using osu.Game.Audio; using osu.Game.Audio;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Rulesets.Mania.MathUtils; using osu.Game.Rulesets.Mania.MathUtils;
@ -24,8 +25,9 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
/// </summary> /// </summary>
private const float osu_base_scoring_distance = 100; private const float osu_base_scoring_distance = 100;
private readonly double endTime; public readonly double EndTime;
private readonly double segmentDuration; public readonly double SegmentDuration;
private readonly int spanCount; private readonly int spanCount;
private PatternType convertType; private PatternType convertType;
@ -52,77 +54,105 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
// The duration of the osu! hit object // The duration of the osu! hit object
double osuDuration = distance / osuVelocity; double osuDuration = distance / osuVelocity;
endTime = hitObject.StartTime + osuDuration; EndTime = hitObject.StartTime + osuDuration;
segmentDuration = (endTime - HitObject.StartTime) / spanCount; SegmentDuration = (EndTime - HitObject.StartTime) / spanCount;
} }
public override Pattern Generate() public override IEnumerable<Pattern> Generate()
{
var originalPattern = generate();
if (originalPattern.HitObjects.Count() == 1)
{
yield return originalPattern;
yield break;
}
// We need to split the intermediate pattern into two new patterns:
// 1. A pattern containing all objects that do not end at our EndTime.
// 2. A pattern containing all objects that end at our EndTime. This will be used for further pattern generation.
var intermediatePattern = new Pattern();
var endTimePattern = new Pattern();
foreach (var obj in originalPattern.HitObjects)
{
if (!Precision.AlmostEquals(EndTime, (obj as IHasEndTime)?.EndTime ?? obj.StartTime))
intermediatePattern.Add(obj);
else
endTimePattern.Add(obj);
}
yield return intermediatePattern;
yield return endTimePattern;
}
private Pattern generate()
{ {
if (TotalColumns == 1) if (TotalColumns == 1)
{ {
var pattern = new Pattern(); var pattern = new Pattern();
addToPattern(pattern, 0, HitObject.StartTime, endTime); addToPattern(pattern, 0, HitObject.StartTime, EndTime);
return pattern; return pattern;
} }
if (spanCount > 1) if (spanCount > 1)
{ {
if (segmentDuration <= 90) if (SegmentDuration <= 90)
return generateRandomHoldNotes(HitObject.StartTime, 1); return generateRandomHoldNotes(HitObject.StartTime, 1);
if (segmentDuration <= 120) if (SegmentDuration <= 120)
{ {
convertType |= PatternType.ForceNotStack; convertType |= PatternType.ForceNotStack;
return generateRandomNotes(HitObject.StartTime, spanCount + 1); return generateRandomNotes(HitObject.StartTime, spanCount + 1);
} }
if (segmentDuration <= 160) if (SegmentDuration <= 160)
return generateStair(HitObject.StartTime); return generateStair(HitObject.StartTime);
if (segmentDuration <= 200 && ConversionDifficulty > 3) if (SegmentDuration <= 200 && ConversionDifficulty > 3)
return generateRandomMultipleNotes(HitObject.StartTime); return generateRandomMultipleNotes(HitObject.StartTime);
double duration = endTime - HitObject.StartTime; double duration = EndTime - HitObject.StartTime;
if (duration >= 4000) if (duration >= 4000)
return generateNRandomNotes(HitObject.StartTime, 0.23, 0, 0); return generateNRandomNotes(HitObject.StartTime, 0.23, 0, 0);
if (segmentDuration > 400 && spanCount < TotalColumns - 1 - RandomStart) if (SegmentDuration > 400 && spanCount < TotalColumns - 1 - RandomStart)
return generateTiledHoldNotes(HitObject.StartTime); return generateTiledHoldNotes(HitObject.StartTime);
return generateHoldAndNormalNotes(HitObject.StartTime); return generateHoldAndNormalNotes(HitObject.StartTime);
} }
if (segmentDuration <= 110) if (SegmentDuration <= 110)
{ {
if (PreviousPattern.ColumnWithObjects < TotalColumns) if (PreviousPattern.ColumnWithObjects < TotalColumns)
convertType |= PatternType.ForceNotStack; convertType |= PatternType.ForceNotStack;
else else
convertType &= ~PatternType.ForceNotStack; convertType &= ~PatternType.ForceNotStack;
return generateRandomNotes(HitObject.StartTime, segmentDuration < 80 ? 1 : 2); return generateRandomNotes(HitObject.StartTime, SegmentDuration < 80 ? 1 : 2);
} }
if (ConversionDifficulty > 6.5) if (ConversionDifficulty > 6.5)
{ {
if ((convertType & PatternType.LowProbability) > 0) if (convertType.HasFlag(PatternType.LowProbability))
return generateNRandomNotes(HitObject.StartTime, 0.78, 0.3, 0); return generateNRandomNotes(HitObject.StartTime, 0.78, 0.3, 0);
return generateNRandomNotes(HitObject.StartTime, 0.85, 0.36, 0.03); return generateNRandomNotes(HitObject.StartTime, 0.85, 0.36, 0.03);
} }
if (ConversionDifficulty > 4) if (ConversionDifficulty > 4)
{ {
if ((convertType & PatternType.LowProbability) > 0) if (convertType.HasFlag(PatternType.LowProbability))
return generateNRandomNotes(HitObject.StartTime, 0.43, 0.08, 0); return generateNRandomNotes(HitObject.StartTime, 0.43, 0.08, 0);
return generateNRandomNotes(HitObject.StartTime, 0.56, 0.18, 0); return generateNRandomNotes(HitObject.StartTime, 0.56, 0.18, 0);
} }
if (ConversionDifficulty > 2.5) if (ConversionDifficulty > 2.5)
{ {
if ((convertType & PatternType.LowProbability) > 0) if (convertType.HasFlag(PatternType.LowProbability))
return generateNRandomNotes(HitObject.StartTime, 0.3, 0, 0); return generateNRandomNotes(HitObject.StartTime, 0.3, 0, 0);
return generateNRandomNotes(HitObject.StartTime, 0.37, 0.08, 0); return generateNRandomNotes(HitObject.StartTime, 0.37, 0.08, 0);
} }
if ((convertType & PatternType.LowProbability) > 0) if (convertType.HasFlag(PatternType.LowProbability))
return generateNRandomNotes(HitObject.StartTime, 0.17, 0, 0); return generateNRandomNotes(HitObject.StartTime, 0.17, 0, 0);
return generateNRandomNotes(HitObject.StartTime, 0.27, 0, 0); return generateNRandomNotes(HitObject.StartTime, 0.27, 0, 0);
} }
@ -148,7 +178,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
{ {
while (pattern.ColumnHasObject(nextColumn) || PreviousPattern.ColumnHasObject(nextColumn)) //find available column while (pattern.ColumnHasObject(nextColumn) || PreviousPattern.ColumnHasObject(nextColumn)) //find available column
nextColumn = Random.Next(RandomStart, TotalColumns); nextColumn = Random.Next(RandomStart, TotalColumns);
addToPattern(pattern, nextColumn, startTime, endTime); addToPattern(pattern, nextColumn, startTime, EndTime);
} }
// This is can't be combined with the above loop due to RNG // This is can't be combined with the above loop due to RNG
@ -156,7 +186,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
{ {
while (pattern.ColumnHasObject(nextColumn)) while (pattern.ColumnHasObject(nextColumn))
nextColumn = Random.Next(RandomStart, TotalColumns); nextColumn = Random.Next(RandomStart, TotalColumns);
addToPattern(pattern, nextColumn, startTime, endTime); addToPattern(pattern, nextColumn, startTime, EndTime);
} }
return pattern; return pattern;
@ -179,7 +209,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
var pattern = new Pattern(); var pattern = new Pattern();
int nextColumn = GetColumn((HitObject as IHasXPosition)?.X ?? 0, true); int nextColumn = GetColumn((HitObject as IHasXPosition)?.X ?? 0, true);
if ((convertType & PatternType.ForceNotStack) > 0 && PreviousPattern.ColumnWithObjects < TotalColumns) if (convertType.HasFlag(PatternType.ForceNotStack) && PreviousPattern.ColumnWithObjects < TotalColumns)
{ {
while (PreviousPattern.ColumnHasObject(nextColumn)) while (PreviousPattern.ColumnHasObject(nextColumn))
nextColumn = Random.Next(RandomStart, TotalColumns); nextColumn = Random.Next(RandomStart, TotalColumns);
@ -193,7 +223,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
nextColumn = Random.Next(RandomStart, TotalColumns); nextColumn = Random.Next(RandomStart, TotalColumns);
lastColumn = nextColumn; lastColumn = nextColumn;
startTime += segmentDuration; startTime += SegmentDuration;
} }
return pattern; return pattern;
@ -223,7 +253,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
for (int i = 0; i <= spanCount; i++) for (int i = 0; i <= spanCount; i++)
{ {
addToPattern(pattern, column, startTime, startTime); addToPattern(pattern, column, startTime, startTime);
startTime += segmentDuration; startTime += SegmentDuration;
// Check if we're at the borders of the stage, and invert the pattern if so // Check if we're at the borders of the stage, and invert the pattern if so
if (increasing) if (increasing)
@ -284,7 +314,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
addToPattern(pattern, nextColumn, startTime, startTime); addToPattern(pattern, nextColumn, startTime, startTime);
nextColumn = Random.Next(RandomStart, TotalColumns); nextColumn = Random.Next(RandomStart, TotalColumns);
startTime += segmentDuration; startTime += SegmentDuration;
} }
return pattern; return pattern;
@ -331,7 +361,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
bool isDoubleSample(SampleInfo sample) => sample.Name == SampleInfo.HIT_CLAP || sample.Name == SampleInfo.HIT_FINISH; bool isDoubleSample(SampleInfo sample) => sample.Name == SampleInfo.HIT_CLAP || sample.Name == SampleInfo.HIT_FINISH;
bool canGenerateTwoNotes = (convertType & PatternType.LowProbability) == 0; bool canGenerateTwoNotes = !convertType.HasFlag(PatternType.LowProbability);
canGenerateTwoNotes &= HitObject.Samples.Any(isDoubleSample) || sampleInfoListAt(HitObject.StartTime).Any(isDoubleSample); canGenerateTwoNotes &= HitObject.Samples.Any(isDoubleSample) || sampleInfoListAt(HitObject.StartTime).Any(isDoubleSample);
if (canGenerateTwoNotes) if (canGenerateTwoNotes)
@ -361,7 +391,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
int columnRepeat = Math.Min(spanCount, TotalColumns); int columnRepeat = Math.Min(spanCount, TotalColumns);
int nextColumn = GetColumn((HitObject as IHasXPosition)?.X ?? 0, true); int nextColumn = GetColumn((HitObject as IHasXPosition)?.X ?? 0, true);
if ((convertType & PatternType.ForceNotStack) > 0 && PreviousPattern.ColumnWithObjects < TotalColumns) if (convertType.HasFlag(PatternType.ForceNotStack) && PreviousPattern.ColumnWithObjects < TotalColumns)
{ {
while (PreviousPattern.ColumnHasObject(nextColumn)) while (PreviousPattern.ColumnHasObject(nextColumn))
nextColumn = Random.Next(RandomStart, TotalColumns); nextColumn = Random.Next(RandomStart, TotalColumns);
@ -372,8 +402,8 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
while (pattern.ColumnHasObject(nextColumn)) while (pattern.ColumnHasObject(nextColumn))
nextColumn = Random.Next(RandomStart, TotalColumns); nextColumn = Random.Next(RandomStart, TotalColumns);
addToPattern(pattern, nextColumn, startTime, endTime); addToPattern(pattern, nextColumn, startTime, EndTime);
startTime += segmentDuration; startTime += SegmentDuration;
} }
return pattern; return pattern;
@ -395,14 +425,14 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
var pattern = new Pattern(); var pattern = new Pattern();
int holdColumn = GetColumn((HitObject as IHasXPosition)?.X ?? 0, true); int holdColumn = GetColumn((HitObject as IHasXPosition)?.X ?? 0, true);
if ((convertType & PatternType.ForceNotStack) > 0 && PreviousPattern.ColumnWithObjects < TotalColumns) if (convertType.HasFlag(PatternType.ForceNotStack) && PreviousPattern.ColumnWithObjects < TotalColumns)
{ {
while (PreviousPattern.ColumnHasObject(holdColumn)) while (PreviousPattern.ColumnHasObject(holdColumn))
holdColumn = Random.Next(RandomStart, TotalColumns); holdColumn = Random.Next(RandomStart, TotalColumns);
} }
// Create the hold note // Create the hold note
addToPattern(pattern, holdColumn, startTime, endTime); addToPattern(pattern, holdColumn, startTime, EndTime);
int nextColumn = Random.Next(RandomStart, TotalColumns); int nextColumn = Random.Next(RandomStart, TotalColumns);
int noteCount; int noteCount;
@ -434,7 +464,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
pattern.Add(rowPattern); pattern.Add(rowPattern);
rowPattern.Clear(); rowPattern.Clear();
startTime += segmentDuration; startTime += SegmentDuration;
} }
return pattern; return pattern;
@ -452,7 +482,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
if (curveData == null) if (curveData == null)
return HitObject.Samples; return HitObject.Samples;
double segmentTime = (endTime - HitObject.StartTime) / spanCount; double segmentTime = (EndTime - HitObject.StartTime) / spanCount;
int index = (int)(segmentTime == 0 ? 0 : (time - HitObject.StartTime) / segmentTime); int index = (int)(segmentTime == 0 ? 0 : (time - HitObject.StartTime) / segmentTime);
return curveData.RepeatSamples[index]; return curveData.RepeatSamples[index];

View File

@ -19,26 +19,33 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
public EndTimeObjectPatternGenerator(FastRandom random, HitObject hitObject, ManiaBeatmap beatmap, IBeatmap originalBeatmap) public EndTimeObjectPatternGenerator(FastRandom random, HitObject hitObject, ManiaBeatmap beatmap, IBeatmap originalBeatmap)
: base(random, hitObject, beatmap, new Pattern(), originalBeatmap) : base(random, hitObject, beatmap, new Pattern(), originalBeatmap)
{ {
var endtimeData = HitObject as IHasEndTime; endTime = (HitObject as IHasEndTime)?.EndTime ?? 0;
endTime = endtimeData?.EndTime ?? 0;
} }
public override Pattern Generate() public override IEnumerable<Pattern> Generate()
{
yield return generate();
}
private Pattern generate()
{ {
var pattern = new Pattern(); var pattern = new Pattern();
bool generateHold = endTime - HitObject.StartTime >= 100; bool generateHold = endTime - HitObject.StartTime >= 100;
if (TotalColumns == 8) switch (TotalColumns)
{ {
if (HitObject.Samples.Any(s => s.Name == SampleInfo.HIT_FINISH) && endTime - HitObject.StartTime < 1000) case 8 when HitObject.Samples.Any(s => s.Name == SampleInfo.HIT_FINISH) && endTime - HitObject.StartTime < 1000:
addToPattern(pattern, 0, generateHold); addToPattern(pattern, 0, generateHold);
else break;
case 8:
addToPattern(pattern, getNextRandomColumn(RandomStart), generateHold); addToPattern(pattern, getNextRandomColumn(RandomStart), generateHold);
} break;
else if (TotalColumns > 0) default:
if (TotalColumns > 0)
addToPattern(pattern, getNextRandomColumn(0), generateHold); addToPattern(pattern, getNextRandomColumn(0), generateHold);
break;
}
return pattern; return pattern;
} }

View File

@ -2,6 +2,7 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System; using System;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using OpenTK; using OpenTK;
using osu.Game.Audio; using osu.Game.Audio;
@ -20,7 +21,8 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
private readonly PatternType convertType; private readonly PatternType convertType;
public HitObjectPatternGenerator(FastRandom random, HitObject hitObject, ManiaBeatmap beatmap, Pattern previousPattern, double previousTime, Vector2 previousPosition, double density, PatternType lastStair, IBeatmap originalBeatmap) public HitObjectPatternGenerator(FastRandom random, HitObject hitObject, ManiaBeatmap beatmap, Pattern previousPattern, double previousTime, Vector2 previousPosition, double density,
PatternType lastStair, IBeatmap originalBeatmap)
: base(random, hitObject, beatmap, previousPattern, originalBeatmap) : base(random, hitObject, beatmap, previousPattern, originalBeatmap)
{ {
if (previousTime > hitObject.StartTime) throw new ArgumentOutOfRangeException(nameof(previousTime)); if (previousTime > hitObject.StartTime) throw new ArgumentOutOfRangeException(nameof(previousTime));
@ -78,31 +80,37 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
else else
convertType |= PatternType.LowProbability; convertType |= PatternType.LowProbability;
if ((convertType & PatternType.KeepSingle) == 0) if (!convertType.HasFlag(PatternType.KeepSingle))
{ {
if (HitObject.Samples.Any(s => s.Name == SampleInfo.HIT_FINISH) && TotalColumns != 8) if (HitObject.Samples.Any(s => s.Name == SampleInfo.HIT_FINISH) && TotalColumns != 8)
convertType |= PatternType.Mirror; convertType |= PatternType.Mirror;
else else if (HitObject.Samples.Any(s => s.Name == SampleInfo.HIT_CLAP))
convertType |= PatternType.Gathered; convertType |= PatternType.Gathered;
} }
} }
public override Pattern Generate() public override IEnumerable<Pattern> Generate()
{
yield return generate();
}
private Pattern generate()
{
var pattern = new Pattern();
try
{ {
if (TotalColumns == 1) if (TotalColumns == 1)
{ {
var pattern = new Pattern();
addToPattern(pattern, 0); addToPattern(pattern, 0);
return pattern; return pattern;
} }
int lastColumn = PreviousPattern.HitObjects.FirstOrDefault()?.Column ?? 0; int lastColumn = PreviousPattern.HitObjects.FirstOrDefault()?.Column ?? 0;
if ((convertType & PatternType.Reverse) > 0 && PreviousPattern.HitObjects.Any()) if (convertType.HasFlag(PatternType.Reverse) && PreviousPattern.HitObjects.Any())
{ {
// Generate a new pattern by copying the last hit objects in reverse-column order // Generate a new pattern by copying the last hit objects in reverse-column order
var pattern = new Pattern();
for (int i = RandomStart; i < TotalColumns; i++) for (int i = RandomStart; i < TotalColumns; i++)
if (PreviousPattern.ColumnHasObject(i)) if (PreviousPattern.ColumnHasObject(i))
addToPattern(pattern, RandomStart + TotalColumns - i - 1); addToPattern(pattern, RandomStart + TotalColumns - i - 1);
@ -110,26 +118,22 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
return pattern; return pattern;
} }
if ((convertType & PatternType.Cycle) > 0 && PreviousPattern.HitObjects.Count() == 1 if (convertType.HasFlag(PatternType.Cycle) && PreviousPattern.HitObjects.Count() == 1
// If we convert to 7K + 1, let's not overload the special key // If we convert to 7K + 1, let's not overload the special key
&& (TotalColumns != 8 || lastColumn != 0) && (TotalColumns != 8 || lastColumn != 0)
// Make sure the last column was not the centre column // Make sure the last column was not the centre column
&& (TotalColumns % 2 == 0 || lastColumn != TotalColumns / 2)) && (TotalColumns % 2 == 0 || lastColumn != TotalColumns / 2))
{ {
// Generate a new pattern by cycling backwards (similar to Reverse but for only one hit object) // Generate a new pattern by cycling backwards (similar to Reverse but for only one hit object)
var pattern = new Pattern();
int column = RandomStart + TotalColumns - lastColumn - 1; int column = RandomStart + TotalColumns - lastColumn - 1;
addToPattern(pattern, column); addToPattern(pattern, column);
return pattern; return pattern;
} }
if ((convertType & PatternType.ForceStack) > 0 && PreviousPattern.HitObjects.Any()) if (convertType.HasFlag(PatternType.ForceStack) && PreviousPattern.HitObjects.Any())
{ {
// Generate a new pattern by placing on the already filled columns // Generate a new pattern by placing on the already filled columns
var pattern = new Pattern();
for (int i = RandomStart; i < TotalColumns; i++) for (int i = RandomStart; i < TotalColumns; i++)
if (PreviousPattern.ColumnHasObject(i)) if (PreviousPattern.ColumnHasObject(i))
addToPattern(pattern, i); addToPattern(pattern, i);
@ -137,72 +141,76 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
return pattern; return pattern;
} }
if ((convertType & PatternType.Stair) > 0 && PreviousPattern.HitObjects.Count() == 1) if (PreviousPattern.HitObjects.Count() == 1)
{
if (convertType.HasFlag(PatternType.Stair))
{ {
// Generate a new pattern by placing on the next column, cycling back to the start if there is no "next" // Generate a new pattern by placing on the next column, cycling back to the start if there is no "next"
var pattern = new Pattern();
int targetColumn = lastColumn + 1; int targetColumn = lastColumn + 1;
if (targetColumn == TotalColumns) if (targetColumn == TotalColumns)
{
targetColumn = RandomStart; targetColumn = RandomStart;
StairType = PatternType.ReverseStair;
}
addToPattern(pattern, targetColumn); addToPattern(pattern, targetColumn);
return pattern; return pattern;
} }
if ((convertType & PatternType.ReverseStair) > 0 && PreviousPattern.HitObjects.Count() == 1) if (convertType.HasFlag(PatternType.ReverseStair))
{ {
// Generate a new pattern by placing on the previous column, cycling back to the end if there is no "previous" // Generate a new pattern by placing on the previous column, cycling back to the end if there is no "previous"
var pattern = new Pattern();
int targetColumn = lastColumn - 1; int targetColumn = lastColumn - 1;
if (targetColumn == RandomStart - 1) if (targetColumn == RandomStart - 1)
{
targetColumn = TotalColumns - 1; targetColumn = TotalColumns - 1;
StairType = PatternType.Stair;
}
addToPattern(pattern, targetColumn); addToPattern(pattern, targetColumn);
return pattern; return pattern;
} }
}
if ((convertType & PatternType.KeepSingle) > 0) if (convertType.HasFlag(PatternType.KeepSingle))
return generateRandomNotes(1); return pattern = generateRandomNotes(1);
if ((convertType & PatternType.Mirror) > 0) if (convertType.HasFlag(PatternType.Mirror))
{ {
if (ConversionDifficulty > 6.5) if (ConversionDifficulty > 6.5)
return generateRandomPatternWithMirrored(0.12, 0.38, 0.12); return pattern = generateRandomPatternWithMirrored(0.12, 0.38, 0.12);
if (ConversionDifficulty > 4) if (ConversionDifficulty > 4)
return generateRandomPatternWithMirrored(0.12, 0.17, 0); return pattern = generateRandomPatternWithMirrored(0.12, 0.17, 0);
return generateRandomPatternWithMirrored(0.12, 0, 0); return pattern = generateRandomPatternWithMirrored(0.12, 0, 0);
} }
if (ConversionDifficulty > 6.5) if (ConversionDifficulty > 6.5)
{ {
if ((convertType & PatternType.LowProbability) > 0) if (convertType.HasFlag(PatternType.LowProbability))
return generateRandomPattern(0.78, 0.42, 0, 0); return pattern = generateRandomPattern(0.78, 0.42, 0, 0);
return generateRandomPattern(1, 0.62, 0, 0); return pattern = generateRandomPattern(1, 0.62, 0, 0);
} }
if (ConversionDifficulty > 4) if (ConversionDifficulty > 4)
{ {
if ((convertType & PatternType.LowProbability) > 0) if (convertType.HasFlag(PatternType.LowProbability))
return generateRandomPattern(0.35, 0.08, 0, 0); return pattern = generateRandomPattern(0.35, 0.08, 0, 0);
return generateRandomPattern(0.52, 0.15, 0, 0); return pattern = generateRandomPattern(0.52, 0.15, 0, 0);
} }
if (ConversionDifficulty > 2) if (ConversionDifficulty > 2)
{ {
if ((convertType & PatternType.LowProbability) > 0) if (convertType.HasFlag(PatternType.LowProbability))
return generateRandomPattern(0.18, 0, 0, 0); return pattern = generateRandomPattern(0.18, 0, 0, 0);
return generateRandomPattern(0.45, 0, 0, 0); return pattern = generateRandomPattern(0.45, 0, 0, 0);
} }
return generateRandomPattern(0, 0, 0, 0); return pattern = generateRandomPattern(0, 0, 0, 0);
}
finally
{
foreach (var obj in pattern.HitObjects)
{
if (convertType.HasFlag(PatternType.Stair) && obj.Column == TotalColumns - 1)
StairType = PatternType.ReverseStair;
if (convertType.HasFlag(PatternType.ReverseStair) && obj.Column == RandomStart)
StairType = PatternType.Stair;
}
}
} }
/// <summary> /// <summary>
@ -218,7 +226,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
{ {
var pattern = new Pattern(); var pattern = new Pattern();
bool allowStacking = (convertType & PatternType.ForceNotStack) == 0; bool allowStacking = !convertType.HasFlag(PatternType.ForceNotStack);
if (!allowStacking) if (!allowStacking)
noteCount = Math.Min(noteCount, TotalColumns - RandomStart - PreviousPattern.ColumnWithObjects); noteCount = Math.Min(noteCount, TotalColumns - RandomStart - PreviousPattern.ColumnWithObjects);
@ -228,7 +236,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
{ {
while (pattern.ColumnHasObject(nextColumn) || PreviousPattern.ColumnHasObject(nextColumn) && !allowStacking) while (pattern.ColumnHasObject(nextColumn) || PreviousPattern.ColumnHasObject(nextColumn) && !allowStacking)
{ {
if ((convertType & PatternType.Gathered) > 0) if (convertType.HasFlag(PatternType.Gathered))
{ {
nextColumn++; nextColumn++;
if (nextColumn == TotalColumns) if (nextColumn == TotalColumns)
@ -360,7 +368,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
{ {
addToCentre = false; addToCentre = false;
if ((convertType & PatternType.ForceNotStack) > 0) if (convertType.HasFlag(PatternType.ForceNotStack))
return getRandomNoteCount(1 / 2f + p2 / 2, p2, (p2 + p3) / 2, p3); return getRandomNoteCount(1 / 2f + p2 / 2, p2, (p2 + p3) / 2, p3);
switch (TotalColumns) switch (TotalColumns)

View File

@ -103,17 +103,14 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
HitObject lastObject = OriginalBeatmap.HitObjects.LastOrDefault(); HitObject lastObject = OriginalBeatmap.HitObjects.LastOrDefault();
HitObject firstObject = OriginalBeatmap.HitObjects.FirstOrDefault(); HitObject firstObject = OriginalBeatmap.HitObjects.FirstOrDefault();
double drainTime = (lastObject?.StartTime ?? 0) - (firstObject?.StartTime ?? 0); // Drain time in seconds
drainTime -= OriginalBeatmap.TotalBreakTime; int drainTime = (int)(((lastObject?.StartTime ?? 0) - (firstObject?.StartTime ?? 0) - OriginalBeatmap.TotalBreakTime) / 1000);
if (drainTime == 0) if (drainTime == 0)
drainTime = 10000000; drainTime = 10000;
// We need this in seconds
drainTime /= 1000;
BeatmapDifficulty difficulty = OriginalBeatmap.BeatmapInfo.BaseDifficulty; BeatmapDifficulty difficulty = OriginalBeatmap.BeatmapInfo.BaseDifficulty;
conversionDifficulty = ((difficulty.DrainRate + MathHelper.Clamp(difficulty.ApproachRate, 4, 7)) / 1.5 + OriginalBeatmap.HitObjects.Count() / drainTime * 9f) / 38f * 5f / 1.15; conversionDifficulty = ((difficulty.DrainRate + MathHelper.Clamp(difficulty.ApproachRate, 4, 7)) / 1.5 + (double)OriginalBeatmap.HitObjects.Count() / drainTime * 9f) / 38f * 5f / 1.15;
conversionDifficulty = Math.Min(conversionDifficulty.Value, 12); conversionDifficulty = Math.Min(conversionDifficulty.Value, 12);
return conversionDifficulty.Value; return conversionDifficulty.Value;

View File

@ -2,6 +2,7 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System; using System;
using System.Collections.Generic;
using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects;
namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns
@ -42,9 +43,9 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns
} }
/// <summary> /// <summary>
/// Generates the pattern for <see cref="HitObject"/>, filled with hit objects. /// Generates the patterns for <see cref="HitObject"/>, each filled with hit objects.
/// </summary> /// </summary>
/// <returns>The <see cref="Pattern"/> containing the hit objects.</returns> /// <returns>The <see cref="Pattern"/>s containing the hit objects.</returns>
public abstract Pattern Generate(); public abstract IEnumerable<Pattern> Generate();
} }
} }

View File

@ -19,14 +19,14 @@ namespace osu.Game.Rulesets.Mania
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(ManiaConfigManager config) private void load()
{ {
Children = new Drawable[] Children = new Drawable[]
{ {
new SettingsEnumDropdown<ManiaScrollingDirection> new SettingsEnumDropdown<ManiaScrollingDirection>
{ {
LabelText = "Scrolling direction", LabelText = "Scrolling direction",
Bindable = config.GetBindable<ManiaScrollingDirection>(ManiaSetting.ScrollDirection) Bindable = ((ManiaConfigManager)Config).GetBindable<ManiaScrollingDirection>(ManiaSetting.ScrollDirection)
} }
}; };
} }

View File

@ -15,11 +15,15 @@ namespace osu.Game.Rulesets.Mania.MathUtils
private const uint y = 842502087; private const uint y = 842502087;
private const uint z = 3579807591; private const uint z = 3579807591;
private const uint w = 273326509; private const uint w = 273326509;
private uint _x, _y = y, _z = z, _w = w;
internal uint X { get; private set; }
internal uint Y { get; private set; } = y;
internal uint Z { get; private set; } = z;
internal uint W { get; private set; } = w;
public FastRandom(int seed) public FastRandom(int seed)
{ {
_x = (uint)seed; X = (uint)seed;
} }
public FastRandom() public FastRandom()
@ -33,11 +37,11 @@ namespace osu.Game.Rulesets.Mania.MathUtils
/// <returns>The random value.</returns> /// <returns>The random value.</returns>
public uint NextUInt() public uint NextUInt()
{ {
uint t = _x ^ _x << 11; uint t = X ^ X << 11;
_x = _y; X = Y;
_y = _z; Y = Z;
_z = _w; Z = W;
return _w = _w ^ _w >> 19 ^ t ^ t >> 8; return W = W ^ W >> 19 ^ t ^ t >> 8;
} }
/// <summary> /// <summary>

View File

@ -56,10 +56,15 @@ namespace osu.Game.Rulesets.Mania.Replays
{ {
foreach (var point in group) foreach (var point in group)
{ {
if (point is HitPoint) switch (point)
{
case HitPoint _:
actions.Add(columnActions[point.Column]); actions.Add(columnActions[point.Column]);
if (point is ReleasePoint) break;
case ReleasePoint _:
actions.Remove(columnActions[point.Column]); actions.Remove(columnActions[point.Column]);
break;
}
} }
Replay.Frames.Add(new ManiaReplayFrame(group.First().Time, actions.ToArray())); Replay.Frames.Add(new ManiaReplayFrame(group.First().Time, actions.ToArray()));

View File

@ -3,7 +3,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using osu.Framework.Input; using osu.Framework.Input.StateChanges;
using osu.Game.Rulesets.Replays; using osu.Game.Rulesets.Replays;
namespace osu.Game.Rulesets.Mania.Replays namespace osu.Game.Rulesets.Mania.Replays

View File

@ -1,103 +1,132 @@
{ {
"Mappings": [{ "Mappings": [{
"StartTime": 500, "RandomW": 2659373485,
"RandomX": 3579807591,
"RandomY": 273326509,
"RandomZ": 272969173,
"StartTime": 500.0,
"Objects": [{ "Objects": [{
"StartTime": 500, "StartTime": 500.0,
"EndTime": 2500, "EndTime": 2500.0,
"Column": 0 "Column": 0
}, }, {
{ "StartTime": 1500.0,
"StartTime": 1500, "EndTime": 2500.0,
"EndTime": 2500,
"Column": 1 "Column": 1
} }]
] }, {
}, "RandomW": 3083803045,
{ "RandomX": 273326509,
"StartTime": 3000, "RandomY": 272969173,
"RandomZ": 2659373485,
"StartTime": 3000.0,
"Objects": [{ "Objects": [{
"StartTime": 3000, "StartTime": 3000.0,
"EndTime": 4000, "EndTime": 4000.0,
"Column": 2 "Column": 2
}] }]
}, }, {
{ "RandomW": 4073554232,
"StartTime": 4500, "RandomX": 272969173,
"RandomY": 2659373485,
"RandomZ": 3083803045,
"StartTime": 4500.0,
"Objects": [{ "Objects": [{
"StartTime": 4500, "StartTime": 4500.0,
"EndTime": 5500, "EndTime": 5500.0,
"Column": 4 "Column": 4
}] }]
}, }, {
{ "RandomW": 3420401969,
"StartTime": 6000, "RandomX": 2659373485,
"RandomY": 3083803045,
"RandomZ": 4073554232,
"StartTime": 6000.0,
"Objects": [{ "Objects": [{
"StartTime": 6000, "StartTime": 6000.0,
"EndTime": 6500, "EndTime": 6500.0,
"Column": 2 "Column": 2
}] }]
}, }, {
{ "RandomW": 1129881182,
"StartTime": 7000, "RandomX": 3083803045,
"RandomY": 4073554232,
"RandomZ": 3420401969,
"StartTime": 7000.0,
"Objects": [{ "Objects": [{
"StartTime": 7000, "StartTime": 7000.0,
"EndTime": 8000, "EndTime": 8000.0,
"Column": 2 "Column": 2
}] }]
}, }, {
{ "RandomW": 315568458,
"StartTime": 8500, "RandomX": 3420401969,
"RandomY": 1129881182,
"RandomZ": 2358617505,
"StartTime": 8500.0,
"Objects": [{ "Objects": [{
"StartTime": 8500, "StartTime": 8500.0,
"EndTime": 11000, "EndTime": 11000.0,
"Column": 0 "Column": 0
}] }]
}, }, {
{ "RandomW": 548134043,
"StartTime": 11500, "RandomX": 1129881182,
"RandomY": 2358617505,
"RandomZ": 315568458,
"StartTime": 11500.0,
"Objects": [{ "Objects": [{
"StartTime": 11500, "StartTime": 11500.0,
"EndTime": 12000, "EndTime": 12000.0,
"Column": 1 "Column": 1
}] }]
}, }, {
{ "RandomW": 3979422122,
"StartTime": 12500, "RandomX": 548134043,
"RandomY": 2810584254,
"RandomZ": 2250186050,
"StartTime": 12500.0,
"Objects": [{ "Objects": [{
"StartTime": 12500, "StartTime": 12500.0,
"EndTime": 16500, "EndTime": 16500.0,
"Column": 4 "Column": 4
}] }]
}, }, {
{ "RandomW": 2466283411,
"StartTime": 17000, "RandomX": 2810584254,
"RandomY": 2250186050,
"RandomZ": 3979422122,
"StartTime": 17000.0,
"Objects": [{ "Objects": [{
"StartTime": 17000, "StartTime": 17000.0,
"EndTime": 18000, "EndTime": 18000.0,
"Column": 2 "Column": 2
}] }]
}, }, {
{ "RandomW": 83157665,
"StartTime": 18500, "RandomX": 2250186050,
"RandomY": 3979422122,
"RandomZ": 2466283411,
"StartTime": 18500.0,
"Objects": [{ "Objects": [{
"StartTime": 18500, "StartTime": 18500.0,
"EndTime": 19450, "EndTime": 19450.0,
"Column": 0 "Column": 0
}] }]
}, }, {
{ "RandomW": 2383087700,
"StartTime": 19875, "RandomX": 83157665,
"RandomY": 2055150192,
"RandomZ": 510071020,
"StartTime": 19875.0,
"Objects": [{ "Objects": [{
"StartTime": 19875, "StartTime": 19875.0,
"EndTime": 23875, "EndTime": 23875.0,
"Column": 1 "Column": 1
}, }, {
{ "StartTime": 19875.0,
"StartTime": 19875, "EndTime": 23875.0,
"EndTime": 23875,
"Column": 0 "Column": 0
} }]
] }]
}
]
} }

View File

@ -32,8 +32,7 @@ namespace osu.Game.Rulesets.Mania.UI
protected override Container<Drawable> Content => hitObjectArea; protected override Container<Drawable> Content => hitObjectArea;
public Column(ScrollingDirection direction) public Column()
: base(direction)
{ {
RelativeSizeAxes = Axes.Y; RelativeSizeAxes = Axes.Y;
Width = column_width; Width = column_width;
@ -118,9 +117,9 @@ namespace osu.Game.Rulesets.Mania.UI
} }
} }
protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent) protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
{ {
var dependencies = new DependencyContainer(base.CreateLocalDependencies(parent)); var dependencies = new DependencyContainer(base.CreateChildDependencies(parent));
dependencies.CacheAs<IBindable<ManiaAction>>(Action); dependencies.CacheAs<IBindable<ManiaAction>>(Action);
return dependencies; return dependencies;
} }
@ -139,7 +138,7 @@ namespace osu.Game.Rulesets.Mania.UI
internal void OnJudgement(DrawableHitObject judgedObject, Judgement judgement) internal void OnJudgement(DrawableHitObject judgedObject, Judgement judgement)
{ {
if (!judgement.IsHit || !judgedObject.DisplayJudgement) if (!judgement.IsHit || !judgedObject.DisplayJudgement || !DisplayJudgements)
return; return;
explosionContainer.Add(new HitExplosion(judgedObject) explosionContainer.Add(new HitExplosion(judgedObject)

View File

@ -8,11 +8,9 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Mania.Configuration; using osu.Game.Rulesets.Mania.Configuration;
using osu.Game.Rulesets.UI.Scrolling;
namespace osu.Game.Rulesets.Mania.UI namespace osu.Game.Rulesets.Mania.UI
{ {
@ -21,8 +19,7 @@ namespace osu.Game.Rulesets.Mania.UI
public List<Column> Columns => stages.SelectMany(x => x.Columns).ToList(); public List<Column> Columns => stages.SelectMany(x => x.Columns).ToList();
private readonly List<ManiaStage> stages = new List<ManiaStage>(); private readonly List<ManiaStage> stages = new List<ManiaStage>();
public ManiaPlayfield(ScrollingDirection direction, List<StageDefinition> stageDefinitions) public ManiaPlayfield(List<StageDefinition> stageDefinitions)
: base(direction)
{ {
if (stageDefinitions == null) if (stageDefinitions == null)
throw new ArgumentNullException(nameof(stageDefinitions)); throw new ArgumentNullException(nameof(stageDefinitions));
@ -42,7 +39,7 @@ namespace osu.Game.Rulesets.Mania.UI
int firstColumnIndex = 0; int firstColumnIndex = 0;
for (int i = 0; i < stageDefinitions.Count; i++) for (int i = 0; i < stageDefinitions.Count; i++)
{ {
var newStage = new ManiaStage(direction, firstColumnIndex, stageDefinitions[i], ref normalColumnAction, ref specialColumnAction); var newStage = new ManiaStage(firstColumnIndex, stageDefinitions[i], ref normalColumnAction, ref specialColumnAction);
newStage.VisibleTimeRange.BindTo(VisibleTimeRange); newStage.VisibleTimeRange.BindTo(VisibleTimeRange);
playfieldGrid.Content[0][i] = newStage; playfieldGrid.Content[0][i] = newStage;
@ -76,10 +73,5 @@ namespace osu.Game.Rulesets.Mania.UI
{ {
maniaConfig.BindWith(ManiaSetting.ScrollTime, VisibleTimeRange); maniaConfig.BindWith(ManiaSetting.ScrollTime, VisibleTimeRange);
} }
internal void OnJudgement(DrawableHitObject judgedObject, Judgement judgement)
{
getStageByColumn(((ManiaHitObject)judgedObject.HitObject).Column).OnJudgement(judgedObject, judgement);
}
} }
} }

View File

@ -70,24 +70,24 @@ namespace osu.Game.Rulesets.Mania.UI
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(ManiaConfigManager config) private void load()
{ {
BarLines.ForEach(Playfield.Add); BarLines.ForEach(Playfield.Add);
config.BindWith(ManiaSetting.ScrollDirection, configDirection); ((ManiaConfigManager)Config).BindWith(ManiaSetting.ScrollDirection, configDirection);
configDirection.BindValueChanged(d => scrollingInfo.Direction.Value = (ScrollingDirection)d, true); configDirection.BindValueChanged(d => scrollingInfo.Direction.Value = (ScrollingDirection)d, true);
} }
private DependencyContainer dependencies; private DependencyContainer dependencies;
protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent) protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
{ {
dependencies = new DependencyContainer(base.CreateLocalDependencies(parent)); dependencies = new DependencyContainer(base.CreateChildDependencies(parent));
dependencies.CacheAs<IScrollingInfo>(scrollingInfo = new ScrollingInfo()); dependencies.CacheAs<IScrollingInfo>(scrollingInfo = new ScrollingInfo());
return dependencies; return dependencies;
} }
protected sealed override Playfield CreatePlayfield() => new ManiaPlayfield(scrollingInfo.Direction, Beatmap.Stages) protected sealed override Playfield CreatePlayfield() => new ManiaPlayfield(Beatmap.Stages)
{ {
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
Origin = Anchor.Centre, Origin = Anchor.Centre,

View File

@ -11,11 +11,6 @@ namespace osu.Game.Rulesets.Mania.UI
{ {
private readonly IBindable<ScrollingDirection> direction = new Bindable<ScrollingDirection>(); private readonly IBindable<ScrollingDirection> direction = new Bindable<ScrollingDirection>();
public ManiaScrollingPlayfield(ScrollingDirection direction)
: base(direction)
{
}
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(IScrollingInfo scrollingInfo) private void load(IScrollingInfo scrollingInfo)
{ {

View File

@ -43,8 +43,7 @@ namespace osu.Game.Rulesets.Mania.UI
private readonly int firstColumnIndex; private readonly int firstColumnIndex;
public ManiaStage(ScrollingDirection direction, int firstColumnIndex, StageDefinition definition, ref ManiaAction normalColumnStartAction, ref ManiaAction specialColumnStartAction) public ManiaStage(int firstColumnIndex, StageDefinition definition, ref ManiaAction normalColumnStartAction, ref ManiaAction specialColumnStartAction)
: base(direction)
{ {
this.firstColumnIndex = firstColumnIndex; this.firstColumnIndex = firstColumnIndex;
@ -124,7 +123,7 @@ namespace osu.Game.Rulesets.Mania.UI
for (int i = 0; i < definition.Columns; i++) for (int i = 0; i < definition.Columns; i++)
{ {
var isSpecial = definition.IsSpecialColumn(i); var isSpecial = definition.IsSpecialColumn(i);
var column = new Column(direction) var column = new Column
{ {
IsSpecial = isSpecial, IsSpecial = isSpecial,
Action = { Value = isSpecial ? specialColumnStartAction++ : normalColumnStartAction++ } Action = { Value = isSpecial ? specialColumnStartAction++ : normalColumnStartAction++ }
@ -164,7 +163,7 @@ namespace osu.Game.Rulesets.Mania.UI
internal void OnJudgement(DrawableHitObject judgedObject, Judgement judgement) internal void OnJudgement(DrawableHitObject judgedObject, Judgement judgement)
{ {
if (!judgedObject.DisplayJudgement) if (!judgedObject.DisplayJudgement || !DisplayJudgements)
return; return;
judgements.Clear(); judgements.Clear();

View File

@ -54,7 +54,7 @@ namespace osu.Game.Rulesets.Osu.Tests
public struct ConvertValue : IEquatable<ConvertValue> public struct ConvertValue : IEquatable<ConvertValue>
{ {
/// <summary> /// <summary>
/// A sane value to account for osu!stable using ints everwhere. /// A sane value to account for osu!stable using <see cref="int"/>s everywhere.
/// </summary> /// </summary>
private const double conversion_lenience = 2; private const double conversion_lenience = 2;

View File

@ -61,19 +61,19 @@ namespace osu.Game.Rulesets.Osu.Difficulty
double speedRating = Math.Sqrt(skills[1].DifficultyValue()) * difficulty_multiplier; double speedRating = Math.Sqrt(skills[1].DifficultyValue()) * difficulty_multiplier;
double starRating = aimRating + speedRating + Math.Abs(aimRating - speedRating) / 2; double starRating = aimRating + speedRating + Math.Abs(aimRating - speedRating) / 2;
// Todo: These int casts are temporary to achieve 1:1 results with osu!stable, and should be remoevd in the future // Todo: These int casts are temporary to achieve 1:1 results with osu!stable, and should be removed in the future
double hitWindowGreat = (int)(beatmap.HitObjects.First().HitWindows.Great / 2) / timeRate; double hitWindowGreat = (int)(beatmap.HitObjects.First().HitWindows.Great / 2) / timeRate;
double preEmpt = (int)BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.ApproachRate, 1800, 1200, 450) / timeRate; double preempt = (int)BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.ApproachRate, 1800, 1200, 450) / timeRate;
int maxCombo = beatmap.HitObjects.Count(); int maxCombo = beatmap.HitObjects.Count();
// Add the ticks + tail of the slider. 1 is subtracted because the "headcircle" would be counted twice (once for the slider itself in the line above) // Add the ticks + tail of the slider. 1 is subtracted because the head circle would be counted twice (once for the slider itself in the line above)
maxCombo += beatmap.HitObjects.OfType<Slider>().Sum(s => s.NestedHitObjects.Count - 1); maxCombo += beatmap.HitObjects.OfType<Slider>().Sum(s => s.NestedHitObjects.Count - 1);
return new OsuDifficultyAttributes(mods, starRating) return new OsuDifficultyAttributes(mods, starRating)
{ {
AimStrain = aimRating, AimStrain = aimRating,
SpeedStrain = speedRating, SpeedStrain = speedRating,
ApproachRate = preEmpt > 1200 ? (1800 - preEmpt) / 120 : (1200 - preEmpt) / 150 + 5, ApproachRate = preempt > 1200 ? (1800 - preempt) / 120 : (1200 - preempt) / 150 + 5,
OverallDifficulty = (80 - hitWindowGreat) / 6, OverallDifficulty = (80 - hitWindowGreat) / 6,
MaxCombo = maxCombo MaxCombo = maxCombo
}; };

View File

@ -1,12 +0,0 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Rulesets.Osu.UI;
namespace osu.Game.Rulesets.Osu.Edit
{
public class OsuEditPlayfield : OsuPlayfield
{
protected override bool DisplayJudgements => false;
}
}

View File

@ -4,7 +4,6 @@
using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Cursor;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Rulesets.Osu.UI; using osu.Game.Rulesets.Osu.UI;
using osu.Game.Rulesets.UI;
using OpenTK; using OpenTK;
namespace osu.Game.Rulesets.Osu.Edit namespace osu.Game.Rulesets.Osu.Edit
@ -16,8 +15,6 @@ namespace osu.Game.Rulesets.Osu.Edit
{ {
} }
protected override Playfield CreatePlayfield() => new OsuEditPlayfield();
protected override Vector2 PlayfieldArea => Vector2.One; protected override Vector2 PlayfieldArea => Vector2.One;
protected override CursorContainer CreateCursor() => null; protected override CursorContainer CreateCursor() => null;

View File

@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Osu.Mods
public override void ApplyToDrawableHitObjects(IEnumerable<DrawableHitObject> drawables) public override void ApplyToDrawableHitObjects(IEnumerable<DrawableHitObject> drawables)
{ {
void adjustFadeIn(OsuHitObject h) => h.TimeFadein = h.TimePreempt * fade_in_duration_multiplier; void adjustFadeIn(OsuHitObject h) => h.TimeFadeIn = h.TimePreempt * fade_in_duration_multiplier;
foreach (var d in drawables.OfType<DrawableOsuHitObject>()) foreach (var d in drawables.OfType<DrawableOsuHitObject>())
{ {
@ -41,7 +41,7 @@ namespace osu.Game.Rulesets.Osu.Mods
var h = d.HitObject; var h = d.HitObject;
var fadeOutStartTime = h.StartTime - h.TimePreempt + h.TimeFadein; var fadeOutStartTime = h.StartTime - h.TimePreempt + h.TimeFadeIn;
var fadeOutDuration = h.TimePreempt * fade_out_duration_multiplier; var fadeOutDuration = h.TimePreempt * fade_out_duration_multiplier;
// new duration from completed fade in to end (before fading out) // new duration from completed fade in to end (before fading out)

View File

@ -96,12 +96,12 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
using (fp.BeginAbsoluteSequence(fadeInTime)) using (fp.BeginAbsoluteSequence(fadeInTime))
{ {
fp.FadeIn(currHitObject.TimeFadein); fp.FadeIn(currHitObject.TimeFadeIn);
fp.ScaleTo(1, currHitObject.TimeFadein, Easing.Out); fp.ScaleTo(1, currHitObject.TimeFadeIn, Easing.Out);
fp.MoveTo(pointEndPosition, currHitObject.TimeFadein, Easing.Out); fp.MoveTo(pointEndPosition, currHitObject.TimeFadeIn, Easing.Out);
fp.Delay(fadeOutTime - fadeInTime).FadeOut(currHitObject.TimeFadein); fp.Delay(fadeOutTime - fadeInTime).FadeOut(currHitObject.TimeFadeIn);
} }
fp.Expire(true); fp.Expire(true);

View File

@ -100,7 +100,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
{ {
base.UpdatePreemptState(); base.UpdatePreemptState();
ApproachCircle.FadeIn(Math.Min(HitObject.TimeFadein * 2, HitObject.TimePreempt)); ApproachCircle.FadeIn(Math.Min(HitObject.TimeFadeIn * 2, HitObject.TimePreempt));
ApproachCircle.ScaleTo(1.1f, HitObject.TimePreempt); ApproachCircle.ScaleTo(1.1f, HitObject.TimePreempt);
} }

View File

@ -45,7 +45,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
AccentColour = skin.GetValue<SkinConfiguration, Color4>(s => s.ComboColours.Count > 0 ? s.ComboColours[combo.ComboIndex % s.ComboColours.Count] : (Color4?)null) ?? Color4.White; AccentColour = skin.GetValue<SkinConfiguration, Color4>(s => s.ComboColours.Count > 0 ? s.ComboColours[combo.ComboIndex % s.ComboColours.Count] : (Color4?)null) ?? Color4.White;
} }
protected virtual void UpdatePreemptState() => this.FadeIn(HitObject.TimeFadein); protected virtual void UpdatePreemptState() => this.FadeIn(HitObject.TimeFadeIn);
protected virtual void UpdateCurrentState(ArmedState state) protected virtual void UpdateCurrentState(ArmedState state)
{ {

View File

@ -167,7 +167,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
{ {
Disc.Tracking = OsuActionInputManager.PressedActions.Any(x => x == OsuAction.LeftButton || x == OsuAction.RightButton); Disc.Tracking = OsuActionInputManager.PressedActions.Any(x => x == OsuAction.LeftButton || x == OsuAction.RightButton);
if (!spmCounter.IsPresent && Disc.Tracking) if (!spmCounter.IsPresent && Disc.Tracking)
spmCounter.FadeIn(HitObject.TimeFadein); spmCounter.FadeIn(HitObject.TimeFadeIn);
base.Update(); base.Update();
} }

View File

@ -5,7 +5,8 @@ using System.Linq;
using osu.Framework.Graphics; 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.Input; using osu.Framework.Input.EventArgs;
using osu.Framework.Input.States;
using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Objects.Types;
using OpenTK; using OpenTK;
using OpenTK.Graphics; using OpenTK.Graphics;

View File

@ -212,7 +212,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
var spanProgress = slider.ProgressAt(completionProgress); var spanProgress = slider.ProgressAt(completionProgress);
double start = 0; double start = 0;
double end = SnakingIn ? MathHelper.Clamp((Time.Current - (slider.StartTime - slider.TimePreempt)) / slider.TimeFadein, 0, 1) : 1; double end = SnakingIn ? MathHelper.Clamp((Time.Current - (slider.StartTime - slider.TimePreempt)) / slider.TimeFadeIn, 0, 1) : 1;
if (span >= slider.SpanCount() - 1) if (span >= slider.SpanCount() - 1)
{ {

View File

@ -4,7 +4,7 @@
using System; using System;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Input; using osu.Framework.Input.States;
using osu.Game.Graphics; using osu.Game.Graphics;
using OpenTK; using OpenTK;
using OpenTK.Graphics; using OpenTK.Graphics;

View File

@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Osu.Objects
public event Action<Vector2> PositionChanged; public event Action<Vector2> PositionChanged;
public double TimePreempt = 600; public double TimePreempt = 600;
public double TimeFadein = 400; public double TimeFadeIn = 400;
private Vector2 position; private Vector2 position;
@ -65,7 +65,7 @@ namespace osu.Game.Rulesets.Osu.Objects
base.ApplyDefaultsToSelf(controlPointInfo, difficulty); base.ApplyDefaultsToSelf(controlPointInfo, difficulty);
TimePreempt = (float)BeatmapDifficulty.DifficultyRange(difficulty.ApproachRate, 1800, 1200, 450); TimePreempt = (float)BeatmapDifficulty.DifficultyRange(difficulty.ApproachRate, 1800, 1200, 450);
TimeFadein = (float)BeatmapDifficulty.DifficultyRange(difficulty.ApproachRate, 1200, 800, 300); TimeFadeIn = (float)BeatmapDifficulty.DifficultyRange(difficulty.ApproachRate, 1200, 800, 300);
Scale = (1.0f - 0.7f * (difficulty.CircleSize - 5) / 5) / 2; Scale = (1.0f - 0.7f * (difficulty.CircleSize - 5) / 5) / 2;
} }

View File

@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Osu.Objects
// This is so on repeats ticks don't appear too late to be visually processed by the player. // This is so on repeats ticks don't appear too late to be visually processed by the player.
offset = 200; offset = 200;
else else
offset = TimeFadein * 0.66f; offset = TimeFadeIn * 0.66f;
TimePreempt = (StartTime - SpanStartTime) / 2 + offset; TimePreempt = (StartTime - SpanStartTime) / 2 + offset;
} }

View File

@ -31,8 +31,8 @@ namespace osu.Game.Rulesets.Osu
public override IEnumerable<KeyBinding> GetDefaultKeyBindings(int variant = 0) => new[] public override IEnumerable<KeyBinding> GetDefaultKeyBindings(int variant = 0) => new[]
{ {
new KeyBinding(InputKey.Z, OsuAction.LeftButton), new KeyBinding(InputKey.A, OsuAction.LeftButton),
new KeyBinding(InputKey.X, OsuAction.RightButton), new KeyBinding(InputKey.S, OsuAction.RightButton),
new KeyBinding(InputKey.MouseLeft, OsuAction.LeftButton), new KeyBinding(InputKey.MouseLeft, OsuAction.LeftButton),
new KeyBinding(InputKey.MouseRight, OsuAction.RightButton), new KeyBinding(InputKey.MouseRight, OsuAction.RightButton),
}; };

View File

@ -285,11 +285,11 @@ namespace osu.Game.Rulesets.Osu.Replays
AddFrameToReplay(startFrame); AddFrameToReplay(startFrame);
// We add intermediate frames for spinning / following a slider here. switch (h)
if (h is Spinner) {
// We add intermediate frames for spinning / following a slider here.
case Spinner spinner:
{ {
Spinner s = h as Spinner;
Vector2 difference = startPosition - SPINNER_CENTRE; Vector2 difference = startPosition - SPINNER_CENTRE;
float radius = difference.Length; float radius = difference.Length;
@ -297,7 +297,7 @@ namespace osu.Game.Rulesets.Osu.Replays
double t; double t;
for (double j = h.StartTime + FrameDelay; j < s.EndTime; j += FrameDelay) for (double j = h.StartTime + FrameDelay; j < spinner.EndTime; j += FrameDelay)
{ {
t = ApplyModsToTime(j - h.StartTime) * spinnerDirection; t = ApplyModsToTime(j - h.StartTime) * spinnerDirection;
@ -305,24 +305,25 @@ namespace osu.Game.Rulesets.Osu.Replays
AddFrameToReplay(new OsuReplayFrame((int)j, new Vector2(pos.X, pos.Y), action)); AddFrameToReplay(new OsuReplayFrame((int)j, new Vector2(pos.X, pos.Y), action));
} }
t = ApplyModsToTime(s.EndTime - h.StartTime) * spinnerDirection; t = ApplyModsToTime(spinner.EndTime - h.StartTime) * spinnerDirection;
Vector2 endPosition = SPINNER_CENTRE + CirclePosition(t / 20 + angle, SPIN_RADIUS); Vector2 endPosition = SPINNER_CENTRE + CirclePosition(t / 20 + angle, SPIN_RADIUS);
AddFrameToReplay(new OsuReplayFrame(s.EndTime, new Vector2(endPosition.X, endPosition.Y), action)); AddFrameToReplay(new OsuReplayFrame(spinner.EndTime, new Vector2(endPosition.X, endPosition.Y), action));
endFrame.Position = endPosition; endFrame.Position = endPosition;
break;
} }
else if (h is Slider) case Slider slider:
{ {
Slider s = h as Slider; for (double j = FrameDelay; j < slider.Duration; j += FrameDelay)
for (double j = FrameDelay; j < s.Duration; j += FrameDelay)
{ {
Vector2 pos = s.StackedPositionAt(j / s.Duration); Vector2 pos = slider.StackedPositionAt(j / slider.Duration);
AddFrameToReplay(new OsuReplayFrame(h.StartTime + j, new Vector2(pos.X, pos.Y), action)); AddFrameToReplay(new OsuReplayFrame(h.StartTime + j, new Vector2(pos.X, pos.Y), action));
} }
AddFrameToReplay(new OsuReplayFrame(s.EndTime, new Vector2(s.StackedEndPosition.X, s.StackedEndPosition.Y), action)); AddFrameToReplay(new OsuReplayFrame(slider.EndTime, new Vector2(slider.StackedEndPosition.X, slider.StackedEndPosition.Y), action));
break;
}
} }
// We only want to let go of our button if we are at the end of the current replay. Otherwise something is still going on after us so we need to keep the button pressed! // We only want to let go of our button if we are at the end of the current replay. Otherwise something is still going on after us so we need to keep the button pressed!

View File

@ -3,7 +3,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using osu.Framework.Input; using osu.Framework.Input.StateChanges;
using osu.Framework.MathUtils; using osu.Framework.MathUtils;
using osu.Game.Rulesets.Replays; using osu.Game.Rulesets.Replays;
using OpenTK; using OpenTK;

View File

@ -12,6 +12,7 @@ using osu.Framework.Graphics.Primitives;
using osu.Framework.Graphics.Shaders; using osu.Framework.Graphics.Shaders;
using osu.Framework.Graphics.Textures; using osu.Framework.Graphics.Textures;
using osu.Framework.Input; using osu.Framework.Input;
using osu.Framework.Input.States;
using osu.Framework.Timing; using osu.Framework.Timing;
using OpenTK; using OpenTK;
using OpenTK.Graphics; using OpenTK.Graphics;

View File

@ -39,6 +39,11 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
private int downCount; private int downCount;
private const float pressed_scale = 1.2f;
private const float released_scale = 1f;
private float targetScale => downCount > 0 ? pressed_scale : released_scale;
public bool OnPressed(OsuAction action) public bool OnPressed(OsuAction action)
{ {
switch (action) switch (action)
@ -46,7 +51,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
case OsuAction.LeftButton: case OsuAction.LeftButton:
case OsuAction.RightButton: case OsuAction.RightButton:
downCount++; downCount++;
ActiveCursor.ScaleTo(1).ScaleTo(1.2f, 100, Easing.OutQuad); ActiveCursor.ScaleTo(released_scale).ScaleTo(targetScale, 100, Easing.OutQuad);
break; break;
} }
@ -60,7 +65,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
case OsuAction.LeftButton: case OsuAction.LeftButton:
case OsuAction.RightButton: case OsuAction.RightButton:
if (--downCount == 0) if (--downCount == 0)
ActiveCursor.ScaleTo(1, 200, Easing.OutQuad); ActiveCursor.ScaleTo(targetScale, 200, Easing.OutQuad);
break; break;
} }
@ -72,13 +77,13 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
protected override void PopIn() protected override void PopIn()
{ {
fadeContainer.FadeTo(1, 300, Easing.OutQuint); fadeContainer.FadeTo(1, 300, Easing.OutQuint);
ActiveCursor.ScaleTo(1, 400, Easing.OutQuint); ActiveCursor.ScaleTo(targetScale, 400, Easing.OutQuint);
} }
protected override void PopOut() protected override void PopOut()
{ {
fadeContainer.FadeTo(0.05f, 450, Easing.OutQuint); fadeContainer.FadeTo(0.05f, 450, Easing.OutQuint);
ActiveCursor.ScaleTo(0.8f, 450, Easing.OutQuint); ActiveCursor.ScaleTo(targetScale * 0.8f, 450, Easing.OutQuint);
} }
public class OsuCursor : Container public class OsuCursor : Container

View File

@ -20,8 +20,6 @@ namespace osu.Game.Rulesets.Osu.UI
private readonly JudgementContainer<DrawableOsuJudgement> judgementLayer; private readonly JudgementContainer<DrawableOsuJudgement> judgementLayer;
private readonly ConnectionRenderer<OsuHitObject> connectionLayer; private readonly ConnectionRenderer<OsuHitObject> connectionLayer;
protected virtual bool DisplayJudgements => true;
public static readonly Vector2 BASE_SIZE = new Vector2(512, 384); public static readonly Vector2 BASE_SIZE = new Vector2(512, 384);
public OsuPlayfield() public OsuPlayfield()

View File

@ -1,6 +1,7 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Linq;
using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Cursor;
using osu.Framework.Input; using osu.Framework.Input;
using OpenTK; using OpenTK;
@ -33,19 +34,30 @@ namespace osu.Game.Rulesets.Osu.UI
protected override DrawableHitObject<OsuHitObject> GetVisualRepresentation(OsuHitObject h) protected override DrawableHitObject<OsuHitObject> GetVisualRepresentation(OsuHitObject h)
{ {
if (h is HitCircle circle) switch (h)
{
case HitCircle circle:
return new DrawableHitCircle(circle); return new DrawableHitCircle(circle);
case Slider slider:
if (h is Slider slider)
return new DrawableSlider(slider); return new DrawableSlider(slider);
case Spinner spinner:
if (h is Spinner spinner)
return new DrawableSpinner(spinner); return new DrawableSpinner(spinner);
}
return null; return null;
} }
protected override ReplayInputHandler CreateReplayInputHandler(Replay replay) => new OsuReplayInputHandler(replay); protected override ReplayInputHandler CreateReplayInputHandler(Replay replay) => new OsuReplayInputHandler(replay);
public override double GameplayStartTime
{
get
{
var first = (OsuHitObject)Objects.First();
return first.StartTime - first.TimePreempt;
}
}
protected override Vector2 GetAspectAdjustedSize() protected override Vector2 GetAspectAdjustedSize()
{ {
var aspectSize = DrawSize.X * 0.75f < DrawSize.Y ? new Vector2(DrawSize.X, DrawSize.X * 0.75f) : new Vector2(DrawSize.Y * 4f / 3f, DrawSize.Y); var aspectSize = DrawSize.X * 0.75f < DrawSize.Y ? new Vector2(DrawSize.X, DrawSize.X * 0.75f) : new Vector2(DrawSize.Y * 4f / 3f, DrawSize.Y);

View File

@ -4,7 +4,7 @@
using osu.Game.Rulesets.Replays; using osu.Game.Rulesets.Replays;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using osu.Framework.Input; using osu.Framework.Input.StateChanges;
namespace osu.Game.Rulesets.Taiko.Replays namespace osu.Game.Rulesets.Taiko.Replays
{ {

View File

@ -80,15 +80,16 @@ namespace osu.Game.Rulesets.Taiko.Scoring
foreach (var obj in beatmap.HitObjects) foreach (var obj in beatmap.HitObjects)
{ {
if (obj is Hit) switch (obj)
{ {
case Hit _:
AddJudgement(new TaikoJudgement { Result = HitResult.Great }); AddJudgement(new TaikoJudgement { Result = HitResult.Great });
if (obj.IsStrong) if (obj.IsStrong)
AddJudgement(new TaikoStrongHitJudgement()); AddJudgement(new TaikoStrongHitJudgement());
} break;
else if (obj is DrumRoll) case DrumRoll drumRoll:
{ var count = drumRoll.NestedHitObjects.OfType<DrumRollTick>().Count();
for (int i = 0; i < ((DrumRoll)obj).NestedHitObjects.OfType<DrumRollTick>().Count(); i++) for (int i = 0; i < count; i++)
{ {
AddJudgement(new TaikoDrumRollTickJudgement { Result = HitResult.Great }); AddJudgement(new TaikoDrumRollTickJudgement { Result = HitResult.Great });
@ -100,10 +101,10 @@ namespace osu.Game.Rulesets.Taiko.Scoring
if (obj.IsStrong) if (obj.IsStrong)
AddJudgement(new TaikoStrongHitJudgement()); AddJudgement(new TaikoStrongHitJudgement());
} break;
else if (obj is Swell) case Swell _:
{
AddJudgement(new TaikoJudgement { Result = HitResult.Great }); AddJudgement(new TaikoJudgement { Result = HitResult.Great });
break;
} }
} }
} }

View File

@ -58,8 +58,9 @@ namespace osu.Game.Rulesets.Taiko.UI
private readonly Box background; private readonly Box background;
public TaikoPlayfield(ControlPointInfo controlPoints) public TaikoPlayfield(ControlPointInfo controlPoints)
: base(ScrollingDirection.Left)
{ {
Direction.Value = ScrollingDirection.Left;
AddRangeInternal(new Drawable[] AddRangeInternal(new Drawable[]
{ {
backgroundContainer = new Container backgroundContainer = new Container
@ -212,17 +213,22 @@ namespace osu.Game.Rulesets.Taiko.UI
base.Add(h); base.Add(h);
var barline = h as DrawableBarLine; switch (h)
if (barline != null) {
case DrawableBarLine barline:
barlineContainer.Add(barline.CreateProxy()); barlineContainer.Add(barline.CreateProxy());
break;
var taikoObject = h as DrawableTaikoHitObject; case DrawableTaikoHitObject taikoObject:
if (taikoObject != null)
topLevelHitContainer.Add(taikoObject.CreateProxiedContent()); topLevelHitContainer.Add(taikoObject.CreateProxiedContent());
break;
}
} }
internal void OnJudgement(DrawableHitObject judgedObject, Judgement judgement) internal void OnJudgement(DrawableHitObject judgedObject, Judgement judgement)
{ {
if (!DisplayJudgements)
return;
if (judgedObject.DisplayJudgement && judgementContainer.FirstOrDefault(j => j.JudgedObject == judgedObject) == null) if (judgedObject.DisplayJudgement && judgementContainer.FirstOrDefault(j => j.JudgedObject == judgedObject) == null)
{ {
judgementContainer.Add(new DrawableTaikoJudgement(judgement, judgedObject) judgementContainer.Add(new DrawableTaikoJudgement(judgement, judgedObject)

View File

@ -98,31 +98,21 @@ namespace osu.Game.Rulesets.Taiko.UI
protected override DrawableHitObject<TaikoHitObject> GetVisualRepresentation(TaikoHitObject h) protected override DrawableHitObject<TaikoHitObject> GetVisualRepresentation(TaikoHitObject h)
{ {
var centreHit = h as CentreHit; switch (h)
if (centreHit != null)
{ {
if (h.IsStrong) case CentreHit centreHit when h.IsStrong:
return new DrawableCentreHitStrong(centreHit); return new DrawableCentreHitStrong(centreHit);
case CentreHit centreHit:
return new DrawableCentreHit(centreHit); return new DrawableCentreHit(centreHit);
} case RimHit rimHit when h.IsStrong:
var rimHit = h as RimHit;
if (rimHit != null)
{
if (h.IsStrong)
return new DrawableRimHitStrong(rimHit); return new DrawableRimHitStrong(rimHit);
case RimHit rimHit:
return new DrawableRimHit(rimHit); return new DrawableRimHit(rimHit);
} case DrumRoll drumRoll:
var drumRoll = h as DrumRoll;
if (drumRoll != null)
{
return new DrawableDrumRoll(drumRoll); return new DrawableDrumRoll(drumRoll);
} case Swell swell:
var swell = h as Swell;
if (swell != null)
return new DrawableSwell(swell); return new DrawableSwell(swell);
}
return null; return null;
} }

View File

@ -11,6 +11,7 @@ using osu.Game.Audio;
using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Objects.Types;
using osu.Game.Beatmaps.Formats; using osu.Game.Beatmaps.Formats;
using osu.Game.Beatmaps.Timing; using osu.Game.Beatmaps.Timing;
using osu.Game.Rulesets.Objects;
using osu.Game.Skinning; using osu.Game.Skinning;
namespace osu.Game.Tests.Beatmaps.Formats namespace osu.Game.Tests.Beatmaps.Formats
@ -211,5 +212,58 @@ namespace osu.Game.Tests.Beatmaps.Formats
Assert.IsTrue(hitObjects[1].Samples.Any(s => s.Name == SampleInfo.HIT_CLAP)); Assert.IsTrue(hitObjects[1].Samples.Any(s => s.Name == SampleInfo.HIT_CLAP));
} }
} }
[Test]
public void TestDecodeControlPointCustomSampleBank()
{
var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false };
using (var resStream = Resource.OpenResource("controlpoint-custom-samplebank.osu"))
using (var stream = new StreamReader(resStream))
{
var hitObjects = decoder.Decode(stream).HitObjects;
Assert.AreEqual("normal-hitnormal", getTestableSampleInfo(hitObjects[0]).LookupNames.First());
Assert.AreEqual("normal-hitnormal", getTestableSampleInfo(hitObjects[1]).LookupNames.First());
Assert.AreEqual("normal-hitnormal2", getTestableSampleInfo(hitObjects[2]).LookupNames.First());
Assert.AreEqual("normal-hitnormal", getTestableSampleInfo(hitObjects[3]).LookupNames.First());
}
SampleInfo getTestableSampleInfo(HitObject hitObject) => hitObject.SampleControlPoint.ApplyTo(hitObject.Samples[0]);
}
[Test]
public void TestDecodeHitObjectCustomSampleBank()
{
var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false };
using (var resStream = Resource.OpenResource("hitobject-custom-samplebank.osu"))
using (var stream = new StreamReader(resStream))
{
var hitObjects = decoder.Decode(stream).HitObjects;
Assert.AreEqual("normal-hitnormal", getTestableSampleInfo(hitObjects[0]).LookupNames.First());
Assert.AreEqual("normal-hitnormal2", getTestableSampleInfo(hitObjects[1]).LookupNames.First());
Assert.AreEqual("normal-hitnormal3", getTestableSampleInfo(hitObjects[2]).LookupNames.First());
}
SampleInfo getTestableSampleInfo(HitObject hitObject) => hitObject.SampleControlPoint.ApplyTo(hitObject.Samples[0]);
}
[Test]
public void TestDecodeHitObjectFileSamples()
{
var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false };
using (var resStream = Resource.OpenResource("hitobject-file-samples.osu"))
using (var stream = new StreamReader(resStream))
{
var hitObjects = decoder.Decode(stream).HitObjects;
Assert.AreEqual("hit_1.wav", getTestableSampleInfo(hitObjects[0]).LookupNames.First());
Assert.AreEqual("hit_2.wav", getTestableSampleInfo(hitObjects[1]).LookupNames.First());
Assert.AreEqual("normal-hitnormal2", getTestableSampleInfo(hitObjects[2]).LookupNames.First());
Assert.AreEqual("hit_1.wav", getTestableSampleInfo(hitObjects[3]).LookupNames.First());
}
SampleInfo getTestableSampleInfo(HitObject hitObject) => hitObject.SampleControlPoint.ApplyTo(hitObject.Samples[0]);
}
} }
} }

View File

@ -118,7 +118,11 @@ namespace osu.Game.Tests.Beatmaps.Formats
public void TestParity(string beatmap) public void TestParity(string beatmap)
{ {
var legacy = decode(beatmap, out Beatmap json); var legacy = decode(beatmap, out Beatmap json);
json.WithDeepEqual(legacy).IgnoreProperty(r => r.DeclaringType == typeof(HitWindows)).Assert(); json.WithDeepEqual(legacy)
.IgnoreProperty(r => r.DeclaringType == typeof(HitWindows)
// Todo: CustomSampleBank shouldn't exist going forward, we need a conversion mechanism
|| r.Name == nameof(LegacyDecoder<Beatmap>.LegacySampleControlPoint.CustomSampleBank))
.Assert();
} }
/// <summary> /// <summary>

View File

@ -0,0 +1,16 @@
osu file format v14
[General]
SampleSet: Normal
[TimingPoints]
2170,468.75,4,1,0,40,1,0
2638,-100,4,1,1,40,0,0
3107,-100,4,1,2,40,0,0
3576,-100,4,1,0,40,0,0
[HitObjects]
255,193,2170,1,0,0:0:0:0:
256,191,2638,5,0,0:0:0:0:
255,193,3107,1,0,0:0:0:0:
256,191,3576,1,0,0:0:0:0:

View File

@ -0,0 +1,13 @@
osu file format v14
[General]
SampleSet: Normal
[TimingPoints]
2170,468.75,4,1,0,40,1,0
3107,-100,4,1,2,40,0,0
[HitObjects]
255,193,2170,1,0,0:0:0:0:
256,191,3107,5,0,0:0:0:0:
255,193,4000,1,0,0:0:3:70:

View File

@ -0,0 +1,16 @@
osu file format v14
[General]
SampleSet: Normal
[TimingPoints]
2170,468.75,4,1,0,40,1,0
2638,-100,4,1,1,40,0,0
3107,-100,4,1,2,40,0,0
3576,-100,4,1,0,40,0,0
[HitObjects]
255,193,2170,1,0,0:0:0:0:hit_1.wav
256,191,2638,5,0,0:0:0:0:hit_2.wav
255,193,3107,1,0,0:0:0:0:
256,191,3576,1,0,0:0:0:0:hit_1.wav

View File

@ -1,6 +1,9 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Colour;
@ -13,6 +16,13 @@ namespace osu.Game.Tests.Visual
[TestFixture] [TestFixture]
public class TestCaseButtonSystem : OsuTestCase public class TestCaseButtonSystem : OsuTestCase
{ {
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(ButtonSystem),
typeof(ButtonArea),
typeof(Button)
};
public TestCaseButtonSystem() public TestCaseButtonSystem()
{ {
OsuLogo logo; OsuLogo logo;
@ -30,6 +40,9 @@ namespace osu.Game.Tests.Visual
}; };
buttons.SetOsuLogo(logo); buttons.SetOsuLogo(logo);
foreach (var s in Enum.GetValues(typeof(ButtonSystemState)).OfType<ButtonSystemState>().Skip(1))
AddStep($"State to {s}", () => buttons.State = s);
} }
} }
} }

View File

@ -7,7 +7,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; 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; using osu.Framework.Input.States;
using osu.Framework.MathUtils; using osu.Framework.MathUtils;
using osu.Game.Graphics.Cursor; using osu.Game.Graphics.Cursor;
using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Sprites;

View File

@ -0,0 +1,28 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Shapes;
using osu.Game.Screens.Menu;
using OpenTK.Graphics;
namespace osu.Game.Tests.Visual
{
public class TestCaseDisclaimer : OsuTestCase
{
[BackgroundDependencyLoader]
private void load()
{
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Color4.Black,
},
new Disclaimer()
};
}
}
}

View File

@ -8,7 +8,7 @@ using System.Linq;
using OpenTK.Input; using OpenTK.Input;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Input; using osu.Framework.Input.EventArgs;
using osu.Framework.Logging; using osu.Framework.Logging;
using osu.Game.Screens.Play; using osu.Game.Screens.Play;

View File

@ -25,11 +25,7 @@ namespace osu.Game.Tests.Visual
Children = new[] Children = new[]
{ {
new NamedIconButton("No change", new IconButton()), new NamedIconButton("No change", new IconButton()),
new NamedIconButton("Background colours", new IconButton new NamedIconButton("Background colours", new ColouredIconButton()),
{
FlashColour = Color4.DarkGreen,
HoverColour = Color4.Green,
}),
new NamedIconButton("Full-width", new IconButton { ButtonSize = new Vector2(200, 30) }), new NamedIconButton("Full-width", new IconButton { ButtonSize = new Vector2(200, 30) }),
new NamedIconButton("Unchanging size", new IconButton(), false), new NamedIconButton("Unchanging size", new IconButton(), false),
new NamedIconButton("Icon colours", new IconButton new NamedIconButton("Icon colours", new IconButton
@ -41,6 +37,15 @@ namespace osu.Game.Tests.Visual
}; };
} }
private class ColouredIconButton : IconButton
{
public ColouredIconButton()
{
FlashColour = Color4.DarkGreen;
HoverColour = Color4.Green;
}
}
private class NamedIconButton : Container private class NamedIconButton : Container
{ {
public NamedIconButton(string name, IconButton button, bool allowSizeChange = true) public NamedIconButton(string name, IconButton button, bool allowSizeChange = true)

View File

@ -18,7 +18,6 @@ namespace osu.Game.Tests.Visual
{ {
Origin = Anchor.Centre, Origin = Anchor.Centre,
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
IsCounting = true,
Children = new KeyCounter[] Children = new KeyCounter[]
{ {
new KeyCounterKeyboard(Key.Z), new KeyCounterKeyboard(Key.Z),

View File

@ -0,0 +1,41 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Screens.Edit.Screens.Setup.Components.LabelledComponents;
using System;
using System.Collections.Generic;
namespace osu.Game.Tests.Visual
{
[TestFixture]
public class TestCaseLabelledTextBox : OsuTestCase
{
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(LabelledTextBox),
};
[BackgroundDependencyLoader]
private void load()
{
Child = new Container
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.X,
Padding = new MarginPadding { Left = 150, Right = 150 },
Child = new LabelledTextBox
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
LabelText = "Testing text",
PlaceholderText = "This is definitely working as intended",
}
};
}
}
}

View File

@ -114,7 +114,7 @@ namespace osu.Game.Tests.Visual
testMultiplierTextColour(noFailMod, modSelect.LowMultiplierColour); testMultiplierTextColour(noFailMod, modSelect.LowMultiplierColour);
testMultiplierTextColour(hiddenMod, modSelect.HighMultiplierColour); testMultiplierTextColour(hiddenMod, modSelect.HighMultiplierColour);
testUnimplmentedMod(autoPilotMod); testUnimplementedMod(autoPilotMod);
} }
private void testManiaMods(ManiaRuleset ruleset) private void testManiaMods(ManiaRuleset ruleset)
@ -154,7 +154,7 @@ namespace osu.Game.Tests.Visual
checkNotSelected(mod); checkNotSelected(mod);
} }
private void testUnimplmentedMod(Mod mod) private void testUnimplementedMod(Mod mod)
{ {
selectNext(mod); selectNext(mod);
checkNotSelected(mod); checkNotSelected(mod);

View File

@ -88,7 +88,7 @@ namespace osu.Game.Tests.Visual
private class TestOnScreenDisplay : OnScreenDisplay private class TestOnScreenDisplay : OnScreenDisplay
{ {
protected override void Display(Drawable toDisplay) => toDisplay.FadeIn().ResizeHeightTo(110); protected override void DisplayTemporarily(Drawable toDisplay) => toDisplay.FadeIn().ResizeHeightTo(110);
} }
} }
} }

View File

@ -6,7 +6,6 @@ using System.Collections.Generic;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Framework.Timing;
using osu.Game.Screens; using osu.Game.Screens;
using osu.Game.Screens.Menu; using osu.Game.Screens.Menu;
using OpenTK.Graphics; using OpenTK.Graphics;
@ -23,19 +22,15 @@ namespace osu.Game.Tests.Visual
public TestCaseOsuGame() public TestCaseOsuGame()
{ {
var rateAdjustClock = new StopwatchClock(true); Children = new Drawable[]
var framedClock = new FramedClock(rateAdjustClock); {
framedClock.ProcessFrame(); new Box
Add(new Box
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Colour = Color4.Black, Colour = Color4.Black,
}); },
new Loader()
Add(new Loader()); };
AddSliderStep("Playback speed", 0.0, 2.0, 1, v => rateAdjustClock.Rate = v);
} }
} }
} }

View File

@ -16,7 +16,6 @@ using osu.Game.Rulesets;
using osu.Game.Screens.Select; using osu.Game.Screens.Select;
using osu.Game.Screens.Select.Carousel; using osu.Game.Screens.Select.Carousel;
using osu.Game.Screens.Select.Filter; using osu.Game.Screens.Select.Filter;
using osu.Game.Tests.Platform;
namespace osu.Game.Tests.Visual namespace osu.Game.Tests.Visual
{ {
@ -28,6 +27,7 @@ namespace osu.Game.Tests.Visual
private RulesetStore rulesets; private RulesetStore rulesets;
private WorkingBeatmap defaultBeatmap; private WorkingBeatmap defaultBeatmap;
private DatabaseContextFactory factory;
public override IReadOnlyList<Type> RequiredTypes => new[] public override IReadOnlyList<Type> RequiredTypes => new[]
{ {
@ -54,18 +54,25 @@ namespace osu.Game.Tests.Visual
public new BeatmapCarousel Carousel => base.Carousel; public new BeatmapCarousel Carousel => base.Carousel;
} }
protected override void Dispose(bool isDisposing)
{
factory.ResetDatabase();
base.Dispose(isDisposing);
}
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load() private void load()
{ {
TestSongSelect songSelect = null; TestSongSelect songSelect = null;
var storage = new TestStorage(@"TestCasePlaySongSelect"); factory = new DatabaseContextFactory(LocalStorage);
factory.ResetDatabase();
// this is by no means clean. should be replacing inside of OsuGameBase somehow. using (var usage = factory.Get())
IDatabaseContextFactory factory = new SingletonContextFactory(new OsuDbContext()); usage.Migrate();
Dependencies.Cache(rulesets = new RulesetStore(factory)); Dependencies.Cache(rulesets = new RulesetStore(factory));
Dependencies.Cache(manager = new BeatmapManager(storage, factory, rulesets, null, null) Dependencies.Cache(manager = new BeatmapManager(LocalStorage, factory, rulesets, null, null)
{ {
DefaultBeatmap = defaultBeatmap = Beatmap.Default DefaultBeatmap = defaultBeatmap = Beatmap.Default
}); });

View File

@ -14,9 +14,9 @@ namespace osu.Game.Tests.Visual
{ {
private readonly PreviewTrackManager trackManager = new TestPreviewTrackManager(); private readonly PreviewTrackManager trackManager = new TestPreviewTrackManager();
protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent) protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
{ {
var dependencies = new DependencyContainer(base.CreateLocalDependencies(parent)); var dependencies = new DependencyContainer(base.CreateChildDependencies(parent));
dependencies.CacheAs(trackManager); dependencies.CacheAs(trackManager);
dependencies.CacheAs<IPreviewTrackOwner>(this); dependencies.CacheAs<IPreviewTrackOwner>(this);
return dependencies; return dependencies;
@ -101,9 +101,9 @@ namespace osu.Game.Tests.Visual
AddInternal(track); AddInternal(track);
} }
protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent) protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
{ {
var dependencies = new DependencyContainer(base.CreateLocalDependencies(parent)); var dependencies = new DependencyContainer(base.CreateChildDependencies(parent));
dependencies.CacheAs<IPreviewTrackOwner>(this); dependencies.CacheAs<IPreviewTrackOwner>(this);
return dependencies; return dependencies;
} }

View File

@ -117,7 +117,6 @@ namespace osu.Game.Tests.Visual
public new readonly ScrollingDirection Direction; public new readonly ScrollingDirection Direction;
public TestPlayfield(ScrollingDirection direction) public TestPlayfield(ScrollingDirection direction)
: base(direction)
{ {
Direction = direction; Direction = direction;

View File

@ -16,8 +16,8 @@ namespace osu.Game.Tests.Visual
public override IReadOnlyList<Type> RequiredTypes => new[] public override IReadOnlyList<Type> RequiredTypes => new[]
{ {
typeof(ToolbarButton), typeof(ToolbarButton),
typeof(ToolbarModeSelector), typeof(ToolbarRulesetSelector),
typeof(ToolbarModeButton), typeof(ToolbarRulesetButton),
typeof(ToolbarNotificationButton), typeof(ToolbarNotificationButton),
}; };

View File

@ -53,7 +53,6 @@ namespace osu.Game.Tests.Visual
CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c1.jpg", CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c1.jpg",
JoinDate = DateTimeOffset.Now.AddDays(-1), JoinDate = DateTimeOffset.Now.AddDays(-1),
LastVisit = DateTimeOffset.Now, LastVisit = DateTimeOffset.Now,
Age = 1,
ProfileOrder = new[] { "me" }, ProfileOrder = new[] { "me" },
Statistics = new UserStatistics Statistics = new UserStatistics
{ {

View File

@ -2,6 +2,7 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System; using System;
using System.Collections.Generic;
namespace osu.Game.Audio namespace osu.Game.Audio
{ {
@ -28,9 +29,37 @@ namespace osu.Game.Audio
/// </summary> /// </summary>
public string Name; public string Name;
/// <summary>
/// An optional suffix to provide priority lookup. Falls back to non-suffixed <see cref="Name"/>.
/// </summary>
public string Suffix;
/// <summary> /// <summary>
/// The sample volume. /// The sample volume.
/// </summary> /// </summary>
public int Volume; public int Volume;
/// <summary>
/// Retrieve all possible filenames that can be used as a source, returned in order of preference (highest first).
/// </summary>
public virtual IEnumerable<string> LookupNames
{
get
{
if (!string.IsNullOrEmpty(Namespace))
{
if (!string.IsNullOrEmpty(Suffix))
yield return $"{Namespace}/{Bank}-{Name}{Suffix}";
yield return $"{Namespace}/{Bank}-{Name}";
}
// check non-namespace as a fallback even when we have a namespace
if (!string.IsNullOrEmpty(Suffix))
yield return $"{Bank}-{Name}{Suffix}";
yield return $"{Bank}-{Name}";
}
}
public SampleInfo Clone() => (SampleInfo)MemberwiseClone();
} }
} }

View File

@ -45,6 +45,16 @@ namespace osu.Game.Beatmaps
/// </summary> /// </summary>
public event Action<DownloadBeatmapSetRequest> BeatmapDownloadBegan; public event Action<DownloadBeatmapSetRequest> BeatmapDownloadBegan;
/// <summary>
/// Fired when a beatmap download is interrupted, due to user cancellation or other failures.
/// </summary>
public event Action<DownloadBeatmapSetRequest> BeatmapDownloadFailed;
/// <summary>
/// Fired when a beatmap load is requested (into the interactive game UI).
/// </summary>
public Action<BeatmapSetInfo> PresentBeatmap;
/// <summary> /// <summary>
/// A default representation of a WorkingBeatmap to use when no beatmap is available. /// A default representation of a WorkingBeatmap to use when no beatmap is available.
/// </summary> /// </summary>
@ -79,33 +89,46 @@ namespace osu.Game.Beatmaps
this.audioManager = audioManager; this.audioManager = audioManager;
} }
protected override void Populate(BeatmapSetInfo model, ArchiveReader archive) protected override void Populate(BeatmapSetInfo beatmapSet, ArchiveReader archive)
{ {
model.Beatmaps = createBeatmapDifficulties(archive); if (archive != null)
beatmapSet.Beatmaps = createBeatmapDifficulties(archive);
foreach (BeatmapInfo b in model.Beatmaps) foreach (BeatmapInfo b in beatmapSet.Beatmaps)
{ {
// remove metadata from difficulties where it matches the set // remove metadata from difficulties where it matches the set
if (model.Metadata.Equals(b.Metadata)) if (beatmapSet.Metadata.Equals(b.Metadata))
b.Metadata = null; b.Metadata = null;
// by setting the model here, we can update the noline set id below. b.BeatmapSet = beatmapSet;
b.BeatmapSet = model;
fetchAndPopulateOnlineIDs(b);
} }
// check if a set already exists with the same online id, delete if it does. // check if a set already exists with the same online id, delete if it does.
if (model.OnlineBeatmapSetID != null) if (beatmapSet.OnlineBeatmapSetID != null)
{ {
var existingOnlineId = beatmaps.ConsumableItems.FirstOrDefault(b => b.OnlineBeatmapSetID == model.OnlineBeatmapSetID); var existingOnlineId = beatmaps.ConsumableItems.FirstOrDefault(b => b.OnlineBeatmapSetID == beatmapSet.OnlineBeatmapSetID);
if (existingOnlineId != null) if (existingOnlineId != null)
{ {
Delete(existingOnlineId); Delete(existingOnlineId);
beatmaps.PurgeDeletable(s => s.ID == existingOnlineId.ID); beatmaps.PurgeDeletable(s => s.ID == existingOnlineId.ID);
Logger.Log($"Found existing beatmap set with same OnlineBeatmapSetID ({model.OnlineBeatmapSetID}). It has been purged.", LoggingTarget.Database); Logger.Log($"Found existing beatmap set with same OnlineBeatmapSetID ({beatmapSet.OnlineBeatmapSetID}). It has been purged.", LoggingTarget.Database);
} }
} }
validateOnlineIds(beatmapSet.Beatmaps);
foreach (BeatmapInfo b in beatmapSet.Beatmaps)
fetchAndPopulateOnlineIDs(b, beatmapSet.Beatmaps);
}
private void validateOnlineIds(List<BeatmapInfo> beatmaps)
{
var beatmapIds = beatmaps.Where(b => b.OnlineBeatmapID.HasValue).Select(b => b.OnlineBeatmapID).ToList();
// ensure all IDs are unique in this set and none match existing IDs in the local beatmap store.
if (beatmapIds.GroupBy(b => b).Any(g => g.Count() > 1) || QueryBeatmaps(b => beatmapIds.Contains(b.OnlineBeatmapID)).Any())
// remove all online IDs if any problems were found.
beatmaps.ForEach(b => b.OnlineBeatmapID = null);
} }
protected override BeatmapSetInfo CheckForExisting(BeatmapSetInfo model) protected override BeatmapSetInfo CheckForExisting(BeatmapSetInfo model)
@ -143,7 +166,7 @@ namespace osu.Game.Beatmaps
return; return;
} }
var downloadNotification = new ProgressNotification var downloadNotification = new DownloadNotification
{ {
CompletionText = $"Imported {beatmapSetInfo.Metadata.Artist} - {beatmapSetInfo.Metadata.Title}!", CompletionText = $"Imported {beatmapSetInfo.Metadata.Artist} - {beatmapSetInfo.Metadata.Title}!",
Text = $"Downloading {beatmapSetInfo.Metadata.Artist} - {beatmapSetInfo.Metadata.Title}", Text = $"Downloading {beatmapSetInfo.Metadata.Artist} - {beatmapSetInfo.Metadata.Title}",
@ -163,18 +186,28 @@ namespace osu.Game.Beatmaps
Task.Factory.StartNew(() => Task.Factory.StartNew(() =>
{ {
BeatmapSetInfo importedBeatmap;
// This gets scheduled back to the update thread, but we want the import to run in the background. // This gets scheduled back to the update thread, but we want the import to run in the background.
using (var stream = new MemoryStream(data)) using (var stream = new MemoryStream(data))
using (var archive = new ZipArchiveReader(stream, beatmapSetInfo.ToString())) using (var archive = new ZipArchiveReader(stream, beatmapSetInfo.ToString()))
Import(archive); importedBeatmap = Import(archive);
downloadNotification.CompletionClickAction = () =>
{
PresentBeatmap?.Invoke(importedBeatmap);
return true;
};
downloadNotification.State = ProgressNotificationState.Completed; downloadNotification.State = ProgressNotificationState.Completed;
currentDownloads.Remove(request); currentDownloads.Remove(request);
}, TaskCreationOptions.LongRunning); }, TaskCreationOptions.LongRunning);
}; };
request.Failure += error => request.Failure += error =>
{ {
BeatmapDownloadFailed?.Invoke(request);
if (error is OperationCanceledException) return; if (error is OperationCanceledException) return;
downloadNotification.State = ProgressNotificationState.Cancelled; downloadNotification.State = ProgressNotificationState.Cancelled;
@ -276,7 +309,7 @@ namespace osu.Game.Beatmaps
/// </summary> /// </summary>
/// <param name="query">The query.</param> /// <param name="query">The query.</param>
/// <returns>Results from the provided query.</returns> /// <returns>Results from the provided query.</returns>
public IEnumerable<BeatmapInfo> QueryBeatmaps(Expression<Func<BeatmapInfo, bool>> query) => beatmaps.Beatmaps.AsNoTracking().Where(query); public IQueryable<BeatmapInfo> QueryBeatmaps(Expression<Func<BeatmapInfo, bool>> query) => beatmaps.Beatmaps.AsNoTracking().Where(query);
/// <summary> /// <summary>
/// Denotes whether an osu-stable installation is present to perform automated imports from. /// Denotes whether an osu-stable installation is present to perform automated imports from.
@ -355,21 +388,10 @@ namespace osu.Game.Beatmaps
beatmap.BeatmapInfo.Hash = ms.ComputeSHA2Hash(); beatmap.BeatmapInfo.Hash = ms.ComputeSHA2Hash();
beatmap.BeatmapInfo.MD5Hash = ms.ComputeMD5Hash(); beatmap.BeatmapInfo.MD5Hash = ms.ComputeMD5Hash();
// check that no existing beatmap exists that is imported with the same online beatmap ID. if so, give it precedence. var ruleset = rulesets.GetRuleset(beatmap.BeatmapInfo.RulesetID);
if (beatmap.BeatmapInfo.OnlineBeatmapID.HasValue && QueryBeatmap(b => b.OnlineBeatmapID.Value == beatmap.BeatmapInfo.OnlineBeatmapID.Value) != null)
beatmap.BeatmapInfo.OnlineBeatmapID = null;
RulesetInfo ruleset = rulesets.GetRuleset(beatmap.BeatmapInfo.RulesetID);
beatmap.BeatmapInfo.Ruleset = ruleset; beatmap.BeatmapInfo.Ruleset = ruleset;
if (ruleset != null)
{
// TODO: this should be done in a better place once we actually need to dynamically update it. // TODO: this should be done in a better place once we actually need to dynamically update it.
beatmap.BeatmapInfo.StarDifficulty = ruleset.CreateInstance().CreateDifficultyCalculator(new DummyConversionBeatmap(beatmap)).Calculate().StarRating; beatmap.BeatmapInfo.StarDifficulty = ruleset?.CreateInstance().CreateDifficultyCalculator(new DummyConversionBeatmap(beatmap)).Calculate().StarRating ?? 0;
}
else
beatmap.BeatmapInfo.StarDifficulty = 0;
beatmapInfos.Add(beatmap.BeatmapInfo); beatmapInfos.Add(beatmap.BeatmapInfo);
} }
@ -382,16 +404,17 @@ namespace osu.Game.Beatmaps
/// Query the API to populate mising OnlineBeatmapID / OnlineBeatmapSetID properties. /// Query the API to populate mising OnlineBeatmapID / OnlineBeatmapSetID properties.
/// </summary> /// </summary>
/// <param name="beatmap">The beatmap to populate.</param> /// <param name="beatmap">The beatmap to populate.</param>
/// <param name="otherBeatmaps">The other beatmaps contained within this set.</param>
/// <param name="force">Whether to re-query if the provided beatmap already has populated values.</param> /// <param name="force">Whether to re-query if the provided beatmap already has populated values.</param>
/// <returns>True if population was successful.</returns> /// <returns>True if population was successful.</returns>
private bool fetchAndPopulateOnlineIDs(BeatmapInfo beatmap, bool force = false) private bool fetchAndPopulateOnlineIDs(BeatmapInfo beatmap, IEnumerable<BeatmapInfo> otherBeatmaps, bool force = false)
{ {
if (api?.State != APIState.Online)
return false;
if (!force && beatmap.OnlineBeatmapID != null && beatmap.BeatmapSet.OnlineBeatmapSetID != null) if (!force && beatmap.OnlineBeatmapID != null && beatmap.BeatmapSet.OnlineBeatmapSetID != null)
return true; return true;
if (api.State != APIState.Online)
return false;
Logger.Log("Attempting online lookup for IDs...", LoggingTarget.Database); Logger.Log("Attempting online lookup for IDs...", LoggingTarget.Database);
try try
@ -404,6 +427,12 @@ namespace osu.Game.Beatmaps
Logger.Log($"Successfully mapped to {res.OnlineBeatmapSetID} / {res.OnlineBeatmapID}.", LoggingTarget.Database); Logger.Log($"Successfully mapped to {res.OnlineBeatmapSetID} / {res.OnlineBeatmapID}.", LoggingTarget.Database);
if (otherBeatmaps.Any(b => b.OnlineBeatmapID == res.OnlineBeatmapID))
{
Logger.Log("Another beatmap in the same set already mapped to this ID. We'll skip adding it this time.", LoggingTarget.Database);
return false;
}
beatmap.BeatmapSet.OnlineBeatmapSetID = res.OnlineBeatmapSetID; beatmap.BeatmapSet.OnlineBeatmapSetID = res.OnlineBeatmapSetID;
beatmap.OnlineBeatmapID = res.OnlineBeatmapID; beatmap.OnlineBeatmapID = res.OnlineBeatmapID;
return true; return true;
@ -432,5 +461,21 @@ namespace osu.Game.Beatmaps
protected override Texture GetBackground() => null; protected override Texture GetBackground() => null;
protected override Track GetTrack() => null; protected override Track GetTrack() => null;
} }
private class DownloadNotification : ProgressNotification
{
public override bool IsImportant => false;
protected override Notification CreateCompletionNotification() => new SilencedProgressCompletionNotification
{
Activated = CompletionClickAction,
Text = CompletionText
};
private class SilencedProgressCompletionNotification : ProgressCompletionNotification
{
public override bool IsImportant => false;
}
}
} }
} }

View File

@ -14,7 +14,6 @@ namespace osu.Game.Beatmaps
public class BeatmapMetadata : IEquatable<BeatmapMetadata> public class BeatmapMetadata : IEquatable<BeatmapMetadata>
{ {
[DatabaseGenerated(DatabaseGeneratedOption.Identity)] [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[JsonIgnore]
public int ID { get; set; } public int ID { get; set; }
public string Title { get; set; } public string Title { get; set; }

View File

@ -14,6 +14,15 @@ namespace osu.Game.Beatmaps.ControlPoints
public int CompareTo(ControlPoint other) => Time.CompareTo(other.Time); public int CompareTo(ControlPoint other) => Time.CompareTo(other.Time);
public bool Equals(ControlPoint other) => Time.Equals(other?.Time); /// <summary>
/// Whether this <see cref="ControlPoint"/> provides the same parametric changes as another <see cref="ControlPoint"/>.
/// Basically an equality check without considering the <see cref="Time"/>.
/// </summary>
/// <param name="other">The <see cref="ControlPoint"/> to compare to.</param>
/// <returns>Whether this <see cref="ControlPoint"/> is equivalent to <paramref name="other"/>.</returns>
public virtual bool EquivalentTo(ControlPoint other) => true;
public bool Equals(ControlPoint other)
=> EquivalentTo(other) && Time.Equals(other?.Time);
} }
} }

View File

@ -17,5 +17,10 @@ namespace osu.Game.Beatmaps.ControlPoints
} }
private double speedMultiplier = 1; private double speedMultiplier = 1;
public override bool EquivalentTo(ControlPoint other)
=> base.EquivalentTo(other)
&& other is DifficultyControlPoint difficulty
&& SpeedMultiplier.Equals(difficulty.SpeedMultiplier);
} }
} }

View File

@ -14,5 +14,11 @@ namespace osu.Game.Beatmaps.ControlPoints
/// Whether the first bar line of this control point is ignored. /// Whether the first bar line of this control point is ignored.
/// </summary> /// </summary>
public bool OmitFirstBarLine; public bool OmitFirstBarLine;
public override bool EquivalentTo(ControlPoint other)
=> base.EquivalentTo(other)
&& other is EffectControlPoint effect
&& KiaiMode.Equals(effect.KiaiMode)
&& OmitFirstBarLine.Equals(effect.OmitFirstBarLine);
} }
} }

View File

@ -30,5 +30,24 @@ namespace osu.Game.Beatmaps.ControlPoints
Name = sampleName, Name = sampleName,
Volume = SampleVolume, Volume = SampleVolume,
}; };
/// <summary>
/// Applies <see cref="SampleBank"/> and <see cref="SampleVolume"/> to a <see cref="SampleInfo"/> if necessary, returning the modified <see cref="SampleInfo"/>.
/// </summary>
/// <param name="sampleInfo">The <see cref="SampleInfo"/>. This will not be modified.</param>
/// <returns>The modified <see cref="SampleInfo"/>. This does not share a reference with <paramref name="sampleInfo"/>.</returns>
public virtual SampleInfo ApplyTo(SampleInfo sampleInfo)
{
var newSampleInfo = sampleInfo.Clone();
newSampleInfo.Bank = sampleInfo.Bank ?? SampleBank;
newSampleInfo.Volume = sampleInfo.Volume > 0 ? sampleInfo.Volume : SampleVolume;
return newSampleInfo;
}
public override bool EquivalentTo(ControlPoint other)
=> base.EquivalentTo(other)
&& other is SampleControlPoint sample
&& SampleBank.Equals(sample.SampleBank)
&& SampleVolume.Equals(sample.SampleVolume);
} }
} }

View File

@ -23,5 +23,11 @@ namespace osu.Game.Beatmaps.ControlPoints
} }
private double beatLength = 1000; private double beatLength = 1000;
public override bool EquivalentTo(ControlPoint other)
=> base.EquivalentTo(other)
&& other is TimingControlPoint timing
&& TimeSignature.Equals(timing.TimeSignature)
&& BeatLength.Equals(timing.BeatLength);
} }
} }

View File

@ -5,6 +5,7 @@ using System.Linq;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Configuration; using osu.Framework.Configuration;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Game.Online.API.Requests;
namespace osu.Game.Beatmaps.Drawables namespace osu.Game.Beatmaps.Drawables
{ {
@ -19,9 +20,9 @@ namespace osu.Game.Beatmaps.Drawables
private BeatmapManager beatmaps; private BeatmapManager beatmaps;
/// <summary> /// <summary>
/// Whether the associated beatmap set has been downloading (by this instance or any other instance). /// Holds the current download state of the beatmap, whether is has already been downloaded, is in progress, or is not downloaded.
/// </summary> /// </summary>
public readonly BindableBool Downloaded = new BindableBool(); public readonly Bindable<DownloadStatus> DownloadState = new Bindable<DownloadStatus>();
public BeatmapSetDownloader(BeatmapSetInfo set, bool noVideo = false) public BeatmapSetDownloader(BeatmapSetInfo set, bool noVideo = false)
{ {
@ -36,10 +37,16 @@ namespace osu.Game.Beatmaps.Drawables
beatmaps.ItemAdded += setAdded; beatmaps.ItemAdded += setAdded;
beatmaps.ItemRemoved += setRemoved; beatmaps.ItemRemoved += setRemoved;
beatmaps.BeatmapDownloadBegan += downloadBegan;
beatmaps.BeatmapDownloadFailed += downloadFailed;
// initial value // initial value
if (set.OnlineBeatmapSetID != null) if (set.OnlineBeatmapSetID != null && beatmaps.QueryBeatmapSets(s => s.OnlineBeatmapSetID == set.OnlineBeatmapSetID && !s.DeletePending).Any())
Downloaded.Value = beatmaps.QueryBeatmapSets(s => s.OnlineBeatmapSetID == set.OnlineBeatmapSetID && !s.DeletePending).Any(); DownloadState.Value = DownloadStatus.Downloaded;
else if (beatmaps.GetExistingDownload(set) != null)
DownloadState.Value = DownloadStatus.Downloading;
else
DownloadState.Value = DownloadStatus.NotDownloaded;
} }
protected override void Dispose(bool isDisposing) protected override void Dispose(bool isDisposing)
@ -50,6 +57,8 @@ namespace osu.Game.Beatmaps.Drawables
{ {
beatmaps.ItemAdded -= setAdded; beatmaps.ItemAdded -= setAdded;
beatmaps.ItemRemoved -= setRemoved; beatmaps.ItemRemoved -= setRemoved;
beatmaps.BeatmapDownloadBegan -= downloadBegan;
beatmaps.BeatmapDownloadFailed -= downloadFailed;
} }
} }
@ -57,28 +66,45 @@ namespace osu.Game.Beatmaps.Drawables
/// Begin downloading the associated beatmap set. /// Begin downloading the associated beatmap set.
/// </summary> /// </summary>
/// <returns>True if downloading began. False if an existing download is active or completed.</returns> /// <returns>True if downloading began. False if an existing download is active or completed.</returns>
public bool Download() public void Download()
{ {
if (Downloaded.Value) if (DownloadState.Value > DownloadStatus.NotDownloaded)
return false; return;
if (beatmaps.GetExistingDownload(set) != null)
return false;
beatmaps.Download(set, noVideo); beatmaps.Download(set, noVideo);
return true;
DownloadState.Value = DownloadStatus.Downloading;
} }
private void setAdded(BeatmapSetInfo s) private void setAdded(BeatmapSetInfo s)
{ {
if (s.OnlineBeatmapSetID == set.OnlineBeatmapSetID) if (s.OnlineBeatmapSetID == set.OnlineBeatmapSetID)
Downloaded.Value = true; DownloadState.Value = DownloadStatus.Downloaded;
} }
private void setRemoved(BeatmapSetInfo s) private void setRemoved(BeatmapSetInfo s)
{ {
if (s.OnlineBeatmapSetID == set.OnlineBeatmapSetID) if (s.OnlineBeatmapSetID == set.OnlineBeatmapSetID)
Downloaded.Value = false; DownloadState.Value = DownloadStatus.NotDownloaded;
}
private void downloadBegan(DownloadBeatmapSetRequest d)
{
if (d.BeatmapSet.OnlineBeatmapSetID == set.OnlineBeatmapSetID)
DownloadState.Value = DownloadStatus.Downloading;
}
private void downloadFailed(DownloadBeatmapSetRequest d)
{
if (d.BeatmapSet.OnlineBeatmapSetID == set.OnlineBeatmapSetID)
DownloadState.Value = DownloadStatus.NotDownloaded;
}
public enum DownloadStatus
{
NotDownloaded,
Downloading,
Downloaded,
} }
} }
} }

View File

@ -23,7 +23,7 @@ namespace osu.Game.Beatmaps.Drawables
if (value == status) return; if (value == status) return;
status = value; status = value;
statusText.Text = Enum.GetName(typeof(BeatmapSetOnlineStatus), Status)?.ToUpper(); statusText.Text = Enum.GetName(typeof(BeatmapSetOnlineStatus), Status)?.ToUpperInvariant();
} }
} }

View File

@ -9,14 +9,14 @@ using OpenTK.Graphics;
namespace osu.Game.Beatmaps.Drawables namespace osu.Game.Beatmaps.Drawables
{ {
public class DifficultyColouredContainer : Container, IHasAccentColour public abstract class DifficultyColouredContainer : Container, IHasAccentColour
{ {
public Color4 AccentColour { get; set; } public Color4 AccentColour { get; set; }
private readonly BeatmapInfo beatmap; private readonly BeatmapInfo beatmap;
private OsuColour palette; private OsuColour palette;
public DifficultyColouredContainer(BeatmapInfo beatmap) protected DifficultyColouredContainer(BeatmapInfo beatmap)
{ {
this.beatmap = beatmap; this.beatmap = beatmap;
} }

View File

@ -3,10 +3,14 @@
using System; using System;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Graphics.Containers; using osu.Game.Graphics.Containers;
using OpenTK; using OpenTK;
using OpenTK.Graphics;
namespace osu.Game.Beatmaps.Drawables namespace osu.Game.Beatmaps.Drawables
{ {
@ -14,7 +18,8 @@ namespace osu.Game.Beatmaps.Drawables
{ {
private readonly BeatmapInfo beatmap; private readonly BeatmapInfo beatmap;
public DifficultyIcon(BeatmapInfo beatmap) : base(beatmap) public DifficultyIcon(BeatmapInfo beatmap)
: base(beatmap)
{ {
if (beatmap == null) if (beatmap == null)
throw new ArgumentNullException(nameof(beatmap)); throw new ArgumentNullException(nameof(beatmap));
@ -28,16 +33,29 @@ namespace osu.Game.Beatmaps.Drawables
{ {
Children = new Drawable[] Children = new Drawable[]
{ {
new SpriteIcon new CircularContainer
{ {
RelativeSizeAxes = Axes.Both,
Scale = new Vector2(0.84f),
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
Origin = Anchor.Centre, Origin = Anchor.Centre,
Masking = true,
EdgeEffect = new EdgeEffectParameters
{
Colour = Color4.Black.Opacity(0.08f),
Type = EdgeEffectType.Shadow,
Radius = 5,
},
Child = new Box
{
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Colour = AccentColour, Colour = AccentColour,
Icon = FontAwesome.fa_circle },
}, },
new ConstrainedIconContainer new ConstrainedIconContainer
{ {
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
// the null coalesce here is only present to make unit tests work (ruleset dlls aren't copied correctly for testing at the moment) // the null coalesce here is only present to make unit tests work (ruleset dlls aren't copied correctly for testing at the moment)
Icon = beatmap.Ruleset?.CreateInstance().CreateIcon() ?? new SpriteIcon { Icon = FontAwesome.fa_question_circle_o } Icon = beatmap.Ruleset?.CreateInstance().CreateIcon() ?? new SpriteIcon { Icon = FontAwesome.fa_question_circle_o }

View File

@ -62,28 +62,30 @@ namespace osu.Game.Beatmaps.Formats
protected override void ParseLine(Beatmap beatmap, Section section, string line) protected override void ParseLine(Beatmap beatmap, Section section, string line)
{ {
var strippedLine = StripComments(line);
switch (section) switch (section)
{ {
case Section.General: case Section.General:
handleGeneral(line); handleGeneral(strippedLine);
return; return;
case Section.Editor: case Section.Editor:
handleEditor(line); handleEditor(strippedLine);
return; return;
case Section.Metadata: case Section.Metadata:
handleMetadata(line); handleMetadata(line);
return; return;
case Section.Difficulty: case Section.Difficulty:
handleDifficulty(line); handleDifficulty(strippedLine);
return; return;
case Section.Events: case Section.Events:
handleEvent(line); handleEvent(strippedLine);
return; return;
case Section.TimingPoints: case Section.TimingPoints:
handleTimingPoint(line); handleTimingPoint(strippedLine);
return; return;
case Section.HitObjects: case Section.HitObjects:
handleHitObject(line); handleHitObject(strippedLine);
return; return;
} }
@ -289,9 +291,9 @@ namespace osu.Game.Beatmaps.Formats
if (split.Length >= 4) if (split.Length >= 4)
sampleSet = (LegacySampleBank)int.Parse(split[3]); sampleSet = (LegacySampleBank)int.Parse(split[3]);
//SampleBank sampleBank = SampleBank.Default; int customSampleBank = 0;
//if (split.Length >= 5) if (split.Length >= 5)
// sampleBank = (SampleBank)int.Parse(split[4]); customSampleBank = int.Parse(split[4]);
int sampleVolume = defaultSampleVolume; int sampleVolume = defaultSampleVolume;
if (split.Length >= 6) if (split.Length >= 6)
@ -305,22 +307,18 @@ namespace osu.Game.Beatmaps.Formats
bool omitFirstBarSignature = false; bool omitFirstBarSignature = false;
if (split.Length >= 8) if (split.Length >= 8)
{ {
int effectFlags = int.Parse(split[7]); EffectFlags effectFlags = (EffectFlags)int.Parse(split[7]);
kiaiMode = (effectFlags & 1) > 0; kiaiMode = effectFlags.HasFlag(EffectFlags.Kiai);
omitFirstBarSignature = (effectFlags & 8) > 0; omitFirstBarSignature = effectFlags.HasFlag(EffectFlags.OmitFirstBarLine);
} }
string stringSampleSet = sampleSet.ToString().ToLower(); string stringSampleSet = sampleSet.ToString().ToLowerInvariant();
if (stringSampleSet == @"none") if (stringSampleSet == @"none")
stringSampleSet = @"normal"; stringSampleSet = @"normal";
DifficultyControlPoint difficultyPoint = beatmap.ControlPointInfo.DifficultyPointAt(time);
SampleControlPoint samplePoint = beatmap.ControlPointInfo.SamplePointAt(time);
EffectControlPoint effectPoint = beatmap.ControlPointInfo.EffectPointAt(time);
if (timingChange) if (timingChange)
{ {
beatmap.ControlPointInfo.TimingPoints.Add(new TimingControlPoint handleTimingControlPoint(new TimingControlPoint
{ {
Time = time, Time = time,
BeatLength = beatLength, BeatLength = beatLength,
@ -328,41 +326,68 @@ namespace osu.Game.Beatmaps.Formats
}); });
} }
if (speedMultiplier != difficultyPoint.SpeedMultiplier) handleDifficultyControlPoint(new DifficultyControlPoint
{
beatmap.ControlPointInfo.DifficultyPoints.RemoveAll(x => x.Time == time);
beatmap.ControlPointInfo.DifficultyPoints.Add(new DifficultyControlPoint
{ {
Time = time, Time = time,
SpeedMultiplier = speedMultiplier SpeedMultiplier = speedMultiplier
}); });
}
if (stringSampleSet != samplePoint.SampleBank || sampleVolume != samplePoint.SampleVolume) handleEffectControlPoint(new EffectControlPoint
{
beatmap.ControlPointInfo.SamplePoints.Add(new SampleControlPoint
{
Time = time,
SampleBank = stringSampleSet,
SampleVolume = sampleVolume
});
}
if (kiaiMode != effectPoint.KiaiMode || omitFirstBarSignature != effectPoint.OmitFirstBarLine)
{
beatmap.ControlPointInfo.EffectPoints.Add(new EffectControlPoint
{ {
Time = time, Time = time,
KiaiMode = kiaiMode, KiaiMode = kiaiMode,
OmitFirstBarLine = omitFirstBarSignature OmitFirstBarLine = omitFirstBarSignature
}); });
}
handleSampleControlPoint(new LegacySampleControlPoint
{
Time = time,
SampleBank = stringSampleSet,
SampleVolume = sampleVolume,
CustomSampleBank = customSampleBank
});
} }
catch (FormatException e) catch (FormatException e)
{ {
} }
} }
private void handleTimingControlPoint(TimingControlPoint newPoint)
{
beatmap.ControlPointInfo.TimingPoints.Add(newPoint);
}
private void handleDifficultyControlPoint(DifficultyControlPoint newPoint)
{
var existing = beatmap.ControlPointInfo.DifficultyPointAt(newPoint.Time);
if (newPoint.EquivalentTo(existing))
return;
beatmap.ControlPointInfo.DifficultyPoints.RemoveAll(x => x.Time == newPoint.Time);
beatmap.ControlPointInfo.DifficultyPoints.Add(newPoint);
}
private void handleEffectControlPoint(EffectControlPoint newPoint)
{
var existing = beatmap.ControlPointInfo.EffectPointAt(newPoint.Time);
if (newPoint.EquivalentTo(existing))
return;
beatmap.ControlPointInfo.EffectPoints.Add(newPoint);
}
private void handleSampleControlPoint(SampleControlPoint newPoint)
{
var existing = beatmap.ControlPointInfo.SamplePointAt(newPoint.Time);
if (newPoint.EquivalentTo(existing))
return;
beatmap.ControlPointInfo.SamplePoints.Add(newPoint);
}
private void handleHitObject(string line) private void handleHitObject(string line)
{ {
// If the ruleset wasn't specified, assume the osu!standard ruleset. // If the ruleset wasn't specified, assume the osu!standard ruleset.
@ -382,5 +407,13 @@ namespace osu.Game.Beatmaps.Formats
private double getOffsetTime() => ApplyOffsets ? offset : 0; private double getOffsetTime() => ApplyOffsets ? offset : 0;
private double getOffsetTime(double time) => time + (ApplyOffsets ? offset : 0); private double getOffsetTime(double time) => time + (ApplyOffsets ? offset : 0);
[Flags]
internal enum EffectFlags
{
None = 0,
Kiai = 1,
OmitFirstBarLine = 8
}
} }
} }

View File

@ -5,6 +5,8 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using osu.Framework.Logging; using osu.Framework.Logging;
using osu.Game.Audio;
using osu.Game.Beatmaps.ControlPoints;
using OpenTK.Graphics; using OpenTK.Graphics;
namespace osu.Game.Beatmaps.Formats namespace osu.Game.Beatmaps.Formats
@ -55,6 +57,8 @@ namespace osu.Game.Beatmaps.Formats
protected virtual void ParseLine(T output, Section section, string line) protected virtual void ParseLine(T output, Section section, string line)
{ {
line = StripComments(line);
switch (section) switch (section)
{ {
case Section.Colours: case Section.Colours:
@ -63,6 +67,14 @@ namespace osu.Game.Beatmaps.Formats
} }
} }
protected string StripComments(string line)
{
var index = line.IndexOf("//", StringComparison.Ordinal);
if (index > 0)
return line.Substring(0, index);
return line;
}
private bool hasComboColours; private bool hasComboColours;
private void handleColours(T output, string line) private void handleColours(T output, string line)
@ -167,5 +179,25 @@ namespace osu.Game.Beatmaps.Formats
Pass = 2, Pass = 2,
Foreground = 3 Foreground = 3
} }
internal class LegacySampleControlPoint : SampleControlPoint
{
public int CustomSampleBank;
public override SampleInfo ApplyTo(SampleInfo sampleInfo)
{
var baseInfo = base.ApplyTo(sampleInfo);
if (string.IsNullOrEmpty(baseInfo.Suffix) && CustomSampleBank > 1)
baseInfo.Suffix = CustomSampleBank.ToString();
return baseInfo;
}
public override bool EquivalentTo(ControlPoint other)
=> base.EquivalentTo(other)
&& other is LegacySampleControlPoint legacy
&& CustomSampleBank == legacy.CustomSampleBank;
}
} }
} }

View File

@ -42,6 +42,8 @@ namespace osu.Game.Beatmaps.Formats
protected override void ParseLine(Storyboard storyboard, Section section, string line) protected override void ParseLine(Storyboard storyboard, Section section, string line)
{ {
line = StripComments(line);
switch (section) switch (section)
{ {
case Section.Events: case Section.Events:

View File

@ -3,6 +3,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects;
namespace osu.Game.Beatmaps namespace osu.Game.Beatmaps

View File

@ -1,6 +1,9 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Rulesets;
using osu.Game.Rulesets.Objects;
namespace osu.Game.Beatmaps namespace osu.Game.Beatmaps
{ {
/// <summary> /// <summary>

View File

@ -14,6 +14,7 @@ using osu.Framework.IO.File;
using System.IO; using System.IO;
using osu.Game.IO.Serialization; using osu.Game.IO.Serialization;
using osu.Game.Rulesets; using osu.Game.Rulesets;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI;
using osu.Game.Skinning; using osu.Game.Skinning;

View File

@ -3,6 +3,7 @@
using System.Linq; using System.Linq;
using osu.Framework.Audio.Track; using osu.Framework.Audio.Track;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Objects.Types;
namespace osu.Game.Beatmaps namespace osu.Game.Beatmaps

Some files were not shown because too many files have changed in this diff Show More