diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000000..6c327f01b3
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,137 @@
+# Contributing Guidelines
+
+Thank you for showing interest in the development of osu!lazer! We aim to provide a good collaborating environment for everyone involved, and as such have decided to list some of the most important things to keep in mind in the process. The guidelines below have been chosen based on past experience.
+
+These are not "official rules" *per se*, but following them will help everyone deal with things in the most efficient manner.
+
+## Table of contents
+
+1. [I would like to submit an issue!](#i-would-like-to-submit-an-issue)
+2. [I would like to submit a pull request!](#i-would-like-to-submit-a-pull-request)
+
+## I would like to submit an issue!
+
+Issues, bug reports and feature suggestions are welcomed, though please keep in mind that at any point in time, hundreds of issues are open, which vary in severity and the amount of time needed to address them. As such it's not uncommon for issues to remain unresolved for a long time or even closed outright if they are deemed not important enough to fix in the foreseeable future. Issues that are required to "go live" or otherwise achieve parity with stable are prioritised the most.
+
+* **Before submitting an issue, try searching existing issues first.**
+
+ For housekeeping purposes, we close issues that overlap with or duplicate other pre-existing issues - you can help us not to have to do that by searching existing issues yourself first. The issue search box, as well as the issue tag system, are tools you can use to check if an issue has been reported before.
+
+* **When submitting a bug report, please try to include as much detail as possible.**
+
+ Bugs are not equal - some of them will be reproducible every time on pretty much all hardware, while others will be hard to track down due to being specific to particular hardware or even somewhat random in nature. As such, providing as much detail as possible when reporting a bug is hugely appreciated. A good starting set of information consists of:
+
+ * the in-game logs, which are located at:
+ * `%AppData%/osu/logs` (on Windows),
+ * `~/.local/share/osu/logs` (on Linux and macOS),
+ * `Android/Data/sh.ppy.osulazer/logs` (on Android),
+ * on iOS they can be obtained by connecting your device to your desktop and [copying the `logs` directory from the app's own document storage using iTunes](https://support.apple.com/en-us/HT201301#copy-to-computer),
+ * your system specifications (including the operating system and platform you are playing on),
+ * a reproduction scenario (list of steps you have performed leading up to the occurrence of the bug),
+ * a video or picture of the bug, if at all possible.
+
+* **Provide more information when asked to do so.**
+
+ Sometimes when a bug is more elusive or complicated, none of the information listed above will pinpoint a concrete cause of the problem. In this case we will most likely ask you for additional info, such as a Windows Event Log dump or a copy of your local lazer database (`client.db`). Providing that information is beneficial to both parties - we can track down the problem better, and hopefully fix it for you at some point once we know where it is!
+
+* **When submitting a feature proposal, please describe it in the most understandable way you can.**
+
+ Communicating your idea for a feature can often be hard, and we would like to avoid any misunderstandings. As such, please try to explain your idea in a short, but understandable manner - it's best to avoid jargon or terms and references that could be considered obscure. A mock-up picture (doesn't have to be good!) of the feature can also go a long way in explaining.
+
+* **Refrain from posting "+1" comments.**
+
+ If an issue has already been created, saying that you also experience it without providing any additional details doesn't really help us in any way. To express support for a proposal or indicate that you are also affected by a particular bug, you can use comment reactions instead.
+
+* **Refrain from asking if an issue has been resolved yet.**
+
+ As mentioned above, the issue tracker has hundreds of issues open at any given time. Currently the game is being worked on by two members of the core team, and a handful of outside contributors who offer their free time to help out. As such, it can happen that an issue gets placed on the backburner due to being less important; generally posting a comment demanding its resolution some months or years after it is reported is not very likely to increase its priority.
+
+* **Avoid long discussions about non-development topics.**
+
+ GitHub is mostly a developer space, and as such isn't really fit for lengthened discussions about gameplay mechanics (which might not even be in any way confirmed for the final release) and similar non-technical matters. Such matters are probably best addressed at the osu! forums.
+
+## I would like to submit a pull request!
+
+We also welcome pull requests from unaffiliated contributors. The [issue tracker](https://github.com/ppy/osu/issues) should provide plenty of issues that you can work on; we also mark issues that we think would be good for newcomers with the [`good-first-issue`](https://github.com/ppy/osu/issues?q=is%3Aissue+is%3Aopen+label%3Agood-first-issue) label.
+
+However, do keep in mind that the core team is committed to bringing osu!lazer up to par with stable first and foremost, so depending on what your contribution concerns, it might not be merged and released right away. Our approach to managing issues and their priorities is described [in the wiki](https://github.com/ppy/osu/wiki/Project-management).
+
+Here are some key things to note before jumping in:
+
+* **Make sure you are comfortable with C\# and your development environment.**
+
+ While we are accepting of all kinds of contributions, we also have a certain quality standard we'd like to uphold and limited time to review your code. Therefore, we would like to avoid providing entry-level advice, and as such if you're not very familiar with C\# as a programming language, we'd recommend that you start off with a few personal projects to get acquainted with the language's syntax, toolchain and principles of object-oriented programming first.
+
+ In addition, please take the time to take a look at and get acquainted with the [development and testing](https://github.com/ppy/osu-framework/wiki/Development-and-Testing) procedure we have set up.
+
+* **Make sure you are familiar with git and the pull request workflow.**
+
+ [git](https://git-scm.com/) is a distributed version control system that might not be very intuitive at the beginning if you're not familiar with version control. In particular, projects using git have a particular workflow for submitting code changes, which is called the pull request workflow.
+
+ To make things run more smoothly, we recommend that you look up some online resources to familiarise yourself with the git vocabulary and commands, and practice working with forks and submitting pull requests at your own pace. A high-level overview of the process can be found in [this article by GitHub](https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/proposing-changes-to-your-work-with-pull-requests).
+
+* **Double-check designs before starting work on new functionality.**
+
+ When implementing new features, keep in mind that we already have a lot of the UI designed. If you wish to work on something with the intention of having it included in the official distribution, please open an issue for discussion and we will give you what you need from a design perspective to proceed. If you want to make *changes* to the design, we recommend you open an issue with your intentions before spending too much time to ensure no effort is wasted.
+
+* **Make sure to submit pull requests off of a topic branch.**
+
+ As described in the article linked in the previous point, topic branches help you parallelise your work and separate it from the main `master` branch, and additionally are easier for maintainers to work with. Working with multiple `master` branches across many remotes is difficult to keep track of, and it's easy to make a mistake and push to the wrong `master` branch by accident.
+
+* **Refrain from making changes through the GitHub web interface.**
+
+ Even though GitHub provides an option to edit code or replace files in the repository using the web interface, we strongly discourage using it in most scenarios. Editing files this way is inefficient and likely to introduce whitespace or file encoding changes that make it more difficult to review the code.
+
+ Code written through the web interface will also very likely be questioned outright by the reviewers, as it is likely that it has not been properly tested or that it will fail continuous integration checks. We strongly encourage using an IDE like [Visual Studio](https://visualstudio.microsoft.com/), [Visual Studio Code](https://code.visualstudio.com/) or [JetBrains Rider](https://www.jetbrains.com/rider/) instead.
+
+* **Add tests for your code whenever possible.**
+
+ Automated tests are an essential part of a quality and reliable codebase. They help to make the code more maintainable by ensuring it is safe to reorganise (or refactor) the code in various ways, and also prevent regressions - bugs that resurface after having been fixed at some point in the past. If it is viable, please put in the time to add tests, so that the changes you make can last for a (hopefully) very long time.
+
+* **Run tests before opening a pull request.**
+
+ Tying into the previous point, sometimes changes in one part of the codebase can result in unpredictable changes in behaviour in other pieces of the code. This is why it is best to always try to run tests before opening a PR.
+
+ Continuous integration will always run the tests for you (and us), too, but it is best not to rely on it, as there might be many builds queued at any time. Running tests on your own will help you be more certain that at the point of clicking the "Create pull request" button, your changes are as ready as can be.
+
+* **Run code style analysis before opening a pull request.**
+
+ As part of continuous integration, we also run code style analysis, which is supposed to make sure that your code is formatted the same way as all the pre-existing code in the repository. The reason we enforce a particular code style everywhere is to make sure the codebase is consistent in that regard - having one whitespace convention in one place and another one elsewhere causes disorganisation.
+
+* **Make sure that the pull request is complete before opening it.**
+
+ Whether it's fixing a bug or implementing new functionality, it's best that you make sure that the change you want to submit as a pull request is as complete as it can be before clicking the *Create pull request* button. Having to track if a pull request is ready for review or not places additional burden on reviewers.
+
+ Draft pull requests are an option, but use them sparingly and within reason. They are best suited to discuss code changes that cannot be easily described in natural language or have a potential large impact on the future direction of the project. When in doubt, don't open drafts unless a maintainer asks you to do so.
+
+* **Only push code when it's ready.**
+
+ As an extension of the above, when making changes to an already-open PR, please try to only push changes you are reasonably certain of. Pushing after every commit causes the continuous integration build queue to grow in size, slowing down work and taking up time that could be spent verifying other changes.
+
+* **Make sure to keep the *Allow edits from maintainers* check box checked.**
+
+ To speed up the merging process, collaborators and team members will sometimes want to push changes to your branch themselves, to make minor code style adjustments or to otherwise refactor the code without having to describe how they'd like the code to look like in painstaking detail. Having the *Allow edits from maintainers* check box checked lets them do that; without it they are forced to report issues back to you and wait for you to address them.
+
+* **Refrain from continually merging the master branch back to the PR.**
+
+ Unless there are merge conflicts that need resolution, there is no need to keep merging `master` back to a branch over and over again. One of the maintainers will merge `master` themselves before merging the PR itself anyway, and continual merge commits can cause CI to get overwhelmed due to queueing up too many builds.
+
+* **Refrain from force-pushing to the PR branch.**
+
+ Force-pushing should be avoided, as it can lead to accidentally overwriting a maintainer's changes or CI building wrong commits. We value all history in the project, so there is no need to squash or amend commits in most cases.
+
+ The cases in which force-pushing is warranted are very rare (such as accidentally leaking sensitive info in one of the files committed, adding unrelated files, or mis-merging a dependent PR).
+
+* **Be patient when waiting for the code to be reviewed and merged.**
+
+ As much as we'd like to review all contributions as fast as possible, our time is limited, as team members have to work on their own tasks in addition to reviewing code. As such, work needs to be prioritised, and it can unfortunately take weeks or months for your PR to be merged, depending on how important it is deemed to be.
+
+* **Don't mistake criticism of code for criticism of your person.**
+
+ As mentioned before, we are highly committed to quality when it comes to the lazer project. This means that contributions from less experienced community members can take multiple rounds of review to get to a mergeable state. We try our utmost best to never conflate a person with the code they authored, and to keep the discussion focused on the code at all times. Please consider our comments and requests a learning experience, and don't treat it as a personal attack.
+
+* **Feel free to reach out for help.**
+
+ If you're uncertain about some part of the codebase or some inner workings of the game and framework, please reach out either by leaving a comment in the relevant issue or PR thread, or by posting a message in the [development Discord server](https://discord.gg/ppy). We will try to help you as much as we can.
+
+ When it comes to which form of communication is best, GitHub generally lends better to longer-form discussions, while Discord is better for snappy call-and-response answers. Use your best discretion when deciding, and try to keep a single discussion in one place instead of moving back and forth.
diff --git a/README.md b/README.md
index 336bf33f7e..dc3ee63844 100644
--- a/README.md
+++ b/README.md
@@ -93,11 +93,7 @@ JetBrains ReSharper InspectCode is also used for wider rule sets. You can run it
## Contributing
-We welcome all contributions, but keep in mind that we already have a lot of the UI designed. If you wish to work on something with the intention of having it included in the official distribution, please open an issue for discussion and we will give you what you need from a design perspective to proceed. If you want to make *changes* to the design, we recommend you open an issue with your intentions before spending too much time to ensure no effort is wasted.
-
-If you're unsure of what you can help with, check out the [list of open issues](https://github.com/ppy/osu/issues) (especially those with the ["good first issue"](https://github.com/ppy/osu/issues?q=is%3Aopen+label%3Agood-first-issue+sort%3Aupdated-desc) label).
-
-Before starting, please make sure you are familiar with the [development and testing](https://github.com/ppy/osu-framework/wiki/Development-and-Testing) procedure we have set up. New component development, and where possible, bug fixing and debugging existing components **should always be done under VisualTests**.
+When it comes to contributing to the project, the two main things you can do to help out are reporting issues and submitting pull requests. Based on past experiences, we have prepared a [list of contributing guidelines](CONTRIBUTING.md) that should hopefully ease you into our collaboration process and answer the most frequently-asked questions.
Note that while we already have certain standards in place, nothing is set in stone. If you have an issue with the way code is structured, with any libraries we are using, or with any processes involved with contributing, *please* bring it up. We welcome all feedback so we can make contributing to this project as painless as possible.
diff --git a/global.json b/global.json
index 6c793a3f1d..bdb90eb0e9 100644
--- a/global.json
+++ b/global.json
@@ -5,6 +5,6 @@
"version": "3.1.100"
},
"msbuild-sdks": {
- "Microsoft.Build.Traversal": "2.0.34"
+ "Microsoft.Build.Traversal": "2.0.48"
}
}
\ No newline at end of file
diff --git a/osu.Android.props b/osu.Android.props
index b7d08fb120..07be3ab0d2 100644
--- a/osu.Android.props
+++ b/osu.Android.props
@@ -51,7 +51,7 @@
-
-
+
+
diff --git a/osu.Desktop/Program.cs b/osu.Desktop/Program.cs
index bd91bcc933..285a813d97 100644
--- a/osu.Desktop/Program.cs
+++ b/osu.Desktop/Program.cs
@@ -33,13 +33,11 @@ namespace osu.Desktop
if (args.Length > 0 && args[0].Contains('.')) // easy way to check for a file import in args
{
var importer = new ArchiveImportIPCChannel(host);
- // Restore the cwd so relative paths given at the command line work correctly
- Directory.SetCurrentDirectory(cwd);
foreach (var file in args)
{
Console.WriteLine(@"Importing {0}", file);
- if (!importer.ImportAsync(Path.GetFullPath(file)).Wait(3000))
+ if (!importer.ImportAsync(Path.GetFullPath(file, cwd)).Wait(3000))
throw new TimeoutException(@"IPC took too long to send");
}
diff --git a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs
index 90a6e609f0..0de2060e2d 100644
--- a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs
+++ b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs
@@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
switch (obj)
{
- case IHasCurve curveData:
+ case IHasPathWithRepeats curveData:
return new JuiceStream
{
StartTime = obj.StartTime,
@@ -42,7 +42,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
LegacyLastTickOffset = (obj as IHasLegacyLastTickOffset)?.LegacyLastTickOffset ?? 0
}.Yield();
- case IHasEndTime endTime:
+ case IHasDuration endTime:
return new BananaShower
{
StartTime = obj.StartTime,
diff --git a/osu.Game.Rulesets.Catch/Objects/BananaShower.cs b/osu.Game.Rulesets.Catch/Objects/BananaShower.cs
index 3a0b5ace53..04a995c77e 100644
--- a/osu.Game.Rulesets.Catch/Objects/BananaShower.cs
+++ b/osu.Game.Rulesets.Catch/Objects/BananaShower.cs
@@ -7,7 +7,7 @@ using osu.Game.Rulesets.Objects.Types;
namespace osu.Game.Rulesets.Catch.Objects
{
- public class BananaShower : CatchHitObject, IHasEndTime
+ public class BananaShower : CatchHitObject, IHasDuration
{
public override FruitVisualRepresentation VisualRepresentation => FruitVisualRepresentation.Banana;
diff --git a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs
index d32595c2e1..2c96ee2b19 100644
--- a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs
+++ b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs
@@ -14,7 +14,7 @@ using osu.Game.Rulesets.Objects.Types;
namespace osu.Game.Rulesets.Catch.Objects
{
- public class JuiceStream : CatchHitObject, IHasCurve
+ public class JuiceStream : CatchHitObject, IHasPathWithRepeats
{
///
/// Positional distance that results in a duration of one second, before any speed adjustments.
@@ -115,15 +115,15 @@ namespace osu.Game.Rulesets.Catch.Objects
}
}
- public double EndTime
+ public float EndX => X + this.CurvePositionAt(1).X / CatchPlayfield.BASE_WIDTH;
+
+ public double Duration
{
- get => StartTime + this.SpanCount() * Path.Distance / Velocity;
+ get => this.SpanCount() * Path.Distance / Velocity;
set => throw new System.NotSupportedException($"Adjust via {nameof(RepeatCount)} instead"); // can be implemented if/when needed.
}
- public float EndX => X + this.CurvePositionAt(1).X / CatchPlayfield.BASE_WIDTH;
-
- public double Duration => EndTime - StartTime;
+ public double EndTime => StartTime + Duration;
private readonly SliderPath path = new SliderPath();
diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapSampleConversionTest.cs b/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapSampleConversionTest.cs
new file mode 100644
index 0000000000..d8f87195d1
--- /dev/null
+++ b/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapSampleConversionTest.cs
@@ -0,0 +1,76 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using NUnit.Framework;
+using osu.Framework.Utils;
+using osu.Game.Audio;
+using osu.Game.Rulesets.Mania.Objects;
+using osu.Game.Rulesets.Objects;
+using osu.Game.Tests.Beatmaps;
+
+namespace osu.Game.Rulesets.Mania.Tests
+{
+ [TestFixture]
+ public class ManiaBeatmapSampleConversionTest : BeatmapConversionTest, SampleConvertValue>
+ {
+ protected override string ResourceAssembly => "osu.Game.Rulesets.Mania";
+
+ [TestCase("convert-samples")]
+ [TestCase("mania-samples")]
+ public void Test(string name) => base.Test(name);
+
+ protected override IEnumerable CreateConvertValue(HitObject hitObject)
+ {
+ yield return new SampleConvertValue
+ {
+ StartTime = hitObject.StartTime,
+ EndTime = hitObject.GetEndTime(),
+ Column = ((ManiaHitObject)hitObject).Column,
+ NodeSamples = getSampleNames((hitObject as HoldNote)?.NodeSamples)
+ };
+ }
+
+ private IList> getSampleNames(List> hitSampleInfo)
+ => hitSampleInfo?.Select(samples =>
+ (IList)samples.Select(sample => sample.LookupNames.First()).ToList())
+ .ToList();
+
+ protected override Ruleset CreateRuleset() => new ManiaRuleset();
+ }
+
+ public struct SampleConvertValue : IEquatable
+ {
+ ///
+ /// A sane value to account for osu!stable using ints everywhere.
+ ///
+ private const float conversion_lenience = 2;
+
+ public double StartTime;
+ public double EndTime;
+ public int Column;
+ public IList> NodeSamples;
+
+ public bool Equals(SampleConvertValue other)
+ => Precision.AlmostEquals(StartTime, other.StartTime, conversion_lenience)
+ && Precision.AlmostEquals(EndTime, other.EndTime, conversion_lenience)
+ && samplesEqual(NodeSamples, other.NodeSamples);
+
+ private static bool samplesEqual(ICollection> firstSampleList, ICollection> secondSampleList)
+ {
+ if (firstSampleList == null && secondSampleList == null)
+ return true;
+
+ // both items can't be null now, so if any single one is, then they're not equal
+ if (firstSampleList == null || secondSampleList == null)
+ return false;
+
+ return firstSampleList.Count == secondSampleList.Count
+ // cannot use .Zip() without the selector function as it doesn't compile in android test project
+ && firstSampleList.Zip(secondSampleList, (first, second) => (first, second))
+ .All(samples => samples.first.SequenceEqual(samples.second));
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaPlacementBlueprintTestScene.cs b/osu.Game.Rulesets.Mania.Tests/ManiaPlacementBlueprintTestScene.cs
index 1119a66f63..0fe4a3c669 100644
--- a/osu.Game.Rulesets.Mania.Tests/ManiaPlacementBlueprintTestScene.cs
+++ b/osu.Game.Rulesets.Mania.Tests/ManiaPlacementBlueprintTestScene.cs
@@ -8,7 +8,6 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Timing;
using osu.Game.Rulesets.Edit;
-using osu.Game.Rulesets.Mania.Edit;
using osu.Game.Rulesets.Mania.Objects.Drawables;
using osu.Game.Rulesets.Mania.UI;
using osu.Game.Rulesets.Mods;
@@ -19,8 +18,7 @@ using osuTK.Graphics;
namespace osu.Game.Rulesets.Mania.Tests
{
- [Cached(Type = typeof(IManiaHitObjectComposer))]
- public abstract class ManiaPlacementBlueprintTestScene : PlacementBlueprintTestScene, IManiaHitObjectComposer
+ public abstract class ManiaPlacementBlueprintTestScene : PlacementBlueprintTestScene
{
private readonly Column column;
diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaSelectionBlueprintTestScene.cs b/osu.Game.Rulesets.Mania.Tests/ManiaSelectionBlueprintTestScene.cs
index 35fe596e98..149f6582ab 100644
--- a/osu.Game.Rulesets.Mania.Tests/ManiaSelectionBlueprintTestScene.cs
+++ b/osu.Game.Rulesets.Mania.Tests/ManiaSelectionBlueprintTestScene.cs
@@ -4,15 +4,13 @@
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Timing;
-using osu.Game.Rulesets.Mania.Edit;
using osu.Game.Rulesets.Mania.UI;
using osu.Game.Tests.Visual;
using osuTK.Graphics;
namespace osu.Game.Rulesets.Mania.Tests
{
- [Cached(Type = typeof(IManiaHitObjectComposer))]
- public abstract class ManiaSelectionBlueprintTestScene : SelectionBlueprintTestScene, IManiaHitObjectComposer
+ public abstract class ManiaSelectionBlueprintTestScene : SelectionBlueprintTestScene
{
[Cached(Type = typeof(IAdjustableClock))]
private readonly IAdjustableClock clock = new StopwatchClock();
diff --git a/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/mania-stage-left.png b/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/mania-stage-left.png
new file mode 100644
index 0000000000..03ca371c4e
Binary files /dev/null and b/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/mania-stage-left.png differ
diff --git a/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/mania-stage-right.png b/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/mania-stage-right.png
new file mode 100644
index 0000000000..45b7be0255
Binary files /dev/null and b/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/mania-stage-right.png differ
diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneManiaBeatSnapGrid.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneManiaBeatSnapGrid.cs
index ce9546415f..639be0bc11 100644
--- a/osu.Game.Rulesets.Mania.Tests/TestSceneManiaBeatSnapGrid.cs
+++ b/osu.Game.Rulesets.Mania.Tests/TestSceneManiaBeatSnapGrid.cs
@@ -2,23 +2,27 @@
// See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
+using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Input.Events;
using osu.Framework.Timing;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Configuration;
+using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Mania.Edit;
using osu.Game.Rulesets.Mania.UI;
+using osu.Game.Rulesets.Objects.Drawables;
+using osu.Game.Rulesets.UI;
using osu.Game.Rulesets.UI.Scrolling;
using osu.Game.Screens.Edit;
using osu.Game.Tests.Visual;
+using osuTK;
namespace osu.Game.Rulesets.Mania.Tests
{
- [Cached(typeof(IManiaHitObjectComposer))]
- public class TestSceneManiaBeatSnapGrid : EditorClockTestScene, IManiaHitObjectComposer
+ public class TestSceneManiaBeatSnapGrid : EditorClockTestScene
{
[Cached(typeof(IScrollingInfo))]
private ScrollingTestContainer.TestScrollingInfo scrollingInfo = new ScrollingTestContainer.TestScrollingInfo();
@@ -50,7 +54,10 @@ namespace osu.Game.Rulesets.Mania.Tests
{
Clock = new FramedClock(new StopwatchClock())
},
- beatSnapGrid = new ManiaBeatSnapGrid()
+ new TestHitObjectComposer(Playfield)
+ {
+ Child = beatSnapGrid = new ManiaBeatSnapGrid()
+ }
};
}
@@ -67,4 +74,51 @@ namespace osu.Game.Rulesets.Mania.Tests
public ManiaPlayfield Playfield { get; }
}
+
+ public class TestHitObjectComposer : HitObjectComposer
+ {
+ public override Playfield Playfield { get; }
+ public override IEnumerable HitObjects => Enumerable.Empty();
+ public override bool CursorInPlacementArea => false;
+
+ public TestHitObjectComposer(Playfield playfield)
+ {
+ Playfield = playfield;
+ }
+
+ public Drawable Child
+ {
+ set => InternalChild = value;
+ }
+
+ public override SnapResult SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition)
+ {
+ throw new System.NotImplementedException();
+ }
+
+ public override float GetBeatSnapDistanceAt(double referenceTime)
+ {
+ throw new System.NotImplementedException();
+ }
+
+ public override float DurationToDistance(double referenceTime, double duration)
+ {
+ throw new System.NotImplementedException();
+ }
+
+ public override double DistanceToDuration(double referenceTime, float distance)
+ {
+ throw new System.NotImplementedException();
+ }
+
+ public override double GetSnappedDurationFromDistance(double referenceTime, float distance)
+ {
+ throw new System.NotImplementedException();
+ }
+
+ public override float GetSnappedDistanceFromDistance(double referenceTime, float distance)
+ {
+ throw new System.NotImplementedException();
+ }
+ }
}
diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneNotes.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneNotes.cs
index ea6a1e2e6a..dd5fd93710 100644
--- a/osu.Game.Rulesets.Mania.Tests/TestSceneNotes.cs
+++ b/osu.Game.Rulesets.Mania.Tests/TestSceneNotes.cs
@@ -156,7 +156,7 @@ namespace osu.Game.Rulesets.Mania.Tests
foreach (var obj in content.OfType())
{
- if (!(obj.HitObject is IHasEndTime endTime))
+ if (!(obj.HitObject is IHasDuration endTime))
continue;
foreach (var nested in obj.NestedHitObjects)
diff --git a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs
index 1c8116754f..b025ac7992 100644
--- a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs
+++ b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs
@@ -6,6 +6,7 @@ using System;
using System.Linq;
using System.Collections.Generic;
using osu.Framework.Utils;
+using osu.Game.Audio;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
@@ -54,7 +55,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
}
else
{
- float percentSliderOrSpinner = (float)beatmap.HitObjects.Count(h => h is IHasEndTime) / beatmap.HitObjects.Count;
+ float percentSliderOrSpinner = (float)beatmap.HitObjects.Count(h => h is IHasDuration) / beatmap.HitObjects.Count;
if (percentSliderOrSpinner < 0.2)
TargetColumns = 7;
else if (percentSliderOrSpinner < 0.3 || roundedCircleSize >= 5)
@@ -175,7 +176,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
break;
}
- case IHasEndTime endTimeData:
+ case IHasDuration endTimeData:
{
conversion = new EndTimeObjectPatternGenerator(Random, original, beatmap, originalBeatmap);
@@ -231,7 +232,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
var pattern = new Pattern();
- if (HitObject is IHasEndTime endTimeData)
+ if (HitObject is IHasDuration endTimeData)
{
pattern.Add(new HoldNote
{
@@ -239,7 +240,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
Duration = endTimeData.Duration,
Column = column,
Samples = HitObject.Samples,
- NodeSamples = (HitObject as IHasRepeats)?.NodeSamples
+ NodeSamples = (HitObject as IHasRepeats)?.NodeSamples ?? defaultNodeSamples
});
}
else if (HitObject is IHasXPosition)
@@ -254,6 +255,16 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
return pattern;
}
+
+ ///
+ /// osu!mania-specific beatmaps in stable only play samples at the start of the hold note.
+ ///
+ private List> defaultNodeSamples
+ => new List>
+ {
+ HitObject.Samples,
+ new List()
+ };
}
}
}
diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs
index d8d5b67c0e..9fbdf58e21 100644
--- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs
+++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs
@@ -472,15 +472,23 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
///
/// The time to retrieve the sample info list from.
///
- private IList sampleInfoListAt(double time)
+ private IList sampleInfoListAt(double time) => nodeSamplesAt(time)?.First() ?? HitObject.Samples;
+
+ ///
+ /// Retrieves the list of node samples that occur at time greater than or equal to .
+ ///
+ /// The time to retrieve node samples at.
+ private List> nodeSamplesAt(double time)
{
- if (!(HitObject is IHasCurve curveData))
- return HitObject.Samples;
+ if (!(HitObject is IHasPathWithRepeats curveData))
+ return null;
double segmentTime = (EndTime - HitObject.StartTime) / spanCount;
int index = (int)(segmentTime == 0 ? 0 : (time - HitObject.StartTime) / segmentTime);
- return curveData.NodeSamples[index];
+
+ // avoid slicing the list & creating copies, if at all possible.
+ return index == 0 ? curveData.NodeSamples : curveData.NodeSamples.Skip(index).ToList();
}
///
@@ -511,7 +519,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
Duration = endTime - startTime,
Column = column,
Samples = HitObject.Samples,
- NodeSamples = (HitObject as IHasRepeats)?.NodeSamples
+ NodeSamples = nodeSamplesAt(startTime)
};
}
diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/EndTimeObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/EndTimeObjectPatternGenerator.cs
index 907bed0d65..d5286a3779 100644
--- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/EndTimeObjectPatternGenerator.cs
+++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/EndTimeObjectPatternGenerator.cs
@@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
public EndTimeObjectPatternGenerator(FastRandom random, HitObject hitObject, ManiaBeatmap beatmap, IBeatmap originalBeatmap)
: base(random, hitObject, beatmap, new Pattern(), originalBeatmap)
{
- endTime = (HitObject as IHasEndTime)?.EndTime ?? 0;
+ endTime = (HitObject as IHasDuration)?.EndTime ?? 0;
}
public override IEnumerable Generate()
diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNotePlacementBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNotePlacementBlueprint.cs
index 500b26917d..b5ec1e1a2a 100644
--- a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNotePlacementBlueprint.cs
+++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNotePlacementBlueprint.cs
@@ -20,9 +20,6 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
private readonly EditNotePiece headPiece;
private readonly EditNotePiece tailPiece;
- [Resolved]
- private IManiaHitObjectComposer composer { get; set; }
-
[Resolved]
private IScrollingInfo scrollingInfo { get; set; }
diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaSelectionBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaSelectionBlueprint.cs
index 0089a9fbee..384f49d9b2 100644
--- a/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaSelectionBlueprint.cs
+++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaSelectionBlueprint.cs
@@ -18,9 +18,6 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
[Resolved]
private IScrollingInfo scrollingInfo { get; set; }
- [Resolved]
- private IManiaHitObjectComposer composer { get; set; }
-
protected ManiaSelectionBlueprint(DrawableHitObject drawableObject)
: base(drawableObject)
{
diff --git a/osu.Game.Rulesets.Mania/Edit/IManiaHitObjectComposer.cs b/osu.Game.Rulesets.Mania/Edit/IManiaHitObjectComposer.cs
deleted file mode 100644
index 3818d0e15d..0000000000
--- a/osu.Game.Rulesets.Mania/Edit/IManiaHitObjectComposer.cs
+++ /dev/null
@@ -1,12 +0,0 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
-// See the LICENCE file in the repository root for full licence text.
-
-using osu.Game.Rulesets.Mania.UI;
-
-namespace osu.Game.Rulesets.Mania.Edit
-{
- public interface IManiaHitObjectComposer
- {
- ManiaPlayfield Playfield { get; }
- }
-}
diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaBeatSnapGrid.cs b/osu.Game.Rulesets.Mania/Edit/ManiaBeatSnapGrid.cs
index b5b6c08fca..2028cae9a5 100644
--- a/osu.Game.Rulesets.Mania/Edit/ManiaBeatSnapGrid.cs
+++ b/osu.Game.Rulesets.Mania/Edit/ManiaBeatSnapGrid.cs
@@ -11,6 +11,8 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Shapes;
using osu.Game.Beatmaps;
using osu.Game.Graphics;
+using osu.Game.Rulesets.Edit;
+using osu.Game.Rulesets.Mania.UI;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.UI.Scrolling;
@@ -63,9 +65,9 @@ namespace osu.Game.Rulesets.Mania.Edit
private (double start, double end)? selectionTimeRange;
[BackgroundDependencyLoader]
- private void load(IManiaHitObjectComposer composer)
+ private void load(HitObjectComposer composer)
{
- foreach (var stage in composer.Playfield.Stages)
+ foreach (var stage in ((ManiaPlayfield)composer.Playfield).Stages)
{
foreach (var column in stage.Columns)
{
diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs
index 73cbadc97c..7e2469a794 100644
--- a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs
+++ b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs
@@ -13,6 +13,7 @@ using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces;
using osu.Game.Rulesets.Mania.UI;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects;
+using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.UI;
using osu.Game.Rulesets.UI.Scrolling;
using osu.Game.Screens.Edit.Compose.Components;
@@ -20,8 +21,7 @@ using osuTK;
namespace osu.Game.Rulesets.Mania.Edit
{
- [Cached(Type = typeof(IManiaHitObjectComposer))]
- public class ManiaHitObjectComposer : HitObjectComposer, IManiaHitObjectComposer
+ public class ManiaHitObjectComposer : HitObjectComposer
{
private DrawableManiaEditRuleset drawableRuleset;
private ManiaBeatSnapGrid beatSnapGrid;
@@ -50,7 +50,7 @@ namespace osu.Game.Rulesets.Mania.Edit
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
=> dependencies = new DependencyContainer(base.CreateChildDependencies(parent));
- public ManiaPlayfield Playfield => ((ManiaPlayfield)drawableRuleset.Playfield);
+ public new ManiaPlayfield Playfield => ((ManiaPlayfield)drawableRuleset.Playfield);
public IScrollingInfo ScrollingInfo => drawableRuleset.ScrollingInfo;
@@ -89,7 +89,8 @@ namespace osu.Game.Rulesets.Mania.Edit
return drawableRuleset;
}
- protected override ComposeBlueprintContainer CreateBlueprintContainer() => new ManiaBlueprintContainer(drawableRuleset.Playfield.AllHitObjects);
+ protected override ComposeBlueprintContainer CreateBlueprintContainer(IEnumerable hitObjects)
+ => new ManiaBlueprintContainer(hitObjects);
protected override IReadOnlyList CompositionTools => new HitObjectCompositionTool[]
{
diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs b/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs
index 4ea71652bc..65f40d7d0a 100644
--- a/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs
+++ b/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs
@@ -4,6 +4,7 @@
using System;
using System.Linq;
using osu.Framework.Allocation;
+using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Mania.Edit.Blueprints;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.UI.Scrolling;
@@ -17,7 +18,7 @@ namespace osu.Game.Rulesets.Mania.Edit
private IScrollingInfo scrollingInfo { get; set; }
[Resolved]
- private IManiaHitObjectComposer composer { get; set; }
+ private HitObjectComposer composer { get; set; }
public override bool HandleMovement(MoveSelectionEvent moveEvent)
{
@@ -31,7 +32,9 @@ namespace osu.Game.Rulesets.Mania.Edit
private void performColumnMovement(int lastColumn, MoveSelectionEvent moveEvent)
{
- var currentColumn = composer.Playfield.GetColumnByPosition(moveEvent.ScreenSpacePosition);
+ var maniaPlayfield = ((ManiaHitObjectComposer)composer).Playfield;
+
+ var currentColumn = maniaPlayfield.GetColumnByPosition(moveEvent.ScreenSpacePosition);
if (currentColumn == null)
return;
@@ -50,7 +53,7 @@ namespace osu.Game.Rulesets.Mania.Edit
maxColumn = obj.Column;
}
- columnDelta = Math.Clamp(columnDelta, -minColumn, composer.Playfield.TotalColumns - 1 - maxColumn);
+ columnDelta = Math.Clamp(columnDelta, -minColumn, maniaPlayfield.TotalColumns - 1 - maxColumn);
foreach (var obj in SelectedHitObjects.OfType())
obj.Column += columnDelta;
diff --git a/osu.Game.Rulesets.Mania/Objects/HoldNote.cs b/osu.Game.Rulesets.Mania/Objects/HoldNote.cs
index e6f722a8a9..a100c9a58e 100644
--- a/osu.Game.Rulesets.Mania/Objects/HoldNote.cs
+++ b/osu.Game.Rulesets.Mania/Objects/HoldNote.cs
@@ -15,7 +15,7 @@ namespace osu.Game.Rulesets.Mania.Objects
///
/// Represents a hit object which requires pressing, holding, and releasing a key.
///
- public class HoldNote : ManiaHitObject, IHasEndTime
+ public class HoldNote : ManiaHitObject, IHasDuration
{
public double EndTime
{
diff --git a/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/convert-samples-expected-conversion.json b/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/convert-samples-expected-conversion.json
new file mode 100644
index 0000000000..b8ce85eef5
--- /dev/null
+++ b/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/convert-samples-expected-conversion.json
@@ -0,0 +1,30 @@
+{
+ "Mappings": [{
+ "StartTime": 1000.0,
+ "Objects": [{
+ "StartTime": 1000.0,
+ "EndTime": 2750.0,
+ "Column": 1,
+ "NodeSamples": [
+ ["normal-hitnormal"],
+ ["soft-hitnormal"],
+ ["drum-hitnormal"]
+ ]
+ }, {
+ "StartTime": 1875.0,
+ "EndTime": 2750.0,
+ "Column": 0,
+ "NodeSamples": [
+ ["soft-hitnormal"],
+ ["drum-hitnormal"]
+ ]
+ }]
+ }, {
+ "StartTime": 3750.0,
+ "Objects": [{
+ "StartTime": 3750.0,
+ "EndTime": 3750.0,
+ "Column": 3
+ }]
+ }]
+}
\ No newline at end of file
diff --git a/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/convert-samples.osu b/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/convert-samples.osu
new file mode 100644
index 0000000000..16b73992d2
--- /dev/null
+++ b/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/convert-samples.osu
@@ -0,0 +1,16 @@
+osu file format v14
+
+[Difficulty]
+HPDrainRate:5
+CircleSize:5
+OverallDifficulty:5
+ApproachRate:5
+SliderMultiplier:1.4
+SliderTickRate:1
+
+[TimingPoints]
+0,500,4,1,0,100,1,0
+
+[HitObjects]
+88,99,1000,6,0,L|306:259,2,245,0|0|0,1:0|2:0|3:0,0:0:0:0:
+259,118,3750,1,0,0:0:0:0:
diff --git a/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/mania-samples-expected-conversion.json b/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/mania-samples-expected-conversion.json
new file mode 100644
index 0000000000..e22540614d
--- /dev/null
+++ b/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/mania-samples-expected-conversion.json
@@ -0,0 +1,25 @@
+{
+ "Mappings": [{
+ "StartTime": 500.0,
+ "Objects": [{
+ "StartTime": 500.0,
+ "EndTime": 1500.0,
+ "Column": 0,
+ "NodeSamples": [
+ ["normal-hitnormal"],
+ []
+ ]
+ }]
+ }, {
+ "StartTime": 2000.0,
+ "Objects": [{
+ "StartTime": 2000.0,
+ "EndTime": 3000.0,
+ "Column": 2,
+ "NodeSamples": [
+ ["drum-hitnormal"],
+ []
+ ]
+ }]
+ }]
+}
\ No newline at end of file
diff --git a/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/mania-samples.osu b/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/mania-samples.osu
new file mode 100644
index 0000000000..7c75b45e5f
--- /dev/null
+++ b/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/mania-samples.osu
@@ -0,0 +1,19 @@
+osu file format v14
+
+[General]
+Mode: 3
+
+[Difficulty]
+HPDrainRate:5
+CircleSize:5
+OverallDifficulty:5
+ApproachRate:5
+SliderMultiplier:1.4
+SliderTickRate:1
+
+[TimingPoints]
+0,500,4,1,0,100,1,0
+
+[HitObjects]
+51,192,500,128,0,1500:1:0:0:0:
+256,192,2000,128,0,3000:3:0:0:0:
diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyStageBackground.cs b/osu.Game.Rulesets.Mania/Skinning/LegacyStageBackground.cs
index 7680526ac4..f177284399 100644
--- a/osu.Game.Rulesets.Mania/Skinning/LegacyStageBackground.cs
+++ b/osu.Game.Rulesets.Mania/Skinning/LegacyStageBackground.cs
@@ -52,10 +52,10 @@ namespace osu.Game.Rulesets.Mania.Skinning
base.Update();
if (leftSprite?.Height > 0)
- leftSprite.Scale = new Vector2(DrawHeight / leftSprite.Height);
+ leftSprite.Scale = new Vector2(1, DrawHeight / leftSprite.Height);
if (rightSprite?.Height > 0)
- rightSprite.Scale = new Vector2(DrawHeight / rightSprite.Height);
+ rightSprite.Scale = new Vector2(1, DrawHeight / rightSprite.Height);
}
}
}
diff --git a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs
index 147d74c929..fcad356a1c 100644
--- a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs
+++ b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs
@@ -29,7 +29,7 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
switch (original)
{
- case IHasCurve curveData:
+ case IHasPathWithRepeats curveData:
return new Slider
{
StartTime = original.StartTime,
@@ -46,7 +46,7 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
TickDistanceMultiplier = beatmap.BeatmapInfo.BeatmapVersion < 8 ? 1f / beatmap.ControlPointInfo.DifficultyPointAt(original.StartTime).SpeedMultiplier : 1
}.Yield();
- case IHasEndTime endTimeData:
+ case IHasDuration endTimeData:
return new Spinner
{
StartTime = original.StartTime,
diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/OsuSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/OsuSelectionBlueprint.cs
index b0e13808a5..8dd550bb96 100644
--- a/osu.Game.Rulesets.Osu/Edit/Blueprints/OsuSelectionBlueprint.cs
+++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/OsuSelectionBlueprint.cs
@@ -12,6 +12,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints
{
protected new T HitObject => (T)DrawableObject.HitObject;
+ protected override bool AlwaysShowWhenSelected => true;
+
protected OsuSelectionBlueprint(DrawableHitObject drawableObject)
: base(drawableObject)
{
diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs
index de5c1e54d7..37019a7a05 100644
--- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs
+++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs
@@ -13,6 +13,7 @@ using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Edit.Tools;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects;
+using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.UI;
using osu.Game.Screens.Edit.Compose.Components;
@@ -46,7 +47,8 @@ namespace osu.Game.Rulesets.Osu.Edit
EditorBeatmap.PlacementObject.ValueChanged += _ => updateDistanceSnapGrid();
}
- protected override ComposeBlueprintContainer CreateBlueprintContainer() => new OsuBlueprintContainer(HitObjects);
+ protected override ComposeBlueprintContainer CreateBlueprintContainer(IEnumerable hitObjects)
+ => new OsuBlueprintContainer(hitObjects);
private DistanceSnapGrid distanceSnapGrid;
private Container distanceSnapGridContainer;
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs
index 7b1941b7f9..5d191119b9 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs
@@ -70,7 +70,7 @@ namespace osu.Game.Rulesets.Osu.Mods
break;
// already hit or beyond the hittable end time.
- if (h.IsHit || (h.HitObject is IHasEndTime hasEnd && time > hasEnd.EndTime))
+ if (h.IsHit || (h.HitObject is IHasDuration hasEnd && time > hasEnd.EndTime))
continue;
switch (h)
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs b/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs
index 297a0fea79..3cad52faeb 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs
@@ -61,7 +61,7 @@ namespace osu.Game.Rulesets.Osu.Mods
}
// Keep wiggling sliders and spinners for their duration
- if (!(osuObject is IHasEndTime endTime))
+ if (!(osuObject is IHasDuration endTime))
return;
amountWiggles = (int)(endTime.Duration / wiggle_duration);
diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs
index 6ba0e1c6aa..705e88040f 100644
--- a/osu.Game.Rulesets.Osu/Objects/Slider.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs
@@ -17,16 +17,16 @@ using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Osu.Objects
{
- public class Slider : OsuHitObject, IHasCurve
+ public class Slider : OsuHitObject, IHasPathWithRepeats
{
- public double EndTime
+ public double EndTime => StartTime + this.SpanCount() * Path.Distance / Velocity;
+
+ public double Duration
{
- get => StartTime + this.SpanCount() * Path.Distance / Velocity;
+ get => EndTime - StartTime;
set => throw new System.NotSupportedException($"Adjust via {nameof(RepeatCount)} instead"); // can be implemented if/when needed.
}
- public double Duration => EndTime - StartTime;
-
private readonly Cached endPositionCache = new Cached();
public override Vector2 EndPosition => endPositionCache.IsValid ? endPositionCache.Value : endPositionCache.Value = Position + this.CurvePositionAt(1);
diff --git a/osu.Game.Rulesets.Osu/Objects/Spinner.cs b/osu.Game.Rulesets.Osu/Objects/Spinner.cs
index 0b8d03d118..418375c090 100644
--- a/osu.Game.Rulesets.Osu/Objects/Spinner.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Spinner.cs
@@ -11,7 +11,7 @@ using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Osu.Objects
{
- public class Spinner : OsuHitObject, IHasEndTime
+ public class Spinner : OsuHitObject, IHasDuration
{
public double EndTime
{
diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs
index 37df5ec540..9bcb3abc63 100644
--- a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs
+++ b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs
@@ -237,6 +237,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
{
Position = new Vector2(part.Position.X - size.X / 2, part.Position.Y + size.Y / 2),
TexturePosition = textureRect.BottomLeft,
+ TextureRect = new Vector4(0, 0, 1, 1),
Colour = DrawColourInfo.Colour.BottomLeft.Linear,
Time = part.Time
});
@@ -245,6 +246,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
{
Position = new Vector2(part.Position.X + size.X / 2, part.Position.Y + size.Y / 2),
TexturePosition = textureRect.BottomRight,
+ TextureRect = new Vector4(0, 0, 1, 1),
Colour = DrawColourInfo.Colour.BottomRight.Linear,
Time = part.Time
});
@@ -253,6 +255,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
{
Position = new Vector2(part.Position.X + size.X / 2, part.Position.Y - size.Y / 2),
TexturePosition = textureRect.TopRight,
+ TextureRect = new Vector4(0, 0, 1, 1),
Colour = DrawColourInfo.Colour.TopRight.Linear,
Time = part.Time
});
@@ -261,6 +264,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
{
Position = new Vector2(part.Position.X - size.X / 2, part.Position.Y - size.Y / 2),
TexturePosition = textureRect.TopLeft,
+ TextureRect = new Vector4(0, 0, 1, 1),
Colour = DrawColourInfo.Colour.TopLeft.Linear,
Time = part.Time
});
@@ -290,6 +294,9 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
[VertexMember(2, VertexAttribPointerType.Float)]
public Vector2 TexturePosition;
+ [VertexMember(4, VertexAttribPointerType.Float)]
+ public Vector4 TextureRect;
+
[VertexMember(1, VertexAttribPointerType.Float)]
public float Time;
diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneEditor.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneEditor.cs
new file mode 100644
index 0000000000..089a7ad00b
--- /dev/null
+++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneEditor.cs
@@ -0,0 +1,17 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using NUnit.Framework;
+using osu.Game.Tests.Visual;
+
+namespace osu.Game.Rulesets.Taiko.Tests
+{
+ [TestFixture]
+ public class TestSceneEditor : EditorTestScene
+ {
+ public TestSceneEditor()
+ : base(new TaikoRuleset())
+ {
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoHitObjectComposer.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoHitObjectComposer.cs
new file mode 100644
index 0000000000..34d5fdf857
--- /dev/null
+++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoHitObjectComposer.cs
@@ -0,0 +1,55 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using NUnit.Framework;
+using osu.Framework.Allocation;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Game.Rulesets.Edit;
+using osu.Game.Rulesets.Taiko.Beatmaps;
+using osu.Game.Rulesets.Taiko.Edit;
+using osu.Game.Rulesets.Taiko.Objects;
+using osu.Game.Screens.Edit;
+using osu.Game.Tests.Visual;
+
+namespace osu.Game.Rulesets.Taiko.Tests
+{
+ public class TestSceneTaikoHitObjectComposer : EditorClockTestScene
+ {
+ [SetUp]
+ public void Setup() => Schedule(() =>
+ {
+ BeatDivisor.Value = 8;
+ Clock.Seek(0);
+
+ Child = new TestComposer { RelativeSizeAxes = Axes.Both };
+ });
+
+ [Test]
+ public void BasicTest()
+ {
+ }
+
+ private class TestComposer : CompositeDrawable
+ {
+ [Cached(typeof(EditorBeatmap))]
+ [Cached(typeof(IBeatSnapProvider))]
+ public readonly EditorBeatmap EditorBeatmap;
+
+ public TestComposer()
+ {
+ InternalChildren = new Drawable[]
+ {
+ EditorBeatmap = new EditorBeatmap(new TaikoBeatmap())
+ {
+ BeatmapInfo = { Ruleset = new TaikoRuleset().RulesetInfo }
+ },
+ new TaikoHitObjectComposer(new TaikoRuleset())
+ };
+
+ for (int i = 0; i < 10; i++)
+ EditorBeatmap.Add(new Hit { StartTime = 125 * i });
+ }
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs
index d324441285..78550ed270 100644
--- a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs
+++ b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs
@@ -114,7 +114,7 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
if (!isForCurrentRuleset && tickSpacing > 0 && osuDuration < 2 * speedAdjustedBeatLength)
{
- List> allSamples = obj is IHasCurve curveData ? curveData.NodeSamples : new List>(new[] { samples });
+ List> allSamples = obj is IHasPathWithRepeats curveData ? curveData.NodeSamples : new List>(new[] { samples });
int i = 0;
@@ -150,7 +150,7 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
break;
}
- case IHasEndTime endTimeData:
+ case IHasDuration endTimeData:
{
double hitMultiplier = BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty, 3, 5, 7.5) * swell_hit_multiplier;
diff --git a/osu.Game.Rulesets.Taiko/Edit/Blueprints/DrumRollPlacementBlueprint.cs b/osu.Game.Rulesets.Taiko/Edit/Blueprints/DrumRollPlacementBlueprint.cs
new file mode 100644
index 0000000000..eb07ce7635
--- /dev/null
+++ b/osu.Game.Rulesets.Taiko/Edit/Blueprints/DrumRollPlacementBlueprint.cs
@@ -0,0 +1,15 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Game.Rulesets.Taiko.Objects;
+
+namespace osu.Game.Rulesets.Taiko.Edit.Blueprints
+{
+ public class DrumRollPlacementBlueprint : TaikoSpanPlacementBlueprint
+ {
+ public DrumRollPlacementBlueprint()
+ : base(new DrumRoll())
+ {
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Taiko/Edit/Blueprints/HitPiece.cs b/osu.Game.Rulesets.Taiko/Edit/Blueprints/HitPiece.cs
new file mode 100644
index 0000000000..b02e3aa9ba
--- /dev/null
+++ b/osu.Game.Rulesets.Taiko/Edit/Blueprints/HitPiece.cs
@@ -0,0 +1,35 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Shapes;
+using osuTK.Graphics;
+
+namespace osu.Game.Rulesets.Taiko.Edit.Blueprints
+{
+ public class HitPiece : CompositeDrawable
+ {
+ public HitPiece()
+ {
+ Origin = Anchor.Centre;
+
+ InternalChild = new CircularContainer
+ {
+ Masking = true,
+ BorderThickness = 10,
+ BorderColour = Color4.Yellow,
+ RelativeSizeAxes = Axes.Both,
+ Children = new Drawable[]
+ {
+ new Box
+ {
+ AlwaysPresent = true,
+ Alpha = 0,
+ RelativeSizeAxes = Axes.Both
+ }
+ }
+ };
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Taiko/Edit/Blueprints/HitPlacementBlueprint.cs b/osu.Game.Rulesets.Taiko/Edit/Blueprints/HitPlacementBlueprint.cs
new file mode 100644
index 0000000000..c5191ab241
--- /dev/null
+++ b/osu.Game.Rulesets.Taiko/Edit/Blueprints/HitPlacementBlueprint.cs
@@ -0,0 +1,52 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Input.Events;
+using osu.Game.Rulesets.Edit;
+using osu.Game.Rulesets.Taiko.Objects;
+using osu.Game.Rulesets.Taiko.UI;
+using osuTK;
+using osuTK.Input;
+
+namespace osu.Game.Rulesets.Taiko.Edit.Blueprints
+{
+ public class HitPlacementBlueprint : PlacementBlueprint
+ {
+ private readonly HitPiece piece;
+
+ private static Hit hit;
+
+ public HitPlacementBlueprint()
+ : base(hit = new Hit())
+ {
+ InternalChild = piece = new HitPiece
+ {
+ Size = new Vector2(TaikoHitObject.DEFAULT_SIZE * TaikoPlayfield.DEFAULT_HEIGHT)
+ };
+ }
+
+ protected override bool OnMouseDown(MouseDownEvent e)
+ {
+ switch (e.Button)
+ {
+ case MouseButton.Left:
+ hit.Type = HitType.Centre;
+ EndPlacement(true);
+ return true;
+
+ case MouseButton.Right:
+ hit.Type = HitType.Rim;
+ EndPlacement(true);
+ return true;
+ }
+
+ return false;
+ }
+
+ public override void UpdatePosition(SnapResult result)
+ {
+ piece.Position = ToLocalSpace(result.ScreenSpacePosition);
+ base.UpdatePosition(result);
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Taiko/Edit/Blueprints/LengthPiece.cs b/osu.Game.Rulesets.Taiko/Edit/Blueprints/LengthPiece.cs
new file mode 100644
index 0000000000..6b651fd739
--- /dev/null
+++ b/osu.Game.Rulesets.Taiko/Edit/Blueprints/LengthPiece.cs
@@ -0,0 +1,40 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Shapes;
+using osuTK.Graphics;
+
+namespace osu.Game.Rulesets.Taiko.Edit.Blueprints
+{
+ public class LengthPiece : CompositeDrawable
+ {
+ public LengthPiece()
+ {
+ Origin = Anchor.CentreLeft;
+
+ InternalChild = new Container
+ {
+ Masking = true,
+ Colour = Color4.Yellow,
+ RelativeSizeAxes = Axes.Both,
+ Children = new Drawable[]
+ {
+ new Box
+ {
+ RelativeSizeAxes = Axes.X,
+ Height = 8,
+ },
+ new Box
+ {
+ Origin = Anchor.BottomLeft,
+ Anchor = Anchor.BottomLeft,
+ RelativeSizeAxes = Axes.X,
+ Height = 8,
+ }
+ }
+ };
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Taiko/Edit/Blueprints/SwellPlacementBlueprint.cs b/osu.Game.Rulesets.Taiko/Edit/Blueprints/SwellPlacementBlueprint.cs
new file mode 100644
index 0000000000..95fa82a0f2
--- /dev/null
+++ b/osu.Game.Rulesets.Taiko/Edit/Blueprints/SwellPlacementBlueprint.cs
@@ -0,0 +1,15 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Game.Rulesets.Taiko.Objects;
+
+namespace osu.Game.Rulesets.Taiko.Edit.Blueprints
+{
+ public class SwellPlacementBlueprint : TaikoSpanPlacementBlueprint
+ {
+ public SwellPlacementBlueprint()
+ : base(new Swell())
+ {
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Taiko/Edit/Blueprints/TaikoSelectionBlueprint.cs b/osu.Game.Rulesets.Taiko/Edit/Blueprints/TaikoSelectionBlueprint.cs
new file mode 100644
index 0000000000..62f69122cc
--- /dev/null
+++ b/osu.Game.Rulesets.Taiko/Edit/Blueprints/TaikoSelectionBlueprint.cs
@@ -0,0 +1,40 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Graphics;
+using osu.Game.Rulesets.Edit;
+using osu.Game.Rulesets.Objects.Drawables;
+using osuTK;
+
+namespace osu.Game.Rulesets.Taiko.Edit.Blueprints
+{
+ public class TaikoSelectionBlueprint : OverlaySelectionBlueprint
+ {
+ public TaikoSelectionBlueprint(DrawableHitObject hitObject)
+ : base(hitObject)
+ {
+ RelativeSizeAxes = Axes.None;
+
+ AddInternal(new HitPiece
+ {
+ RelativeSizeAxes = Axes.Both,
+ Origin = Anchor.TopLeft
+ });
+ }
+
+ protected override void Update()
+ {
+ base.Update();
+
+ // Move the rectangle to cover the hitobjects
+ var topLeft = new Vector2(float.MaxValue, float.MaxValue);
+ var bottomRight = new Vector2(float.MinValue, float.MinValue);
+
+ topLeft = Vector2.ComponentMin(topLeft, Parent.ToLocalSpace(DrawableObject.ScreenSpaceDrawQuad.TopLeft));
+ bottomRight = Vector2.ComponentMax(bottomRight, Parent.ToLocalSpace(DrawableObject.ScreenSpaceDrawQuad.BottomRight));
+
+ Size = bottomRight - topLeft;
+ Position = topLeft;
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Taiko/Edit/Blueprints/TaikoSpanPlacementBlueprint.cs b/osu.Game.Rulesets.Taiko/Edit/Blueprints/TaikoSpanPlacementBlueprint.cs
new file mode 100644
index 0000000000..468d980b23
--- /dev/null
+++ b/osu.Game.Rulesets.Taiko/Edit/Blueprints/TaikoSpanPlacementBlueprint.cs
@@ -0,0 +1,110 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using osu.Framework.Graphics;
+using osu.Framework.Input.Events;
+using osu.Game.Rulesets.Edit;
+using osu.Game.Rulesets.Objects;
+using osu.Game.Rulesets.Objects.Types;
+using osu.Game.Rulesets.Taiko.Objects;
+using osu.Game.Rulesets.Taiko.UI;
+using osuTK;
+using osuTK.Input;
+
+namespace osu.Game.Rulesets.Taiko.Edit.Blueprints
+{
+ public class TaikoSpanPlacementBlueprint : PlacementBlueprint
+ {
+ private readonly HitPiece headPiece;
+ private readonly HitPiece tailPiece;
+
+ private readonly LengthPiece lengthPiece;
+
+ private readonly IHasDuration spanPlacementObject;
+
+ public TaikoSpanPlacementBlueprint(HitObject hitObject)
+ : base(hitObject)
+ {
+ spanPlacementObject = hitObject as IHasDuration;
+
+ RelativeSizeAxes = Axes.Both;
+
+ InternalChildren = new Drawable[]
+ {
+ headPiece = new HitPiece
+ {
+ Size = new Vector2(TaikoHitObject.DEFAULT_SIZE * TaikoPlayfield.DEFAULT_HEIGHT)
+ },
+ lengthPiece = new LengthPiece
+ {
+ Height = TaikoHitObject.DEFAULT_SIZE * TaikoPlayfield.DEFAULT_HEIGHT
+ },
+ tailPiece = new HitPiece
+ {
+ Size = new Vector2(TaikoHitObject.DEFAULT_SIZE * TaikoPlayfield.DEFAULT_HEIGHT)
+ }
+ };
+ }
+
+ private double originalStartTime;
+ private Vector2 originalPosition;
+
+ protected override bool OnMouseDown(MouseDownEvent e)
+ {
+ if (e.Button != MouseButton.Left)
+ return false;
+
+ BeginPlacement(true);
+ return true;
+ }
+
+ protected override void OnMouseUp(MouseUpEvent e)
+ {
+ if (e.Button != MouseButton.Left)
+ return;
+
+ base.OnMouseUp(e);
+ EndPlacement(true);
+ }
+
+ public override void UpdatePosition(SnapResult result)
+ {
+ base.UpdatePosition(result);
+
+ if (PlacementActive)
+ {
+ if (result.Time is double dragTime)
+ {
+ if (dragTime < originalStartTime)
+ {
+ HitObject.StartTime = dragTime;
+ spanPlacementObject.Duration = Math.Abs(dragTime - originalStartTime);
+ headPiece.Position = ToLocalSpace(result.ScreenSpacePosition);
+ tailPiece.Position = originalPosition;
+ }
+ else
+ {
+ HitObject.StartTime = originalStartTime;
+ spanPlacementObject.Duration = Math.Abs(dragTime - originalStartTime);
+ tailPiece.Position = ToLocalSpace(result.ScreenSpacePosition);
+ headPiece.Position = originalPosition;
+ }
+
+ lengthPiece.X = headPiece.X;
+ lengthPiece.Width = tailPiece.X - headPiece.X;
+ }
+ }
+ else
+ {
+ lengthPiece.Position = headPiece.Position = tailPiece.Position = ToLocalSpace(result.ScreenSpacePosition);
+
+ if (result.Time is double startTime)
+ {
+ originalStartTime = HitObject.StartTime = startTime;
+ originalPosition = ToLocalSpace(result.ScreenSpacePosition);
+ }
+ }
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Taiko/Edit/DrumRollCompositionTool.cs b/osu.Game.Rulesets.Taiko/Edit/DrumRollCompositionTool.cs
new file mode 100644
index 0000000000..bf77c76670
--- /dev/null
+++ b/osu.Game.Rulesets.Taiko/Edit/DrumRollCompositionTool.cs
@@ -0,0 +1,20 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Game.Rulesets.Edit;
+using osu.Game.Rulesets.Edit.Tools;
+using osu.Game.Rulesets.Taiko.Edit.Blueprints;
+using osu.Game.Rulesets.Taiko.Objects;
+
+namespace osu.Game.Rulesets.Taiko.Edit
+{
+ public class DrumRollCompositionTool : HitObjectCompositionTool
+ {
+ public DrumRollCompositionTool()
+ : base(nameof(DrumRoll))
+ {
+ }
+
+ public override PlacementBlueprint CreatePlacementBlueprint() => new DrumRollPlacementBlueprint();
+ }
+}
diff --git a/osu.Game.Rulesets.Taiko/Edit/HitCompositionTool.cs b/osu.Game.Rulesets.Taiko/Edit/HitCompositionTool.cs
new file mode 100644
index 0000000000..e877cf6240
--- /dev/null
+++ b/osu.Game.Rulesets.Taiko/Edit/HitCompositionTool.cs
@@ -0,0 +1,20 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Game.Rulesets.Edit;
+using osu.Game.Rulesets.Edit.Tools;
+using osu.Game.Rulesets.Taiko.Edit.Blueprints;
+using osu.Game.Rulesets.Taiko.Objects;
+
+namespace osu.Game.Rulesets.Taiko.Edit
+{
+ public class HitCompositionTool : HitObjectCompositionTool
+ {
+ public HitCompositionTool()
+ : base(nameof(Hit))
+ {
+ }
+
+ public override PlacementBlueprint CreatePlacementBlueprint() => new HitPlacementBlueprint();
+ }
+}
diff --git a/osu.Game.Rulesets.Taiko/Edit/SwellCompositionTool.cs b/osu.Game.Rulesets.Taiko/Edit/SwellCompositionTool.cs
new file mode 100644
index 0000000000..a6191fcedc
--- /dev/null
+++ b/osu.Game.Rulesets.Taiko/Edit/SwellCompositionTool.cs
@@ -0,0 +1,20 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Game.Rulesets.Edit;
+using osu.Game.Rulesets.Edit.Tools;
+using osu.Game.Rulesets.Taiko.Edit.Blueprints;
+using osu.Game.Rulesets.Taiko.Objects;
+
+namespace osu.Game.Rulesets.Taiko.Edit
+{
+ public class SwellCompositionTool : HitObjectCompositionTool
+ {
+ public SwellCompositionTool()
+ : base(nameof(Swell))
+ {
+ }
+
+ public override PlacementBlueprint CreatePlacementBlueprint() => new SwellPlacementBlueprint();
+ }
+}
diff --git a/osu.Game.Rulesets.Taiko/Edit/TaikoBlueprintContainer.cs b/osu.Game.Rulesets.Taiko/Edit/TaikoBlueprintContainer.cs
new file mode 100644
index 0000000000..35227b3c64
--- /dev/null
+++ b/osu.Game.Rulesets.Taiko/Edit/TaikoBlueprintContainer.cs
@@ -0,0 +1,24 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System.Collections.Generic;
+using osu.Game.Rulesets.Edit;
+using osu.Game.Rulesets.Objects.Drawables;
+using osu.Game.Rulesets.Taiko.Edit.Blueprints;
+using osu.Game.Screens.Edit.Compose.Components;
+
+namespace osu.Game.Rulesets.Taiko.Edit
+{
+ public class TaikoBlueprintContainer : ComposeBlueprintContainer
+ {
+ public TaikoBlueprintContainer(IEnumerable hitObjects)
+ : base(hitObjects)
+ {
+ }
+
+ protected override SelectionHandler CreateSelectionHandler() => new TaikoSelectionHandler();
+
+ public override OverlaySelectionBlueprint CreateBlueprintFor(DrawableHitObject hitObject) =>
+ new TaikoSelectionBlueprint(hitObject);
+ }
+}
diff --git a/osu.Game.Rulesets.Taiko/Edit/TaikoHitObjectComposer.cs b/osu.Game.Rulesets.Taiko/Edit/TaikoHitObjectComposer.cs
new file mode 100644
index 0000000000..cdc9672a8e
--- /dev/null
+++ b/osu.Game.Rulesets.Taiko/Edit/TaikoHitObjectComposer.cs
@@ -0,0 +1,30 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System.Collections.Generic;
+using osu.Game.Rulesets.Edit;
+using osu.Game.Rulesets.Edit.Tools;
+using osu.Game.Rulesets.Objects.Drawables;
+using osu.Game.Rulesets.Taiko.Objects;
+using osu.Game.Screens.Edit.Compose.Components;
+
+namespace osu.Game.Rulesets.Taiko.Edit
+{
+ public class TaikoHitObjectComposer : HitObjectComposer
+ {
+ public TaikoHitObjectComposer(TaikoRuleset ruleset)
+ : base(ruleset)
+ {
+ }
+
+ protected override IReadOnlyList CompositionTools => new HitObjectCompositionTool[]
+ {
+ new HitCompositionTool(),
+ new DrumRollCompositionTool(),
+ new SwellCompositionTool()
+ };
+
+ protected override ComposeBlueprintContainer CreateBlueprintContainer(IEnumerable hitObjects)
+ => new TaikoBlueprintContainer(hitObjects);
+ }
+}
diff --git a/osu.Game.Rulesets.Taiko/Edit/TaikoSelectionHandler.cs b/osu.Game.Rulesets.Taiko/Edit/TaikoSelectionHandler.cs
new file mode 100644
index 0000000000..eebf6980fe
--- /dev/null
+++ b/osu.Game.Rulesets.Taiko/Edit/TaikoSelectionHandler.cs
@@ -0,0 +1,80 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using osu.Framework.Graphics.UserInterface;
+using osu.Game.Graphics.UserInterface;
+using osu.Game.Rulesets.Edit;
+using osu.Game.Rulesets.Taiko.Objects;
+using osu.Game.Screens.Edit.Compose.Components;
+
+namespace osu.Game.Rulesets.Taiko.Edit
+{
+ public class TaikoSelectionHandler : SelectionHandler
+ {
+ protected override IEnumerable
public readonly DrawableHitObject DrawableObject;
- protected override bool ShouldBeAlive => (DrawableObject.IsAlive && DrawableObject.IsPresent) || State == SelectionState.Selected;
+ ///
+ /// Whether the blueprint should be shown even when the is not alive.
+ ///
+ protected virtual bool AlwaysShowWhenSelected => false;
+
+ protected override bool ShouldBeAlive => (DrawableObject.IsAlive && DrawableObject.IsPresent) || (AlwaysShowWhenSelected && State == SelectionState.Selected);
protected OverlaySelectionBlueprint(DrawableHitObject drawableObject)
: base(drawableObject.HitObject)
diff --git a/osu.Game/Rulesets/Edit/PlacementBlueprint.cs b/osu.Game/Rulesets/Edit/PlacementBlueprint.cs
index bb89ba8311..02d5955ae6 100644
--- a/osu.Game/Rulesets/Edit/PlacementBlueprint.cs
+++ b/osu.Game/Rulesets/Edit/PlacementBlueprint.cs
@@ -87,11 +87,11 @@ namespace osu.Game.Rulesets.Edit
///
/// Updates the position of this to a new screen-space position.
///
- /// The snap result information.
- public virtual void UpdatePosition(SnapResult snapResult)
+ /// The snap result information.
+ public virtual void UpdatePosition(SnapResult result)
{
if (!PlacementActive)
- HitObject.StartTime = snapResult.Time ?? EditorClock?.CurrentTime ?? Time.Current;
+ HitObject.StartTime = result.Time ?? EditorClock?.CurrentTime ?? Time.Current;
}
///
diff --git a/osu.Game/Rulesets/Objects/HitObject.cs b/osu.Game/Rulesets/Objects/HitObject.cs
index 6f9053d7cb..e2cc98813a 100644
--- a/osu.Game/Rulesets/Objects/HitObject.cs
+++ b/osu.Game/Rulesets/Objects/HitObject.cs
@@ -175,10 +175,10 @@ namespace osu.Game.Rulesets.Objects
/// Returns the end time of this object.
///
///
- /// This returns the where available, falling back to otherwise.
+ /// This returns the where available, falling back to otherwise.
///
/// The object.
/// The end time of this object.
- public static double GetEndTime(this HitObject hitObject) => (hitObject as IHasEndTime)?.EndTime ?? hitObject.StartTime;
+ public static double GetEndTime(this HitObject hitObject) => (hitObject as IHasDuration)?.EndTime ?? hitObject.StartTime;
}
}
diff --git a/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHitObjectParser.cs
index 43e8d01297..c10c8dc30f 100644
--- a/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHitObjectParser.cs
+++ b/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHitObjectParser.cs
@@ -56,7 +56,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Catch
};
}
- protected override HitObject CreateSpinner(Vector2 position, bool newCombo, int comboOffset, double endTime)
+ protected override HitObject CreateSpinner(Vector2 position, bool newCombo, int comboOffset, double duration)
{
// Convert spinners don't create the new combo themselves, but force the next non-spinner hitobject to create a new combo
// Their combo offset is still added to that next hitobject's combo index
@@ -65,11 +65,11 @@ namespace osu.Game.Rulesets.Objects.Legacy.Catch
return new ConvertSpinner
{
- EndTime = endTime
+ Duration = duration
};
}
- protected override HitObject CreateHold(Vector2 position, bool newCombo, int comboOffset, double endTime)
+ protected override HitObject CreateHold(Vector2 position, bool newCombo, int comboOffset, double duration)
{
return null;
}
diff --git a/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertSpinner.cs b/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertSpinner.cs
index 9de311c9d7..014494ec54 100644
--- a/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertSpinner.cs
+++ b/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertSpinner.cs
@@ -8,11 +8,11 @@ namespace osu.Game.Rulesets.Objects.Legacy.Catch
///
/// Legacy osu!catch Spinner-type, used for parsing Beatmaps.
///
- internal sealed class ConvertSpinner : ConvertHitObject, IHasEndTime, IHasXPosition, IHasCombo
+ internal sealed class ConvertSpinner : ConvertHitObject, IHasDuration, IHasXPosition, IHasCombo
{
- public double EndTime { get; set; }
+ public double EndTime => StartTime + Duration;
- public double Duration => EndTime - StartTime;
+ public double Duration { get; set; }
public float X => 256; // Required for CatchBeatmapConverter
diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs
index 9a60a0a75c..9e936c7717 100644
--- a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs
+++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs
@@ -189,9 +189,9 @@ namespace osu.Game.Rulesets.Objects.Legacy
}
else if (type.HasFlag(LegacyHitObjectType.Spinner))
{
- double endTime = Math.Max(startTime, Parsing.ParseDouble(split[5]) + Offset);
+ double duration = Math.Max(0, Parsing.ParseDouble(split[5]) + Offset - startTime);
- result = CreateSpinner(new Vector2(512, 384) / 2, combo, comboOffset, endTime);
+ result = CreateSpinner(new Vector2(512, 384) / 2, combo, comboOffset, duration);
if (split.Length > 6)
readCustomSampleBanks(split[6], bankInfo);
@@ -209,7 +209,7 @@ namespace osu.Game.Rulesets.Objects.Legacy
readCustomSampleBanks(string.Join(":", ss.Skip(1)), bankInfo);
}
- result = CreateHold(pos, combo, comboOffset, endTime + Offset);
+ result = CreateHold(pos, combo, comboOffset, endTime + Offset - startTime);
}
if (result == null)
@@ -321,9 +321,9 @@ namespace osu.Game.Rulesets.Objects.Legacy
/// The position of the hit object.
/// Whether the hit object creates a new combo.
/// When starting a new combo, the offset of the new combo relative to the current one.
- /// The spinner end time.
+ /// The spinner duration.
/// The hit object.
- protected abstract HitObject CreateSpinner(Vector2 position, bool newCombo, int comboOffset, double endTime);
+ protected abstract HitObject CreateSpinner(Vector2 position, bool newCombo, int comboOffset, double duration);
///
/// Creates a legacy Hold-type hit object.
@@ -331,8 +331,8 @@ namespace osu.Game.Rulesets.Objects.Legacy
/// The position of the hit object.
/// Whether the hit object creates a new combo.
/// When starting a new combo, the offset of the new combo relative to the current one.
- /// The hold end time.
- protected abstract HitObject CreateHold(Vector2 position, bool newCombo, int comboOffset, double endTime);
+ /// The hold duration.
+ protected abstract HitObject CreateHold(Vector2 position, bool newCombo, int comboOffset, double duration);
private List convertSoundType(LegacyHitSoundType type, SampleBankInfo bankInfo)
{
diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs
index 924182b265..c522dc623c 100644
--- a/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs
+++ b/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs
@@ -9,7 +9,10 @@ using osu.Game.Beatmaps.ControlPoints;
namespace osu.Game.Rulesets.Objects.Legacy
{
- internal abstract class ConvertSlider : ConvertHitObject, IHasCurve, IHasLegacyLastTickOffset
+ internal abstract class ConvertSlider : ConvertHitObject, IHasPathWithRepeats, IHasLegacyLastTickOffset,
+#pragma warning disable 618
+ IHasCurve
+#pragma warning restore 618
{
///
/// Scoring distance with a speed-adjusted beat length of 1 second.
@@ -26,13 +29,13 @@ namespace osu.Game.Rulesets.Objects.Legacy
public List> NodeSamples { get; set; }
public int RepeatCount { get; set; }
- public double EndTime
+ public double Duration
{
- get => StartTime + this.SpanCount() * Distance / Velocity;
+ get => this.SpanCount() * Distance / Velocity;
set => throw new System.NotSupportedException($"Adjust via {nameof(RepeatCount)} instead"); // can be implemented if/when needed.
}
- public double Duration => EndTime - StartTime;
+ public double EndTime => StartTime + Duration;
public double Velocity = 1;
diff --git a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHitObjectParser.cs
index f94c4aaa75..bc64518f40 100644
--- a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHitObjectParser.cs
+++ b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHitObjectParser.cs
@@ -37,21 +37,21 @@ namespace osu.Game.Rulesets.Objects.Legacy.Mania
};
}
- protected override HitObject CreateSpinner(Vector2 position, bool newCombo, int comboOffset, double endTime)
+ protected override HitObject CreateSpinner(Vector2 position, bool newCombo, int comboOffset, double duration)
{
return new ConvertSpinner
{
X = position.X,
- EndTime = endTime
+ Duration = duration
};
}
- protected override HitObject CreateHold(Vector2 position, bool newCombo, int comboOffset, double endTime)
+ protected override HitObject CreateHold(Vector2 position, bool newCombo, int comboOffset, double duration)
{
return new ConvertHold
{
X = position.X,
- EndTime = endTime
+ Duration = duration
};
}
}
diff --git a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHold.cs b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHold.cs
index 1d92d638dd..2fa4766c1d 100644
--- a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHold.cs
+++ b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHold.cs
@@ -5,12 +5,12 @@ using osu.Game.Rulesets.Objects.Types;
namespace osu.Game.Rulesets.Objects.Legacy.Mania
{
- internal sealed class ConvertHold : ConvertHitObject, IHasXPosition, IHasEndTime
+ internal sealed class ConvertHold : ConvertHitObject, IHasXPosition, IHasDuration
{
public float X { get; set; }
- public double EndTime { get; set; }
+ public double Duration { get; set; }
- public double Duration => EndTime - StartTime;
+ public double EndTime => StartTime + Duration;
}
}
diff --git a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertSpinner.cs b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertSpinner.cs
index 7dc13e27cd..c05aaceb9c 100644
--- a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertSpinner.cs
+++ b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertSpinner.cs
@@ -8,11 +8,11 @@ namespace osu.Game.Rulesets.Objects.Legacy.Mania
///
/// Legacy osu!mania Spinner-type, used for parsing Beatmaps.
///
- internal sealed class ConvertSpinner : ConvertHitObject, IHasEndTime, IHasXPosition
+ internal sealed class ConvertSpinner : ConvertHitObject, IHasDuration, IHasXPosition
{
- public double EndTime { get; set; }
+ public double Duration { get; set; }
- public double Duration => EndTime - StartTime;
+ public double EndTime => StartTime + Duration;
public float X { get; set; }
}
diff --git a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHitObjectParser.cs
index b95ec703b6..75ecab0b8f 100644
--- a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHitObjectParser.cs
+++ b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHitObjectParser.cs
@@ -56,7 +56,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Osu
};
}
- protected override HitObject CreateSpinner(Vector2 position, bool newCombo, int comboOffset, double endTime)
+ protected override HitObject CreateSpinner(Vector2 position, bool newCombo, int comboOffset, double duration)
{
// Convert spinners don't create the new combo themselves, but force the next non-spinner hitobject to create a new combo
// Their combo offset is still added to that next hitobject's combo index
@@ -66,11 +66,11 @@ namespace osu.Game.Rulesets.Objects.Legacy.Osu
return new ConvertSpinner
{
Position = position,
- EndTime = endTime
+ Duration = duration
};
}
- protected override HitObject CreateHold(Vector2 position, bool newCombo, int comboOffset, double endTime)
+ protected override HitObject CreateHold(Vector2 position, bool newCombo, int comboOffset, double duration)
{
return null;
}
diff --git a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSpinner.cs b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSpinner.cs
index 8b21aab411..e9e5ca8c94 100644
--- a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSpinner.cs
+++ b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSpinner.cs
@@ -9,11 +9,11 @@ namespace osu.Game.Rulesets.Objects.Legacy.Osu
///
/// Legacy osu! Spinner-type, used for parsing Beatmaps.
///
- internal sealed class ConvertSpinner : ConvertHitObject, IHasEndTime, IHasPosition, IHasCombo
+ internal sealed class ConvertSpinner : ConvertHitObject, IHasDuration, IHasPosition, IHasCombo
{
- public double EndTime { get; set; }
+ public double Duration { get; set; }
- public double Duration => EndTime - StartTime;
+ public double EndTime => StartTime + Duration;
public Vector2 Position { get; set; }
diff --git a/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHitObjectParser.cs
index db65a61c90..13e3e84c6a 100644
--- a/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHitObjectParser.cs
+++ b/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHitObjectParser.cs
@@ -33,15 +33,15 @@ namespace osu.Game.Rulesets.Objects.Legacy.Taiko
};
}
- protected override HitObject CreateSpinner(Vector2 position, bool newCombo, int comboOffset, double endTime)
+ protected override HitObject CreateSpinner(Vector2 position, bool newCombo, int comboOffset, double duration)
{
return new ConvertSpinner
{
- EndTime = endTime
+ Duration = duration
};
}
- protected override HitObject CreateHold(Vector2 position, bool newCombo, int comboOffset, double endTime)
+ protected override HitObject CreateHold(Vector2 position, bool newCombo, int comboOffset, double duration)
{
return null;
}
diff --git a/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertSpinner.cs b/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertSpinner.cs
index 8e28487f2f..1d5ecb1ef3 100644
--- a/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertSpinner.cs
+++ b/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertSpinner.cs
@@ -8,10 +8,10 @@ namespace osu.Game.Rulesets.Objects.Legacy.Taiko
///
/// Legacy osu!taiko Spinner-type, used for parsing Beatmaps.
///
- internal sealed class ConvertSpinner : ConvertHitObject, IHasEndTime
+ internal sealed class ConvertSpinner : ConvertHitObject, IHasDuration
{
- public double EndTime { get; set; }
+ public double Duration { get; set; }
- public double Duration => EndTime - StartTime;
+ public double EndTime => StartTime + Duration;
}
}
diff --git a/osu.Game/Rulesets/Objects/SliderEventGenerator.cs b/osu.Game/Rulesets/Objects/SliderEventGenerator.cs
index 6df0041e7a..d8c6da86f9 100644
--- a/osu.Game/Rulesets/Objects/SliderEventGenerator.cs
+++ b/osu.Game/Rulesets/Objects/SliderEventGenerator.cs
@@ -11,6 +11,7 @@ namespace osu.Game.Rulesets.Objects
public static class SliderEventGenerator
{
[Obsolete("Use the overload with cancellation support instead.")] // can be removed 20201115
+ // ReSharper disable once RedundantOverload.Global
public static IEnumerable Generate(double startTime, double spanDuration, double velocity, double tickDistance, double totalDistance, int spanCount,
double? legacyLastTickOffset)
{
diff --git a/osu.Game/Rulesets/Objects/Types/IHasCurve.cs b/osu.Game/Rulesets/Objects/Types/IHasCurve.cs
index e98a888bd7..26f50ffa31 100644
--- a/osu.Game/Rulesets/Objects/Types/IHasCurve.cs
+++ b/osu.Game/Rulesets/Objects/Types/IHasCurve.cs
@@ -1,13 +1,12 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System;
using osuTK;
namespace osu.Game.Rulesets.Objects.Types
{
- ///
- /// A HitObject that has a curve.
- ///
+ [Obsolete("Use IHasPathWithRepeats instead.")] // can be removed 20201126
public interface IHasCurve : IHasDistance, IHasRepeats
{
///
@@ -16,6 +15,8 @@ namespace osu.Game.Rulesets.Objects.Types
SliderPath Path { get; }
}
+#pragma warning disable 618
+ [Obsolete("Use IHasPathWithRepeats instead.")] // can be removed 20201126
public static class HasCurveExtensions
{
///
@@ -50,4 +51,5 @@ namespace osu.Game.Rulesets.Objects.Types
public static int SpanAt(this IHasCurve obj, double progress)
=> (int)(progress * obj.SpanCount());
}
+#pragma warning restore 618
}
diff --git a/osu.Game/Rulesets/Objects/Types/IHasDistance.cs b/osu.Game/Rulesets/Objects/Types/IHasDistance.cs
index e7f552115e..b497ca5da3 100644
--- a/osu.Game/Rulesets/Objects/Types/IHasDistance.cs
+++ b/osu.Game/Rulesets/Objects/Types/IHasDistance.cs
@@ -6,7 +6,7 @@ namespace osu.Game.Rulesets.Objects.Types
///
/// A HitObject that has a positional length.
///
- public interface IHasDistance : IHasEndTime
+ public interface IHasDistance : IHasDuration
{
///
/// The positional length of the HitObject.
diff --git a/osu.Game/Rulesets/Objects/Types/IHasDuration.cs b/osu.Game/Rulesets/Objects/Types/IHasDuration.cs
new file mode 100644
index 0000000000..185fd5977b
--- /dev/null
+++ b/osu.Game/Rulesets/Objects/Types/IHasDuration.cs
@@ -0,0 +1,34 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using Newtonsoft.Json;
+
+namespace osu.Game.Rulesets.Objects.Types
+{
+ ///
+ /// A HitObject that ends at a different time than its start time.
+ ///
+#pragma warning disable 618
+ public interface IHasDuration : IHasEndTime
+#pragma warning restore 618
+ {
+ double IHasEndTime.EndTime
+ {
+ get => EndTime;
+ set => Duration = (Duration - EndTime) + value;
+ }
+
+ double IHasEndTime.Duration => Duration;
+
+ ///
+ /// The time at which the HitObject ends.
+ ///
+ new double EndTime { get; }
+
+ ///
+ /// The duration of the HitObject.
+ ///
+ [JsonIgnore]
+ new double Duration { get; set; }
+ }
+}
diff --git a/osu.Game/Rulesets/Objects/Types/IHasEndTime.cs b/osu.Game/Rulesets/Objects/Types/IHasEndTime.cs
index bc7103c60d..c3769c5909 100644
--- a/osu.Game/Rulesets/Objects/Types/IHasEndTime.cs
+++ b/osu.Game/Rulesets/Objects/Types/IHasEndTime.cs
@@ -1,6 +1,7 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System;
using Newtonsoft.Json;
namespace osu.Game.Rulesets.Objects.Types
@@ -8,6 +9,7 @@ namespace osu.Game.Rulesets.Objects.Types
///
/// A HitObject that ends at a different time than its start time.
///
+ [Obsolete("Use IHasDuration instead.")] // can be removed 20201126
public interface IHasEndTime
{
///
diff --git a/osu.Game/Rulesets/Objects/Types/IHasPath.cs b/osu.Game/Rulesets/Objects/Types/IHasPath.cs
new file mode 100644
index 0000000000..567c24a4a2
--- /dev/null
+++ b/osu.Game/Rulesets/Objects/Types/IHasPath.cs
@@ -0,0 +1,13 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+namespace osu.Game.Rulesets.Objects.Types
+{
+ public interface IHasPath : IHasDistance
+ {
+ ///
+ /// The curve.
+ ///
+ SliderPath Path { get; }
+ }
+}
diff --git a/osu.Game/Rulesets/Objects/Types/IHasPathWithRepeats.cs b/osu.Game/Rulesets/Objects/Types/IHasPathWithRepeats.cs
new file mode 100644
index 0000000000..279946b44e
--- /dev/null
+++ b/osu.Game/Rulesets/Objects/Types/IHasPathWithRepeats.cs
@@ -0,0 +1,50 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osuTK;
+
+namespace osu.Game.Rulesets.Objects.Types
+{
+ ///
+ /// A HitObject that has a curve.
+ ///
+ // ReSharper disable once RedundantExtendsListEntry
+ public interface IHasPathWithRepeats : IHasPath, IHasRepeats
+ {
+ }
+
+ public static class HasPathWithRepeatsExtensions
+ {
+ ///
+ /// Computes the position on the curve relative to how much of the has been completed.
+ ///
+ /// The curve.
+ /// [0, 1] where 0 is the start time of the and 1 is the end time of the .
+ /// The position on the curve.
+ public static Vector2 CurvePositionAt(this IHasPathWithRepeats obj, double progress)
+ => obj.Path.PositionAt(obj.ProgressAt(progress));
+
+ ///
+ /// Computes the progress along the curve relative to how much of the has been completed.
+ ///
+ /// The curve.
+ /// [0, 1] where 0 is the start time of the and 1 is the end time of the .
+ /// [0, 1] where 0 is the beginning of the curve and 1 is the end of the curve.
+ public static double ProgressAt(this IHasPathWithRepeats obj, double progress)
+ {
+ double p = progress * obj.SpanCount() % 1;
+ if (obj.SpanAt(progress) % 2 == 1)
+ p = 1 - p;
+ return p;
+ }
+
+ ///
+ /// Determines which span of the curve the progress point is on.
+ ///
+ /// The curve.
+ /// [0, 1] where 0 is the beginning of the curve and 1 is the end of the curve.
+ /// [0, SpanCount) where 0 is the first run.
+ public static int SpanAt(this IHasPathWithRepeats obj, double progress)
+ => (int)(progress * obj.SpanCount());
+ }
+}
diff --git a/osu.Game/Rulesets/Objects/Types/IHasRepeats.cs b/osu.Game/Rulesets/Objects/Types/IHasRepeats.cs
index 256b1f3963..7a3fb16196 100644
--- a/osu.Game/Rulesets/Objects/Types/IHasRepeats.cs
+++ b/osu.Game/Rulesets/Objects/Types/IHasRepeats.cs
@@ -9,7 +9,7 @@ namespace osu.Game.Rulesets.Objects.Types
///
/// A HitObject that spans some length.
///
- public interface IHasRepeats : IHasEndTime
+ public interface IHasRepeats : IHasDuration
{
///
/// The amount of times the HitObject repeats.
diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs
index bee11accca..4f28607733 100644
--- a/osu.Game/Rulesets/Ruleset.cs
+++ b/osu.Game/Rulesets/Ruleset.cs
@@ -22,6 +22,7 @@ using osu.Game.Rulesets.Scoring;
using osu.Game.Scoring;
using osu.Game.Skinning;
using osu.Game.Users;
+using JetBrains.Annotations;
namespace osu.Game.Rulesets
{
@@ -100,7 +101,8 @@ namespace osu.Game.Rulesets
return value;
}
- public ModAutoplay GetAutoplayMod() => GetAllMods().OfType().First();
+ [CanBeNull]
+ public ModAutoplay GetAutoplayMod() => GetAllMods().OfType().FirstOrDefault();
public virtual ISkin CreateLegacySkinProvider(ISkinSource source, IBeatmap beatmap) => null;
diff --git a/osu.Game/Rulesets/RulesetStore.cs b/osu.Game/Rulesets/RulesetStore.cs
index b3026bf2b7..58a2ba056e 100644
--- a/osu.Game/Rulesets/RulesetStore.cs
+++ b/osu.Game/Rulesets/RulesetStore.cs
@@ -6,6 +6,7 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
+using osu.Framework;
using osu.Framework.Logging;
using osu.Framework.Platform;
using osu.Game.Database;
@@ -153,14 +154,14 @@ namespace osu.Game.Rulesets
{
try
{
- string[] files = Directory.GetFiles(Environment.CurrentDirectory, $"{ruleset_library_prefix}.*.dll");
+ var files = Directory.GetFiles(RuntimeInfo.StartupDirectory, $"{ruleset_library_prefix}.*.dll");
foreach (string file in files.Where(f => !Path.GetFileName(f).Contains("Tests")))
loadRulesetFromFile(file);
}
catch (Exception e)
{
- Logger.Error(e, $"Could not load rulesets from directory {Environment.CurrentDirectory}");
+ Logger.Error(e, $"Could not load rulesets from directory {RuntimeInfo.StartupDirectory}");
}
}
diff --git a/osu.Game/Rulesets/Scoring/DrainingHealthProcessor.cs b/osu.Game/Rulesets/Scoring/DrainingHealthProcessor.cs
index fffcbb3c9f..982f527517 100644
--- a/osu.Game/Rulesets/Scoring/DrainingHealthProcessor.cs
+++ b/osu.Game/Rulesets/Scoring/DrainingHealthProcessor.cs
@@ -3,9 +3,11 @@
using System;
using System.Collections.Generic;
+using System.Linq;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects;
+using osu.Game.Utils;
namespace osu.Game.Rulesets.Scoring
{
@@ -47,6 +49,8 @@ namespace osu.Game.Rulesets.Scoring
private double targetMinimumHealth;
private double drainRate = 1;
+ private PeriodTracker noDrainPeriodTracker;
+
///
/// Creates a new .
///
@@ -60,14 +64,14 @@ namespace osu.Game.Rulesets.Scoring
{
base.Update();
- if (!IsBreakTime.Value)
- {
- // When jumping in and out of gameplay time within a single frame, health should only be drained for the period within the gameplay time
- double lastGameplayTime = Math.Clamp(Time.Current - Time.Elapsed, drainStartTime, gameplayEndTime);
- double currentGameplayTime = Math.Clamp(Time.Current, drainStartTime, gameplayEndTime);
+ if (noDrainPeriodTracker?.IsInAny(Time.Current) == true)
+ return;
- Health.Value -= drainRate * (currentGameplayTime - lastGameplayTime);
- }
+ // When jumping in and out of gameplay time within a single frame, health should only be drained for the period within the gameplay time
+ double lastGameplayTime = Math.Clamp(Time.Current - Time.Elapsed, drainStartTime, gameplayEndTime);
+ double currentGameplayTime = Math.Clamp(Time.Current, drainStartTime, gameplayEndTime);
+
+ Health.Value -= drainRate * (currentGameplayTime - lastGameplayTime);
}
public override void ApplyBeatmap(IBeatmap beatmap)
@@ -77,6 +81,19 @@ namespace osu.Game.Rulesets.Scoring
if (beatmap.HitObjects.Count > 0)
gameplayEndTime = beatmap.HitObjects[^1].GetEndTime();
+ noDrainPeriodTracker = new PeriodTracker(beatmap.Breaks.Select(breakPeriod => new Period(
+ beatmap.HitObjects
+ .Select(hitObject => hitObject.GetEndTime())
+ .Where(endTime => endTime <= breakPeriod.StartTime)
+ .DefaultIfEmpty(double.MinValue)
+ .Last(),
+ beatmap.HitObjects
+ .Select(hitObject => hitObject.StartTime)
+ .Where(startTime => startTime >= breakPeriod.EndTime)
+ .DefaultIfEmpty(double.MaxValue)
+ .First()
+ )));
+
targetMinimumHealth = BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.DrainRate, min_health_target, mid_health_target, max_health_target);
base.ApplyBeatmap(beatmap);
diff --git a/osu.Game/Rulesets/Scoring/HealthProcessor.cs b/osu.Game/Rulesets/Scoring/HealthProcessor.cs
index 45edc0f4a3..1535fe4d00 100644
--- a/osu.Game/Rulesets/Scoring/HealthProcessor.cs
+++ b/osu.Game/Rulesets/Scoring/HealthProcessor.cs
@@ -26,11 +26,6 @@ namespace osu.Game.Rulesets.Scoring
///
public readonly BindableDouble Health = new BindableDouble(1) { MinValue = 0, MaxValue = 1 };
- ///
- /// Whether gameplay is currently in a break.
- ///
- public readonly IBindable IsBreakTime = new Bindable();
-
///
/// Whether this ScoreProcessor has already triggered the failed state.
///
diff --git a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs
index c817d84d5c..0dc3324559 100644
--- a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs
+++ b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs
@@ -270,7 +270,7 @@ namespace osu.Game.Rulesets.UI.Scrolling
// Cant use AddOnce() since the delegate is re-constructed every invocation
private void computeInitialStateRecursive(DrawableHitObject hitObject) => hitObject.Schedule(() =>
{
- if (hitObject.HitObject is IHasEndTime e)
+ if (hitObject.HitObject is IHasDuration e)
{
switch (direction.Value)
{
diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs
index cc417bbb10..d07cffff0c 100644
--- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs
+++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs
@@ -44,6 +44,8 @@ namespace osu.Game.Screens.Edit.Compose.Components
private readonly BindableList selectedHitObjects = new BindableList();
+ public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true;
+
[Resolved(canBeNull: true)]
private IPositionSnapProvider snapProvider { get; set; }
diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs
index 7ab6340e07..38893f90a8 100644
--- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs
+++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs
@@ -38,7 +38,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
private Drawable outline;
[Resolved(CanBeNull = true)]
- private EditorBeatmap editorBeatmap { get; set; }
+ protected EditorBeatmap EditorBeatmap { get; private set; }
[Resolved(CanBeNull = true)]
private IEditorChangeHandler changeHandler { get; set; }
@@ -117,7 +117,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
internal void HandleSelected(SelectionBlueprint blueprint)
{
selectedBlueprints.Add(blueprint);
- editorBeatmap.SelectedHitObjects.Add(blueprint.HitObject);
+ EditorBeatmap.SelectedHitObjects.Add(blueprint.HitObject);
UpdateVisibility();
}
@@ -129,7 +129,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
internal void HandleDeselected(SelectionBlueprint blueprint)
{
selectedBlueprints.Remove(blueprint);
- editorBeatmap.SelectedHitObjects.Remove(blueprint.HitObject);
+ EditorBeatmap.SelectedHitObjects.Remove(blueprint.HitObject);
// We don't want to update visibility if > 0, since we may be deselecting blueprints during drag-selection
if (selectedBlueprints.Count == 0)
@@ -165,7 +165,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
changeHandler?.BeginChange();
foreach (var h in selectedBlueprints.ToList())
- editorBeatmap?.Remove(h.HitObject);
+ EditorBeatmap?.Remove(h.HitObject);
changeHandler?.EndChange();
}
diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs
index dd2f7a833e..b95b3842b3 100644
--- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs
+++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs
@@ -72,7 +72,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
shadowComponents.Add(circle);
- if (hitObject is IHasEndTime)
+ if (hitObject is IHasDuration)
{
DragBar dragBarUnderlay;
Container extensionBar;
@@ -290,13 +290,13 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
repeatHitObject.RepeatCount = proposedCount;
break;
- case IHasEndTime endTimeHitObject:
+ case IHasDuration endTimeHitObject:
var snappedTime = Math.Max(hitObject.StartTime, beatSnapProvider.SnapTime(time));
if (endTimeHitObject.EndTime == snappedTime)
return;
- endTimeHitObject.EndTime = snappedTime;
+ endTimeHitObject.Duration = snappedTime - hitObject.StartTime;
break;
}
diff --git a/osu.Game/Screens/Menu/IntroScreen.cs b/osu.Game/Screens/Menu/IntroScreen.cs
index 0d5f3d1142..b99d8ae9d1 100644
--- a/osu.Game/Screens/Menu/IntroScreen.cs
+++ b/osu.Game/Screens/Menu/IntroScreen.cs
@@ -41,9 +41,9 @@ namespace osu.Game.Screens.Menu
protected IBindable MenuMusic { get; private set; }
- private WorkingBeatmap introBeatmap;
+ private WorkingBeatmap initialBeatmap;
- protected Track Track { get; private set; }
+ protected Track Track => initialBeatmap?.Track;
private readonly BindableDouble exitingVolumeFade = new BindableDouble(1);
@@ -58,6 +58,11 @@ namespace osu.Game.Screens.Menu
[Resolved]
private AudioManager audio { get; set; }
+ ///
+ /// Whether the is provided by osu! resources, rather than a user beatmap.
+ ///
+ protected bool UsingThemedIntro { get; private set; }
+
[BackgroundDependencyLoader]
private void load(OsuConfigManager config, SkinManager skinManager, BeatmapManager beatmaps, Framework.Game game)
{
@@ -71,29 +76,45 @@ namespace osu.Game.Screens.Menu
BeatmapSetInfo setInfo = null;
+ // if the user has requested not to play theme music, we should attempt to find a random beatmap from their collection.
if (!MenuMusic.Value)
{
var sets = beatmaps.GetAllUsableBeatmapSets(IncludedDetails.Minimal);
+
if (sets.Count > 0)
- setInfo = beatmaps.QueryBeatmapSet(s => s.ID == sets[RNG.Next(0, sets.Count - 1)].ID);
- }
-
- if (setInfo == null)
- {
- setInfo = beatmaps.QueryBeatmapSet(b => b.Hash == BeatmapHash);
-
- if (setInfo == null)
{
- // we need to import the default menu background beatmap
- setInfo = beatmaps.Import(new ZipArchiveReader(game.Resources.GetStream($"Tracks/{BeatmapFile}"), BeatmapFile)).Result;
-
- setInfo.Protected = true;
- beatmaps.Update(setInfo);
+ setInfo = beatmaps.QueryBeatmapSet(s => s.ID == sets[RNG.Next(0, sets.Count - 1)].ID);
+ initialBeatmap = beatmaps.GetWorkingBeatmap(setInfo.Beatmaps[0]);
}
}
- introBeatmap = beatmaps.GetWorkingBeatmap(setInfo.Beatmaps[0]);
- Track = introBeatmap.Track;
+ // we generally want a song to be playing on startup, so use the intro music even if a user has specified not to if no other track is available.
+ if (setInfo == null)
+ {
+ if (!loadThemedIntro())
+ {
+ // if we detect that the theme track or beatmap is unavailable this is either first startup or things are in a bad state.
+ // this could happen if a user has nuked their files store. for now, reimport to repair this.
+ var import = beatmaps.Import(new ZipArchiveReader(game.Resources.GetStream($"Tracks/{BeatmapFile}"), BeatmapFile)).Result;
+ import.Protected = true;
+ beatmaps.Update(import);
+
+ loadThemedIntro();
+ }
+ }
+
+ bool loadThemedIntro()
+ {
+ setInfo = beatmaps.QueryBeatmapSet(b => b.Hash == BeatmapHash);
+
+ if (setInfo != null)
+ {
+ initialBeatmap = beatmaps.GetWorkingBeatmap(setInfo.Beatmaps[0]);
+ UsingThemedIntro = !(Track is TrackVirtual);
+ }
+
+ return UsingThemedIntro;
+ }
}
public override void OnResuming(IScreen last)
@@ -119,7 +140,7 @@ namespace osu.Game.Screens.Menu
public override void OnSuspending(IScreen next)
{
base.OnSuspending(next);
- Track = null;
+ initialBeatmap = null;
}
protected override BackgroundScreen CreateBackground() => new BackgroundScreenBlack();
@@ -127,7 +148,7 @@ namespace osu.Game.Screens.Menu
protected void StartTrack()
{
// Only start the current track if it is the menu music. A beatmap's track is started when entering the Main Menu.
- if (MenuMusic.Value)
+ if (UsingThemedIntro)
Track.Restart();
}
@@ -141,8 +162,7 @@ namespace osu.Game.Screens.Menu
if (!resuming)
{
- beatmap.Value = introBeatmap;
- introBeatmap = null;
+ beatmap.Value = initialBeatmap;
logo.MoveTo(new Vector2(0.5f));
logo.ScaleTo(Vector2.One);
diff --git a/osu.Game/Screens/Menu/IntroTriangles.cs b/osu.Game/Screens/Menu/IntroTriangles.cs
index 188a49c147..225ad02ec4 100644
--- a/osu.Game/Screens/Menu/IntroTriangles.cs
+++ b/osu.Game/Screens/Menu/IntroTriangles.cs
@@ -46,7 +46,7 @@ namespace osu.Game.Screens.Menu
[BackgroundDependencyLoader]
private void load()
{
- if (MenuVoice.Value && !MenuMusic.Value)
+ if (MenuVoice.Value && !UsingThemedIntro)
welcome = audio.Samples.Get(@"welcome");
}
@@ -61,7 +61,7 @@ namespace osu.Game.Screens.Menu
LoadComponentAsync(new TrianglesIntroSequence(logo, background)
{
RelativeSizeAxes = Axes.Both,
- Clock = new FramedClock(MenuMusic.Value ? Track : null),
+ Clock = new FramedClock(UsingThemedIntro ? Track : null),
LoadMenu = LoadMenu
}, t =>
{
diff --git a/osu.Game/Screens/Multi/DrawableRoomPlaylistItem.cs b/osu.Game/Screens/Multi/DrawableRoomPlaylistItem.cs
index c024304856..414c1f5748 100644
--- a/osu.Game/Screens/Multi/DrawableRoomPlaylistItem.cs
+++ b/osu.Game/Screens/Multi/DrawableRoomPlaylistItem.cs
@@ -188,7 +188,7 @@ namespace osu.Game.Screens.Multi
X = -18,
Children = new Drawable[]
{
- new PlaylistDownloadButton(item.Beatmap.Value.BeatmapSet)
+ new PlaylistDownloadButton(item)
{
Size = new Vector2(50, 30)
},
@@ -212,9 +212,15 @@ namespace osu.Game.Screens.Multi
private class PlaylistDownloadButton : BeatmapPanelDownloadButton
{
- public PlaylistDownloadButton(BeatmapSetInfo beatmapSet)
- : base(beatmapSet)
+ private readonly PlaylistItem playlistItem;
+
+ [Resolved]
+ private BeatmapManager beatmapManager { get; set; }
+
+ public PlaylistDownloadButton(PlaylistItem playlistItem)
+ : base(playlistItem.Beatmap.Value.BeatmapSet)
{
+ this.playlistItem = playlistItem;
Alpha = 0;
}
@@ -223,11 +229,26 @@ namespace osu.Game.Screens.Multi
base.LoadComplete();
State.BindValueChanged(stateChanged, true);
+ FinishTransforms(true);
}
private void stateChanged(ValueChangedEvent state)
{
- this.FadeTo(state.NewValue == DownloadState.LocallyAvailable ? 0 : 1, 500);
+ switch (state.NewValue)
+ {
+ case DownloadState.LocallyAvailable:
+ // Perform a local query of the beatmap by beatmap checksum, and reset the state if not matching.
+ if (beatmapManager.QueryBeatmap(b => b.MD5Hash == playlistItem.Beatmap.Value.MD5Hash) == null)
+ State.Value = DownloadState.NotDownloaded;
+ else
+ this.FadeTo(0, 500);
+
+ break;
+
+ default:
+ this.FadeTo(1, 500);
+ break;
+ }
}
}
diff --git a/osu.Game/Screens/Multi/IRoomManager.cs b/osu.Game/Screens/Multi/IRoomManager.cs
index f6c979851e..bf75843c3e 100644
--- a/osu.Game/Screens/Multi/IRoomManager.cs
+++ b/osu.Game/Screens/Multi/IRoomManager.cs
@@ -14,6 +14,11 @@ namespace osu.Game.Screens.Multi
///
event Action RoomsUpdated;
+ ///
+ /// Whether an initial listing of rooms has been received.
+ ///
+ Bindable InitialRoomsReceived { get; }
+
///
/// All the active s.
///
diff --git a/osu.Game/Screens/Multi/Lounge/LoungeSubScreen.cs b/osu.Game/Screens/Multi/Lounge/LoungeSubScreen.cs
index 7c10f0f975..d4b6a3b79f 100644
--- a/osu.Game/Screens/Multi/Lounge/LoungeSubScreen.cs
+++ b/osu.Game/Screens/Multi/Lounge/LoungeSubScreen.cs
@@ -22,12 +22,16 @@ namespace osu.Game.Screens.Multi.Lounge
protected readonly FilterControl Filter;
+ private readonly Bindable initialRoomsReceived = new Bindable();
+
private readonly Container content;
private readonly LoadingLayer loadingLayer;
[Resolved]
private Bindable selectedRoom { get; set; }
+ private bool joiningRoom;
+
public LoungeSubScreen()
{
SearchContainer searchContainer;
@@ -73,6 +77,14 @@ namespace osu.Game.Screens.Multi.Lounge
};
}
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+
+ initialRoomsReceived.BindTo(RoomManager.InitialRoomsReceived);
+ initialRoomsReceived.BindValueChanged(onInitialRoomsReceivedChanged, true);
+ }
+
protected override void UpdateAfterChildren()
{
base.UpdateAfterChildren();
@@ -126,12 +138,29 @@ namespace osu.Game.Screens.Multi.Lounge
private void joinRequested(Room room)
{
- loadingLayer.Show();
+ joiningRoom = true;
+ updateLoadingLayer();
+
RoomManager?.JoinRoom(room, r =>
{
Open(room);
+ joiningRoom = false;
+ updateLoadingLayer();
+ }, _ =>
+ {
+ joiningRoom = false;
+ updateLoadingLayer();
+ });
+ }
+
+ private void onInitialRoomsReceivedChanged(ValueChangedEvent received) => updateLoadingLayer();
+
+ private void updateLoadingLayer()
+ {
+ if (joiningRoom || !initialRoomsReceived.Value)
+ loadingLayer.Show();
+ else
loadingLayer.Hide();
- }, _ => loadingLayer.Hide());
}
///
diff --git a/osu.Game/Screens/Multi/Match/Components/MatchSettingsOverlay.cs b/osu.Game/Screens/Multi/Match/Components/MatchSettingsOverlay.cs
index 54c4f8f7c7..49a0fc434b 100644
--- a/osu.Game/Screens/Multi/Match/Components/MatchSettingsOverlay.cs
+++ b/osu.Game/Screens/Multi/Match/Components/MatchSettingsOverlay.cs
@@ -433,7 +433,7 @@ namespace osu.Game.Screens.Multi.Match.Components
}
}
- private class CreateRoomButton : TriangleButton
+ public class CreateRoomButton : TriangleButton
{
public CreateRoomButton()
{
diff --git a/osu.Game/Screens/Multi/Match/Components/ReadyButton.cs b/osu.Game/Screens/Multi/Match/Components/ReadyButton.cs
index 4420b2d58a..a64f24dd7e 100644
--- a/osu.Game/Screens/Multi/Match/Components/ReadyButton.cs
+++ b/osu.Game/Screens/Multi/Match/Components/ReadyButton.cs
@@ -3,6 +3,7 @@
using System;
using System.Linq;
+using System.Linq.Expressions;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Game.Beatmaps;
@@ -32,14 +33,14 @@ namespace osu.Game.Screens.Multi.Match.Components
Text = "Start";
}
- private IBindable> managerAdded;
+ private IBindable> managerUpdated;
private IBindable> managerRemoved;
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
- managerAdded = beatmaps.ItemAdded.GetBoundCopy();
- managerAdded.BindValueChanged(beatmapAdded);
+ managerUpdated = beatmaps.ItemUpdated.GetBoundCopy();
+ managerUpdated.BindValueChanged(beatmapUpdated);
managerRemoved = beatmaps.ItemRemoved.GetBoundCopy();
managerRemoved.BindValueChanged(beatmapRemoved);
@@ -52,24 +53,14 @@ namespace osu.Game.Screens.Multi.Match.Components
private void updateSelectedItem(PlaylistItem item)
{
- hasBeatmap = false;
-
- int? beatmapId = SelectedItem.Value?.Beatmap.Value?.OnlineBeatmapID;
- if (beatmapId == null)
- return;
-
- hasBeatmap = beatmaps.QueryBeatmap(b => b.OnlineBeatmapID == beatmapId) != null;
+ hasBeatmap = findBeatmap(expr => beatmaps.QueryBeatmap(expr));
}
- private void beatmapAdded(ValueChangedEvent> weakSet)
+ private void beatmapUpdated(ValueChangedEvent> weakSet)
{
if (weakSet.NewValue.TryGetTarget(out var set))
{
- int? beatmapId = SelectedItem.Value?.Beatmap.Value?.OnlineBeatmapID;
- if (beatmapId == null)
- return;
-
- if (set.Beatmaps.Any(b => b.OnlineBeatmapID == beatmapId))
+ if (findBeatmap(expr => set.Beatmaps.AsQueryable().FirstOrDefault(expr)))
Schedule(() => hasBeatmap = true);
}
}
@@ -78,15 +69,22 @@ namespace osu.Game.Screens.Multi.Match.Components
{
if (weakSet.NewValue.TryGetTarget(out var set))
{
- int? beatmapId = SelectedItem.Value?.Beatmap.Value?.OnlineBeatmapID;
- if (beatmapId == null)
- return;
-
- if (set.Beatmaps.Any(b => b.OnlineBeatmapID == beatmapId))
+ if (findBeatmap(expr => set.Beatmaps.AsQueryable().FirstOrDefault(expr)))
Schedule(() => hasBeatmap = false);
}
}
+ private bool findBeatmap(Func>, BeatmapInfo> expression)
+ {
+ int? beatmapId = SelectedItem.Value?.Beatmap.Value?.OnlineBeatmapID;
+ string checksum = SelectedItem.Value?.Beatmap.Value?.MD5Hash;
+
+ if (beatmapId == null || checksum == null)
+ return false;
+
+ return expression(b => b.OnlineBeatmapID == beatmapId && b.MD5Hash == checksum) != null;
+ }
+
protected override void Update()
{
base.Update();
diff --git a/osu.Game/Screens/Multi/Match/MatchSubScreen.cs b/osu.Game/Screens/Multi/Match/MatchSubScreen.cs
index b0717d3d28..9296fe81bd 100644
--- a/osu.Game/Screens/Multi/Match/MatchSubScreen.cs
+++ b/osu.Game/Screens/Multi/Match/MatchSubScreen.cs
@@ -55,7 +55,7 @@ namespace osu.Game.Screens.Multi.Match
private LeaderboardChatDisplay leaderboardChatDisplay;
private MatchSettingsOverlay settingsOverlay;
- private IBindable> managerAdded;
+ private IBindable> managerUpdated;
public MatchSubScreen(Room room)
{
@@ -210,8 +210,8 @@ namespace osu.Game.Screens.Multi.Match
SelectedItem.BindValueChanged(_ => Scheduler.AddOnce(selectedItemChanged));
SelectedItem.Value = playlist.FirstOrDefault();
- managerAdded = beatmapManager.ItemAdded.GetBoundCopy();
- managerAdded.BindValueChanged(beatmapAdded);
+ managerUpdated = beatmapManager.ItemUpdated.GetBoundCopy();
+ managerUpdated.BindValueChanged(beatmapUpdated);
}
public override bool OnExiting(IScreen next)
@@ -234,6 +234,8 @@ namespace osu.Game.Screens.Multi.Match
Ruleset.Value = item.Ruleset.Value;
}
+ private void beatmapUpdated(ValueChangedEvent> weakSet) => Schedule(updateWorkingBeatmap);
+
private void updateWorkingBeatmap()
{
var beatmap = SelectedItem.Value?.Beatmap.Value;
@@ -244,17 +246,6 @@ namespace osu.Game.Screens.Multi.Match
Beatmap.Value = beatmapManager.GetWorkingBeatmap(localBeatmap);
}
- private void beatmapAdded(ValueChangedEvent> weakSet)
- {
- Schedule(() =>
- {
- if (Beatmap.Value != beatmapManager.DefaultBeatmap)
- return;
-
- updateWorkingBeatmap();
- });
- }
-
private void onStart()
{
switch (type.Value)
diff --git a/osu.Game/Screens/Multi/RoomManager.cs b/osu.Game/Screens/Multi/RoomManager.cs
index ad461af57f..4d6ac46c84 100644
--- a/osu.Game/Screens/Multi/RoomManager.cs
+++ b/osu.Game/Screens/Multi/RoomManager.cs
@@ -25,6 +25,9 @@ namespace osu.Game.Screens.Multi
public event Action RoomsUpdated;
private readonly BindableList rooms = new BindableList();
+
+ public Bindable InitialRoomsReceived { get; } = new Bindable();
+
public IBindableList Rooms => rooms;
public double TimeBetweenListingPolls
@@ -62,7 +65,11 @@ namespace osu.Game.Screens.Multi
InternalChildren = new Drawable[]
{
- listingPollingComponent = new ListingPollingComponent { RoomsReceived = onListingReceived },
+ listingPollingComponent = new ListingPollingComponent
+ {
+ InitialRoomsReceived = { BindTarget = InitialRoomsReceived },
+ RoomsReceived = onListingReceived
+ },
selectionPollingComponent = new SelectionPollingComponent { RoomReceived = onSelectedRoomReceived }
};
}
@@ -262,6 +269,8 @@ namespace osu.Game.Screens.Multi
{
public Action> RoomsReceived;
+ public readonly Bindable InitialRoomsReceived = new Bindable();
+
[Resolved]
private IAPIProvider api { get; set; }
@@ -273,6 +282,8 @@ namespace osu.Game.Screens.Multi
{
currentFilter.BindValueChanged(_ =>
{
+ InitialRoomsReceived.Value = false;
+
if (IsLoaded)
PollImmediately();
});
@@ -292,6 +303,7 @@ namespace osu.Game.Screens.Multi
pollReq.Success += result =>
{
+ InitialRoomsReceived.Value = true;
RoomsReceived?.Invoke(result);
tcs.SetResult(true);
};
diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs
index 36198bcc65..83991ad027 100644
--- a/osu.Game/Screens/Play/Player.cs
+++ b/osu.Game/Screens/Play/Player.cs
@@ -259,8 +259,6 @@ namespace osu.Game.Screens.Play
Breaks = working.Beatmap.Breaks
}
});
-
- HealthProcessor.IsBreakTime.BindTo(breakTracker.IsBreakTime);
}
private void addOverlayComponents(Container target, WorkingBeatmap working)
diff --git a/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs b/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs
index fd8ac33aef..81d5d113ae 100644
--- a/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs
+++ b/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs
@@ -211,7 +211,7 @@ namespace osu.Game.Screens.Ranking.Expanded
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
Font = OsuFont.GetFont(size: 10, weight: FontWeight.SemiBold),
- Text = $"Played on {score.Date.ToLocalTime():g}"
+ Text = $"Played on {score.Date.ToLocalTime():d MMMM yyyy HH:mm}"
}
}
};
diff --git a/osu.Game/Screens/Ranking/ScorePanel.cs b/osu.Game/Screens/Ranking/ScorePanel.cs
index a99b48e8f0..65fb901c89 100644
--- a/osu.Game/Screens/Ranking/ScorePanel.cs
+++ b/osu.Game/Screens/Ranking/ScorePanel.cs
@@ -243,5 +243,10 @@ namespace osu.Game.Screens.Ranking
return true;
}
+
+ public override bool ReceivePositionalInputAt(Vector2 screenSpacePos)
+ => base.ReceivePositionalInputAt(screenSpacePos)
+ || topLayerContainer.ReceivePositionalInputAt(screenSpacePos)
+ || middleLayerContainer.ReceivePositionalInputAt(screenSpacePos);
}
}
diff --git a/osu.Game/Screens/Ranking/ScorePanelList.cs b/osu.Game/Screens/Ranking/ScorePanelList.cs
index 18db3f2af4..1142297274 100644
--- a/osu.Game/Screens/Ranking/ScorePanelList.cs
+++ b/osu.Game/Screens/Ranking/ScorePanelList.cs
@@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System;
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Bindables;
@@ -107,6 +108,9 @@ namespace osu.Game.Screens.Ranking
// Find the panel corresponding to the new score.
expandedPanel = flow.SingleOrDefault(p => p.Score == score.NewValue);
+ // handle horizontal scroll only when not hovering the expanded panel.
+ scroll.HandleScroll = () => expandedPanel?.IsHovered != true;
+
if (expandedPanel == null)
return;
@@ -166,6 +170,11 @@ namespace osu.Game.Screens.Ranking
///
public float? InstantScrollTarget;
+ ///
+ /// Whether this container should handle scroll trigger events.
+ ///
+ public Func HandleScroll;
+
protected override void UpdateAfterChildren()
{
if (InstantScrollTarget != null)
@@ -177,9 +186,9 @@ namespace osu.Game.Screens.Ranking
base.UpdateAfterChildren();
}
- public override bool HandlePositionalInput => false;
+ public override bool HandlePositionalInput => HandleScroll();
- public override bool HandleNonPositionalInput => false;
+ public override bool HandleNonPositionalInput => HandleScroll();
}
}
}
diff --git a/osu.Game/Screens/Ranking/SoloResultsScreen.cs b/osu.Game/Screens/Ranking/SoloResultsScreen.cs
index 3ae723683a..9cf2e6757a 100644
--- a/osu.Game/Screens/Ranking/SoloResultsScreen.cs
+++ b/osu.Game/Screens/Ranking/SoloResultsScreen.cs
@@ -5,6 +5,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Allocation;
+using osu.Game.Beatmaps;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests;
using osu.Game.Rulesets;
@@ -24,6 +25,9 @@ namespace osu.Game.Screens.Ranking
protected override APIRequest FetchScores(Action> scoresCallback)
{
+ if (Score.Beatmap.OnlineBeatmapID == null || Score.Beatmap.Status <= BeatmapSetOnlineStatus.Pending)
+ return null;
+
var req = new GetScoresRequest(Score.Beatmap, Score.Ruleset);
req.Success += r => scoresCallback?.Invoke(r.Scores.Where(s => s.OnlineScoreID != Score.OnlineScoreID).Select(s => s.CreateScoreInfo(rulesets)));
return req;
diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs
index f23e1b1ef2..2d714d1794 100644
--- a/osu.Game/Screens/Select/BeatmapCarousel.cs
+++ b/osu.Game/Screens/Select/BeatmapCarousel.cs
@@ -131,7 +131,7 @@ namespace osu.Game.Screens.Select
private CarouselRoot root;
- private IBindable> itemAdded;
+ private IBindable> itemUpdated;
private IBindable> itemRemoved;
private IBindable> itemHidden;
private IBindable> itemRestored;
@@ -166,8 +166,8 @@ namespace osu.Game.Screens.Select
RightClickScrollingEnabled.ValueChanged += enabled => scroll.RightMouseScrollbar = enabled.NewValue;
RightClickScrollingEnabled.TriggerChange();
- itemAdded = beatmaps.ItemAdded.GetBoundCopy();
- itemAdded.BindValueChanged(beatmapAdded);
+ itemUpdated = beatmaps.ItemUpdated.GetBoundCopy();
+ itemUpdated.BindValueChanged(beatmapUpdated);
itemRemoved = beatmaps.ItemRemoved.GetBoundCopy();
itemRemoved.BindValueChanged(beatmapRemoved);
itemHidden = beatmaps.BeatmapHidden.GetBoundCopy();
@@ -582,7 +582,7 @@ namespace osu.Game.Screens.Select
RemoveBeatmapSet(item);
}
- private void beatmapAdded(ValueChangedEvent> weakItem)
+ private void beatmapUpdated(ValueChangedEvent> weakItem)
{
if (weakItem.NewValue.TryGetTarget(out var item))
UpdateBeatmapSet(item);
diff --git a/osu.Game/Screens/Select/Carousel/TopLocalRank.cs b/osu.Game/Screens/Select/Carousel/TopLocalRank.cs
index aed25787b0..3ad57c1cb0 100644
--- a/osu.Game/Screens/Select/Carousel/TopLocalRank.cs
+++ b/osu.Game/Screens/Select/Carousel/TopLocalRank.cs
@@ -28,7 +28,7 @@ namespace osu.Game.Screens.Select.Carousel
[Resolved]
private IAPIProvider api { get; set; }
- private IBindable> itemAdded;
+ private IBindable> itemUpdated;
private IBindable> itemRemoved;
public TopLocalRank(BeatmapInfo beatmap)
@@ -40,8 +40,8 @@ namespace osu.Game.Screens.Select.Carousel
[BackgroundDependencyLoader]
private void load()
{
- itemAdded = scores.ItemAdded.GetBoundCopy();
- itemAdded.BindValueChanged(scoreChanged);
+ itemUpdated = scores.ItemUpdated.GetBoundCopy();
+ itemUpdated.BindValueChanged(scoreChanged);
itemRemoved = scores.ItemRemoved.GetBoundCopy();
itemRemoved.BindValueChanged(scoreChanged);
diff --git a/osu.Game/Screens/Select/PlaySongSelect.cs b/osu.Game/Screens/Select/PlaySongSelect.cs
index 0a4c0e2085..2236aa4d72 100644
--- a/osu.Game/Screens/Select/PlaySongSelect.cs
+++ b/osu.Game/Screens/Select/PlaySongSelect.cs
@@ -7,6 +7,8 @@ using osu.Framework.Graphics.Sprites;
using osu.Framework.Input.Events;
using osu.Framework.Screens;
using osu.Game.Graphics;
+using osu.Game.Overlays;
+using osu.Game.Overlays.Notifications;
using osu.Game.Scoring;
using osu.Game.Screens.Play;
using osu.Game.Screens.Ranking;
@@ -20,6 +22,9 @@ namespace osu.Game.Screens.Select
private bool removeAutoModOnResume;
private OsuScreen player;
+ [Resolved(CanBeNull = true)]
+ private NotificationOverlay notifications { get; set; }
+
public override bool AllowExternalScreenChange => true;
protected override UserActivity InitialActivity => new UserActivity.ChoosingBeatmap();
@@ -49,8 +54,11 @@ namespace osu.Game.Screens.Select
if (removeAutoModOnResume)
{
- var autoType = Ruleset.Value.CreateInstance().GetAutoplayMod().GetType();
- ModSelect.DeselectTypes(new[] { autoType }, true);
+ var autoType = Ruleset.Value.CreateInstance().GetAutoplayMod()?.GetType();
+
+ if (autoType != null)
+ ModSelect.DeselectTypes(new[] { autoType }, true);
+
removeAutoModOnResume = false;
}
}
@@ -78,10 +86,19 @@ namespace osu.Game.Screens.Select
if (GetContainingInputManager().CurrentState?.Keyboard.ControlPressed == true)
{
var auto = Ruleset.Value.CreateInstance().GetAutoplayMod();
- var autoType = auto.GetType();
+ var autoType = auto?.GetType();
var mods = Mods.Value;
+ if (autoType == null)
+ {
+ notifications?.Post(new SimpleNotification
+ {
+ Text = "The current ruleset doesn't have an autoplay mod avalaible!"
+ });
+ return false;
+ }
+
if (mods.All(m => m.GetType() != autoType))
{
Mods.Value = mods.Append(auto).ToArray();
diff --git a/osu.Game/Tests/Beatmaps/TestBeatmap.cs b/osu.Game/Tests/Beatmaps/TestBeatmap.cs
index a7c84bf692..9fc20fd0f2 100644
--- a/osu.Game/Tests/Beatmaps/TestBeatmap.cs
+++ b/osu.Game/Tests/Beatmaps/TestBeatmap.cs
@@ -1,9 +1,11 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
+using osu.Framework.Extensions;
using osu.Game.Beatmaps;
using osu.Game.IO;
using osu.Game.Rulesets;
@@ -43,10 +45,25 @@ namespace osu.Game.Tests.Beatmaps
private static Beatmap createTestBeatmap()
{
using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(test_beatmap_data)))
- using (var reader = new LineBufferedReader(stream))
- return Decoder.GetDecoder(reader).Decode(reader);
+ {
+ using (var reader = new LineBufferedReader(stream))
+ {
+ var b = Decoder.GetDecoder(reader).Decode(reader);
+
+ b.BeatmapInfo.MD5Hash = test_beatmap_hash.Value.md5;
+ b.BeatmapInfo.Hash = test_beatmap_hash.Value.sha2;
+
+ return b;
+ }
+ }
}
+ private static readonly Lazy<(string md5, string sha2)> test_beatmap_hash = new Lazy<(string md5, string sha2)>(() =>
+ {
+ using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(test_beatmap_data)))
+ return (stream.ComputeMD5Hash(), stream.ComputeSHA2Hash());
+ });
+
private const string test_beatmap_data = @"osu file format v14
[General]
diff --git a/osu.Game/Tests/Visual/OsuTestScene.cs b/osu.Game/Tests/Visual/OsuTestScene.cs
index 5dc8714c07..632d668a01 100644
--- a/osu.Game/Tests/Visual/OsuTestScene.cs
+++ b/osu.Game/Tests/Visual/OsuTestScene.cs
@@ -6,6 +6,7 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
+using osu.Framework;
using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Audio.Track;
@@ -118,7 +119,7 @@ namespace osu.Game.Tests.Visual
}
}
- localStorage = new Lazy(() => new NativeStorage($"{GetType().Name}-{Guid.NewGuid()}"));
+ localStorage = new Lazy(() => new NativeStorage(Path.Combine(RuntimeInfo.StartupDirectory, $"{GetType().Name}-{Guid.NewGuid()}")));
}
[Resolved]
diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj
index d5017a436f..8213719c01 100644
--- a/osu.Game/osu.Game.csproj
+++ b/osu.Game/osu.Game.csproj
@@ -19,14 +19,14 @@
-
-
+
+
-
-
-
+
+
+
diff --git a/osu.iOS.props b/osu.iOS.props
index 19a36f1e1f..fd13455c63 100644
--- a/osu.iOS.props
+++ b/osu.iOS.props
@@ -70,17 +70,17 @@
-
-
+
+
-
-
+
+
-
+
diff --git a/osu.sln.DotSettings b/osu.sln.DotSettings
index e3b64c03b9..b9fc3de734 100644
--- a/osu.sln.DotSettings
+++ b/osu.sln.DotSettings
@@ -1,4 +1,4 @@
-
+
True
True
True
@@ -905,14 +905,17 @@ private void load()
True
True
True
+ True
True
True
True
True
True
True
+ True
True
True
True
+ True
True
True