diff --git a/.github/ISSUE_TEMPLATE/bug-issue.yml b/.github/ISSUE_TEMPLATE/bug-issue.yml
index 91ca622f55..ff6d869e72 100644
--- a/.github/ISSUE_TEMPLATE/bug-issue.yml
+++ b/.github/ISSUE_TEMPLATE/bug-issue.yml
@@ -58,7 +58,8 @@ body:
The default places to find the logs on desktop platforms are as follows:
- `%AppData%/osu/logs` *on Windows*
- - `~/.local/share/osu/logs` *on Linux & macOS*
+ - `~/.local/share/osu/logs` *on Linux*
+ - `~/Library/Application Support/osu/logs` *on macOS*
If you have selected a custom location for the game files, you can find the `logs` folder there.
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 56b3ebe87b..5c11f91994 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -31,7 +31,7 @@ jobs:
run: dotnet tool restore
- name: Restore Packages
- run: dotnet restore
+ run: dotnet restore osu.Desktop.slnf
- name: Restore inspectcode cache
uses: actions/cache@v3
@@ -113,11 +113,11 @@ jobs:
with:
dotnet-version: "6.0.x"
- - name: Setup MSBuild
- uses: microsoft/setup-msbuild@v1
+ - name: Install .NET workloads
+ run: dotnet workload install maui-android
- - name: Build
- run: msbuild osu.Android/osu.Android.csproj /restore /p:Configuration=Debug
+ - name: Compile
+ run: dotnet build -c Debug osu.Android.slnf
build-only-ios:
name: Build only (iOS)
@@ -132,8 +132,8 @@ jobs:
with:
dotnet-version: "6.0.x"
- # Contrary to seemingly any other msbuild, msbuild running on macOS/Mono
- # cannot accept .sln(f) files as arguments.
- # Build just the main game for now.
+ - name: Install .NET Workloads
+ run: dotnet workload install maui-ios
+
- name: Build
- run: msbuild osu.iOS/osu.iOS.csproj /restore /p:Configuration=Debug
+ run: dotnet build -c Debug osu.iOS
diff --git a/.github/workflows/report-nunit.yml b/.github/workflows/report-nunit.yml
index bfc9620174..99e39f6f56 100644
--- a/.github/workflows/report-nunit.yml
+++ b/.github/workflows/report-nunit.yml
@@ -28,7 +28,7 @@ jobs:
timeout-minutes: 5
steps:
- name: Annotate CI run with test results
- uses: dorny/test-reporter@v1.4.2
+ uses: dorny/test-reporter@v1.6.0
with:
artifact: osu-test-results-${{matrix.os.prettyname}}-${{matrix.threadingMode}}
name: Test Results (${{matrix.os.prettyname}}, ${{matrix.threadingMode}})
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index ae2bdd2e82..9f7d88f5c7 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -2,136 +2,87 @@
Thank you for showing interest in the development of osu!. 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)
+1. [Reporting bugs](#reporting-bugs)
+2. [Providing general feedback](#providing-general-feedback)
+3. [Issue or discussion?](#issue-or-discussion)
+4. [Submitting pull requests](#submitting-pull-requests)
+5. [Resources](#resources)
-## I would like to submit an issue!
+## Reporting bugs
-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.
+A **bug** is a situation in which there is something clearly *and objectively* wrong with the game. Examples of applicable bug reports are:
-* **Before submitting an issue, try searching existing issues first.**
+- The game crashes to desktop when I start a beatmap
+- Friends appear twice in the friend listing
+- The game slows down a lot when I play this specific map
+- A piece of text is overlapping another piece of text on the screen
- 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.
+To track bug reports, we primarily use GitHub **issues**. When opening an issue, please keep in mind the following:
-* **When submitting a bug report, please try to include as much detail as possible.**
+- Before opening the issue, please search for any similar existing issues using the text search bar and the issue labels. This includes both open and closed issues (we may have already fixed something, but the fix hasn't yet been released).
+- When opening the issue, please fill out as much of the issue template as you can. In particular, please make sure to include logs and screenshots as much as possible. The instructions on how to find the log files are included in the issue template.
+- We may ask you for follow-up information to reproduce or debug the problem. Please look out for this and provide follow-up info if we request it.
- 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:
+If we cannot reproduce the issue, it is deemed low priority, or it is deemed to be specific to your setup in some way, the issue may be downgraded to a discussion. This will be done by a maintainer for you.
- * 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/files/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.
+## Providing general feedback
-* **Provide more information when asked to do so.**
+If you wish to:
- 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 osu! 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!
+- provide *subjective* feedback on the game (about how the UI looks, about how the default skin works, about game mechanics, about how the PP and scoring systems work, etc.),
+- suggest a new feature to be added to the game,
+- report a non-specific problem with the game that you think may be connected to your hardware or operating system specifically,
-* **When submitting a feature proposal, please describe it in the most understandable way you can.**
+then it is generally best to start with a **discussion** first. Discussions are a good avenue to group subjective feedback on a single topic, or gauge interest in a particular feature request.
- 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.
+When opening a discussion, please keep in mind the following:
-* **Refrain from posting "+1" comments.**
+- Use the search function to see if your idea has been proposed before, or if there is already a thread about a particular issue you wish to raise.
+- If proposing a feature, please try to explain the feature in as much detail as possible.
+- If you're reporting a non-specific problem, please provide applicable logs, screenshots, or video that illustrate the issue.
- 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.
+If a discussion gathers enough traction, then it may be converted into an issue. This will be done by a maintainer for you.
-* **Refrain from asking if an issue has been resolved yet.**
+## Issue or discussion?
- 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.
+We realise that the line between an issue and a discussion may be fuzzy, so while we ask you to use your best judgement based on the description above, please don't think about it too hard either. Feedback in a slightly wrong place is better than no feedback at all.
-* **Avoid long discussions about non-development topics.**
+When in doubt, it's probably best to start with a discussion first. We will escalate to issues as needed.
- 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.
+## Submitting pull requests
-## I would like to submit a pull request!
+While pull requests from unaffiliated contributors are welcome, please note that due to significant community interest and limited review throughput, the core team's primary focus is on the issues which are currently [on the roadmap](https://github.com/orgs/ppy/projects/7/views/6). Reviewing PRs that fall outside of the scope of the roadmap is done on a best-effort basis, so please be aware that it may take a while before a core maintainer gets around to review your change.
-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.
+The [issue tracker](https://github.com/ppy/osu/issues) should provide plenty of issues to start with. We also have a [`good-first-issue`](https://github.com/ppy/osu/issues?q=is%3Aissue+is%3Aopen+label%3Agood-first-issue) label, although from experience it is not used very often, as it is relatively rare that we can spot an issue that will definitively be a good first issue for a new contributor regardless of their programming experience.
-However, do keep in mind that the core team is committed to bringing osu!(lazer) up to par with osu!(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).
+In the case of simple issues, a direct PR is okay. However, if you decide to work on an existing issue which doesn't seem trivial, **please ask us first**. This way we can try to estimate if it is a good fit for you and provide the correct direction on how to address it. In addition, note that while we do not rule out external contributors from working on roadmapped issues, we will generally prefer to handle them ourselves unless they're not very time sensitive.
-Here are some key things to note before jumping in:
+If you'd like to propose a subjective change to one of the visual aspects of the game, or there is a bigger task you'd like to work on, but there is no corresponding issue or discussion thread yet for it, **please open a discussion or issue first** to avoid wasted effort. This in particular applies if you want to work on [one of the available designs from the osu! public Figma library](https://www.figma.com/file/6m10GiGEncVFWmgOoSyakH/osu!-Figma-Library).
-* **Make sure you are comfortable with C\# and your development environment.**
+Aside from the above, below is a brief checklist of things to watch out when you're preparing your code changes:
- 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.
+- Make sure you're comfortable with the principles of object-oriented programming, the syntax of C\# and your development environment.
+- Make sure you are familiar with [git](https://git-scm.com/) and [the pull request workflow](https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/proposing-changes-to-your-work-with-pull-requests).
+- Please do not make code changes via the GitHub web interface.
+- Please add tests for your changes. We expect most new features and bugfixes to have test coverage, unless the effort of adding them is prohibitive. The visual testing methodology we use is described in more detail [here](https://github.com/ppy/osu-framework/wiki/Development-and-Testing).
+- Please run tests and code style analysis (via `InspectCode.{ps1,sh}` scripts in the root of this repository) before opening the PR. This is particularly important if you're a first-time contributor, as CI will not run for your PR until we allow it to do so.
- 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.
+After you're done with your changes and you wish to open the PR, please observe the following recommendations:
-* **Make sure you are familiar with git and the pull request workflow.**
+- Please submit the pull request from a [topic branch](https://git-scm.com/book/en/v2/Git-Branching-Branching-Workflows#_topic_branch) (not `master`), and keep the *Allow edits from maintainers* check box selected, so that we can push fixes to your PR if necessary.
+- Please avoid pushing untested or incomplete code.
+- Please do not force-push or rebase unless we ask you to.
+- Please do not merge `master` continually if there are no conflicts to resolve. We will do this for you when the change is ready for merge.
- [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.
+We are highly committed to quality when it comes to the osu! 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.
- 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).
+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, discussion, 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.
-* **Double-check designs before starting work on new functionality.**
+## Resources
- 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 osu! 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.
+- [Development roadmap](https://github.com/orgs/ppy/projects/7/views/6): What the core team is currently working on
+- [`ppy/osu-framework` wiki](https://github.com/ppy/osu-framework/wiki): Contains introductory information about osu!framework, the bespoke 2D game framework we use for the game
+- [`ppy/osu` wiki](https://github.com/ppy/osu/wiki): Contains articles about various technical aspects of the game
+- [Public Figma library](https://www.figma.com/file/6m10GiGEncVFWmgOoSyakH/osu!-Figma-Library): Contains finished and draft designs for osu!
diff --git a/Directory.Build.props b/Directory.Build.props
index 235feea8ce..3b6b985961 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -17,7 +17,7 @@
-
+
diff --git a/Gemfile b/Gemfile
deleted file mode 100644
index cdd3a6b349..0000000000
--- a/Gemfile
+++ /dev/null
@@ -1,6 +0,0 @@
-source "https://rubygems.org"
-
-gem "fastlane"
-
-plugins_path = File.join(File.dirname(__FILE__), 'fastlane', 'Pluginfile')
-eval_gemfile(plugins_path) if File.exist?(plugins_path)
diff --git a/Gemfile.lock b/Gemfile.lock
deleted file mode 100644
index 07ca3542f9..0000000000
--- a/Gemfile.lock
+++ /dev/null
@@ -1,234 +0,0 @@
-GEM
- remote: https://rubygems.org/
- specs:
- CFPropertyList (3.0.5)
- rexml
- addressable (2.8.1)
- public_suffix (>= 2.0.2, < 6.0)
- artifactory (3.0.15)
- atomos (0.1.3)
- aws-eventstream (1.2.0)
- aws-partitions (1.653.0)
- aws-sdk-core (3.166.0)
- aws-eventstream (~> 1, >= 1.0.2)
- aws-partitions (~> 1, >= 1.651.0)
- aws-sigv4 (~> 1.5)
- jmespath (~> 1, >= 1.6.1)
- aws-sdk-kms (1.59.0)
- aws-sdk-core (~> 3, >= 3.165.0)
- aws-sigv4 (~> 1.1)
- aws-sdk-s3 (1.117.1)
- aws-sdk-core (~> 3, >= 3.165.0)
- aws-sdk-kms (~> 1)
- aws-sigv4 (~> 1.4)
- aws-sigv4 (1.5.2)
- aws-eventstream (~> 1, >= 1.0.2)
- babosa (1.0.4)
- claide (1.1.0)
- colored (1.2)
- colored2 (3.1.2)
- commander (4.6.0)
- highline (~> 2.0.0)
- declarative (0.0.20)
- digest-crc (0.6.4)
- rake (>= 12.0.0, < 14.0.0)
- domain_name (0.5.20190701)
- unf (>= 0.0.5, < 1.0.0)
- dotenv (2.8.1)
- emoji_regex (3.2.3)
- excon (0.93.1)
- faraday (1.10.2)
- faraday-em_http (~> 1.0)
- faraday-em_synchrony (~> 1.0)
- faraday-excon (~> 1.1)
- faraday-httpclient (~> 1.0)
- faraday-multipart (~> 1.0)
- faraday-net_http (~> 1.0)
- faraday-net_http_persistent (~> 1.0)
- faraday-patron (~> 1.0)
- faraday-rack (~> 1.0)
- faraday-retry (~> 1.0)
- ruby2_keywords (>= 0.0.4)
- faraday-cookie_jar (0.0.7)
- faraday (>= 0.8.0)
- http-cookie (~> 1.0.0)
- faraday-em_http (1.0.0)
- faraday-em_synchrony (1.0.0)
- faraday-excon (1.1.0)
- faraday-httpclient (1.0.1)
- faraday-multipart (1.0.4)
- multipart-post (~> 2)
- faraday-net_http (1.0.1)
- faraday-net_http_persistent (1.2.0)
- faraday-patron (1.0.0)
- faraday-rack (1.0.0)
- faraday-retry (1.0.3)
- faraday_middleware (1.2.0)
- faraday (~> 1.0)
- fastimage (2.2.6)
- fastlane (2.210.1)
- CFPropertyList (>= 2.3, < 4.0.0)
- addressable (>= 2.8, < 3.0.0)
- artifactory (~> 3.0)
- aws-sdk-s3 (~> 1.0)
- babosa (>= 1.0.3, < 2.0.0)
- bundler (>= 1.12.0, < 3.0.0)
- colored
- commander (~> 4.6)
- dotenv (>= 2.1.1, < 3.0.0)
- emoji_regex (>= 0.1, < 4.0)
- excon (>= 0.71.0, < 1.0.0)
- faraday (~> 1.0)
- faraday-cookie_jar (~> 0.0.6)
- faraday_middleware (~> 1.0)
- fastimage (>= 2.1.0, < 3.0.0)
- gh_inspector (>= 1.1.2, < 2.0.0)
- google-apis-androidpublisher_v3 (~> 0.3)
- google-apis-playcustomapp_v1 (~> 0.1)
- google-cloud-storage (~> 1.31)
- highline (~> 2.0)
- json (< 3.0.0)
- jwt (>= 2.1.0, < 3)
- mini_magick (>= 4.9.4, < 5.0.0)
- multipart-post (~> 2.0.0)
- naturally (~> 2.2)
- optparse (~> 0.1.1)
- plist (>= 3.1.0, < 4.0.0)
- rubyzip (>= 2.0.0, < 3.0.0)
- security (= 0.1.3)
- simctl (~> 1.6.3)
- terminal-notifier (>= 2.0.0, < 3.0.0)
- terminal-table (>= 1.4.5, < 2.0.0)
- tty-screen (>= 0.6.3, < 1.0.0)
- tty-spinner (>= 0.8.0, < 1.0.0)
- word_wrap (~> 1.0.0)
- xcodeproj (>= 1.13.0, < 2.0.0)
- xcpretty (~> 0.3.0)
- xcpretty-travis-formatter (>= 0.0.3)
- fastlane-plugin-clean_testflight_testers (0.3.0)
- fastlane-plugin-souyuz (0.11.1)
- souyuz (= 0.11.1)
- fastlane-plugin-xamarin (0.6.3)
- gh_inspector (1.1.3)
- google-apis-androidpublisher_v3 (0.29.0)
- google-apis-core (>= 0.9.0, < 2.a)
- google-apis-core (0.9.1)
- addressable (~> 2.5, >= 2.5.1)
- googleauth (>= 0.16.2, < 2.a)
- httpclient (>= 2.8.1, < 3.a)
- mini_mime (~> 1.0)
- representable (~> 3.0)
- retriable (>= 2.0, < 4.a)
- rexml
- webrick
- google-apis-iamcredentials_v1 (0.15.0)
- google-apis-core (>= 0.9.0, < 2.a)
- google-apis-playcustomapp_v1 (0.12.0)
- google-apis-core (>= 0.9.1, < 2.a)
- google-apis-storage_v1 (0.19.0)
- google-apis-core (>= 0.9.0, < 2.a)
- google-cloud-core (1.6.0)
- google-cloud-env (~> 1.0)
- google-cloud-errors (~> 1.0)
- google-cloud-env (1.6.0)
- faraday (>= 0.17.3, < 3.0)
- google-cloud-errors (1.3.0)
- google-cloud-storage (1.43.0)
- addressable (~> 2.8)
- digest-crc (~> 0.4)
- google-apis-iamcredentials_v1 (~> 0.1)
- google-apis-storage_v1 (~> 0.19.0)
- google-cloud-core (~> 1.6)
- googleauth (>= 0.16.2, < 2.a)
- mini_mime (~> 1.0)
- googleauth (1.3.0)
- faraday (>= 0.17.3, < 3.a)
- jwt (>= 1.4, < 3.0)
- memoist (~> 0.16)
- multi_json (~> 1.11)
- os (>= 0.9, < 2.0)
- signet (>= 0.16, < 2.a)
- highline (2.0.3)
- http-cookie (1.0.5)
- domain_name (~> 0.5)
- httpclient (2.8.3)
- jmespath (1.6.1)
- json (2.6.2)
- jwt (2.5.0)
- memoist (0.16.2)
- mini_magick (4.11.0)
- mini_mime (1.1.2)
- mini_portile2 (2.8.0)
- multi_json (1.15.0)
- multipart-post (2.0.0)
- nanaimo (0.3.0)
- naturally (2.2.1)
- nokogiri (1.13.9)
- mini_portile2 (~> 2.8.0)
- racc (~> 1.4)
- optparse (0.1.1)
- os (1.1.4)
- plist (3.6.0)
- public_suffix (5.0.0)
- racc (1.6.0)
- rake (13.0.6)
- representable (3.2.0)
- declarative (< 0.1.0)
- trailblazer-option (>= 0.1.1, < 0.2.0)
- uber (< 0.2.0)
- retriable (3.1.2)
- rexml (3.2.5)
- rouge (2.0.7)
- ruby2_keywords (0.0.5)
- rubyzip (2.3.2)
- security (0.1.3)
- signet (0.17.0)
- addressable (~> 2.8)
- faraday (>= 0.17.5, < 3.a)
- jwt (>= 1.5, < 3.0)
- multi_json (~> 1.10)
- simctl (1.6.8)
- CFPropertyList
- naturally
- souyuz (0.11.1)
- fastlane (>= 2.182.0)
- highline (~> 2.0)
- nokogiri (~> 1.7)
- terminal-notifier (2.0.0)
- terminal-table (1.8.0)
- unicode-display_width (~> 1.1, >= 1.1.1)
- trailblazer-option (0.1.2)
- tty-cursor (0.7.1)
- tty-screen (0.8.1)
- tty-spinner (0.9.3)
- tty-cursor (~> 0.7)
- uber (0.1.0)
- unf (0.1.4)
- unf_ext
- unf_ext (0.0.8.2)
- unicode-display_width (1.8.0)
- webrick (1.7.0)
- word_wrap (1.0.0)
- xcodeproj (1.22.0)
- CFPropertyList (>= 2.3.3, < 4.0)
- atomos (~> 0.1.3)
- claide (>= 1.0.2, < 2.0)
- colored2 (~> 3.1)
- nanaimo (~> 0.3.0)
- rexml (~> 3.2.4)
- xcpretty (0.3.0)
- rouge (~> 2.0.7)
- xcpretty-travis-formatter (1.0.1)
- xcpretty (~> 0.2, >= 0.0.7)
-
-PLATFORMS
- ruby
-
-DEPENDENCIES
- fastlane
- fastlane-plugin-clean_testflight_testers
- fastlane-plugin-souyuz
- fastlane-plugin-xamarin
-
-BUNDLED WITH
- 2.0.1
diff --git a/README.md b/README.md
index 75d61dad4d..f3f025fa10 100644
--- a/README.md
+++ b/README.md
@@ -32,7 +32,7 @@ If you are looking to install or test osu! without setting up a development envi
**Latest build:**
-| [Windows 8.1+ (x64)](https://github.com/ppy/osu/releases/latest/download/install.exe) | macOS 10.15+ ([Intel](https://github.com/ppy/osu/releases/latest/download/osu.app.Intel.zip), [Apple Silicon](https://github.com/ppy/osu/releases/latest/download/osu.app.Apple.Silicon.zip)) | [Linux (x64)](https://github.com/ppy/osu/releases/latest/download/osu.AppImage) | [iOS 10+](https://osu.ppy.sh/home/testflight) | [Android 5+](https://github.com/ppy/osu/releases/latest/download/sh.ppy.osulazer.apk) |
+| [Windows 8.1+ (x64)](https://github.com/ppy/osu/releases/latest/download/install.exe) | macOS 10.15+ ([Intel](https://github.com/ppy/osu/releases/latest/download/osu.app.Intel.zip), [Apple Silicon](https://github.com/ppy/osu/releases/latest/download/osu.app.Apple.Silicon.zip)) | [Linux (x64)](https://github.com/ppy/osu/releases/latest/download/osu.AppImage) | [iOS 13.4+](https://osu.ppy.sh/home/testflight) | [Android 5+](https://github.com/ppy/osu/releases/latest/download/sh.ppy.osulazer.apk) |
| ------------- | ------------- | ------------- | ------------- | ------------- |
- The iOS testflight link may fill up (Apple has a hard limit of 10,000 users). We reset it occasionally when this happens. Please do not ask about this. Check back regularly for link resets or follow [peppy](https://twitter.com/ppy) on twitter for announcements of link resets.
@@ -101,9 +101,7 @@ JetBrains ReSharper InspectCode is also used for wider rule sets. You can run it
## Contributing
-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.
+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. Please refer to the [contributing guidelines](CONTRIBUTING.md) to understand how to help in the most effective way possible.
If you wish to help with localisation efforts, head over to [crowdin](https://crowdin.com/project/osu-web).
diff --git a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/osu.Game.Rulesets.EmptyFreeform.Tests.csproj b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/osu.Game.Rulesets.EmptyFreeform.Tests.csproj
index 52b728a115..a1c53ece03 100644
--- a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/osu.Game.Rulesets.EmptyFreeform.Tests.csproj
+++ b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/osu.Game.Rulesets.EmptyFreeform.Tests.csproj
@@ -9,9 +9,9 @@
false
-
+
-
+
diff --git a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/osu.Game.Rulesets.EmptyFreeform.csproj b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/osu.Game.Rulesets.EmptyFreeform.csproj
index 092a013614..d09e7647e0 100644
--- a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/osu.Game.Rulesets.EmptyFreeform.csproj
+++ b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/osu.Game.Rulesets.EmptyFreeform.csproj
@@ -1,6 +1,6 @@
- netstandard2.1
+ net6.0
osu.Game.Rulesets.EmptyFreeform
Library
AnyCPU
@@ -12,4 +12,4 @@
-
\ No newline at end of file
+
diff --git a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj
index 95b96adab0..683e9fd5e8 100644
--- a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj
+++ b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj
@@ -9,9 +9,9 @@
false
-
+
-
+
diff --git a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/osu.Game.Rulesets.Pippidon.csproj b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/osu.Game.Rulesets.Pippidon.csproj
index a3607343c9..9c8867f4ef 100644
--- a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/osu.Game.Rulesets.Pippidon.csproj
+++ b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/osu.Game.Rulesets.Pippidon.csproj
@@ -1,6 +1,6 @@
- netstandard2.1
+ net6.0
osu.Game.Rulesets.Pippidon
Library
AnyCPU
@@ -12,4 +12,4 @@
-
\ No newline at end of file
+
diff --git a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/osu.Game.Rulesets.EmptyScrolling.Tests.csproj b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/osu.Game.Rulesets.EmptyScrolling.Tests.csproj
index d12403016d..b7a7fff18a 100644
--- a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/osu.Game.Rulesets.EmptyScrolling.Tests.csproj
+++ b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/osu.Game.Rulesets.EmptyScrolling.Tests.csproj
@@ -9,9 +9,9 @@
false
-
+
-
+
diff --git a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/osu.Game.Rulesets.EmptyScrolling.csproj b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/osu.Game.Rulesets.EmptyScrolling.csproj
index 2ea52429ab..5bf3884f53 100644
--- a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/osu.Game.Rulesets.EmptyScrolling.csproj
+++ b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/osu.Game.Rulesets.EmptyScrolling.csproj
@@ -1,6 +1,6 @@
- netstandard2.1
+ net6.0
osu.Game.Rulesets.EmptyScrolling
Library
AnyCPU
@@ -12,4 +12,4 @@
-
\ No newline at end of file
+
diff --git a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj
index 95b96adab0..683e9fd5e8 100644
--- a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj
+++ b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj
@@ -9,9 +9,9 @@
false
-
+
-
+
diff --git a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/osu.Game.Rulesets.Pippidon.csproj b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/osu.Game.Rulesets.Pippidon.csproj
index a3607343c9..9c8867f4ef 100644
--- a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/osu.Game.Rulesets.Pippidon.csproj
+++ b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/osu.Game.Rulesets.Pippidon.csproj
@@ -1,6 +1,6 @@
- netstandard2.1
+ net6.0
osu.Game.Rulesets.Pippidon
Library
AnyCPU
@@ -12,4 +12,4 @@
-
\ No newline at end of file
+
diff --git a/appveyor.yml b/appveyor.yml
index 5be73f9875..ed48a997e8 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -1,6 +1,6 @@
clone_depth: 1
version: '{branch}-{build}'
-image: Visual Studio 2019
+image: Visual Studio 2022
cache:
- '%LOCALAPPDATA%\NuGet\v3-cache -> appveyor.yml'
@@ -11,6 +11,8 @@ dotnet_csproj:
before_build:
- cmd: dotnet --info # Useful when version mismatch between CI and local
+ - cmd: dotnet workload install maui-android # Change to `dotnet workload restore` once there's no old projects
+ - cmd: dotnet workload install maui-ios # Change to `dotnet workload restore` once there's no old projects
- cmd: nuget restore -verbosity quiet # Only nuget.exe knows both new (.NET Core) and old (Xamarin) projects
build:
diff --git a/appveyor_deploy.yml b/appveyor_deploy.yml
index adf98848bc..175c8d0f1b 100644
--- a/appveyor_deploy.yml
+++ b/appveyor_deploy.yml
@@ -1,6 +1,6 @@
clone_depth: 1
version: '{build}'
-image: Visual Studio 2019
+image: Visual Studio 2022
test: off
skip_non_tags: true
configuration: Release
@@ -83,4 +83,4 @@ artifacts:
deploy:
- provider: Environment
- name: nuget
\ No newline at end of file
+ name: nuget
diff --git a/fastlane/Appfile b/fastlane/Appfile
deleted file mode 100644
index 083de66985..0000000000
--- a/fastlane/Appfile
+++ /dev/null
@@ -1,2 +0,0 @@
-app_identifier("sh.ppy.osulazer") # The bundle identifier of your app
-apple_id("apple-dev@ppy.sh") # Your Apple email address
diff --git a/fastlane/Fastfile b/fastlane/Fastfile
deleted file mode 100644
index 716115e5c6..0000000000
--- a/fastlane/Fastfile
+++ /dev/null
@@ -1,147 +0,0 @@
-update_fastlane
-
-platform :android do
-desc 'Deploy to play store'
- lane :beta do |options|
-
- update_version(
- version: options[:version],
- build: options[:build],
- )
-
- build(options)
-
- supply(
- apk: './osu.Android/bin/Release/sh.ppy.osulazer-Signed.apk',
- package_name: 'sh.ppy.osulazer',
- track: 'alpha', # upload to alpha, we can promote it later
- json_key: options[:json_key],
- )
- end
-
- desc 'Deploy to github release'
- lane :build_github do |options|
-
- update_version(
- version: options[:version],
- build: options[:build],
- )
-
- build(options)
-
- client = HTTPClient.new
- changelog = client.get_content 'https://gist.githubusercontent.com/peppy/aaa2ec1a323554b619671cac6dbbb776/raw'
- changelog.gsub!('$BUILD_ID', options[:build])
-
- set_github_release(
- repository_name: "ppy/osu",
- api_token: ENV["GITHUB_TOKEN"],
- name: options[:build],
- tag_name: options[:build],
- is_draft: true,
- description: changelog,
- commitish: "master",
- upload_assets: ["osu.Android/bin/Release/sh.ppy.osulazer.apk"]
- )
-
- end
-
- desc 'Compile the project'
- lane :build do |options|
- nuget_restore(project_path: 'osu.Android/osu.Android.csproj')
- nuget_restore(project_path: 'osu.Game/osu.Game.csproj')
- nuget_restore(project_path: 'osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj')
- nuget_restore(project_path: 'osu.Game.Rulesets.Taiko/osu.Game.Rulesets.Taiko.csproj')
- nuget_restore(project_path: 'osu.Game.Rulesets.Catch/osu.Game.Rulesets.Catch.csproj')
- nuget_restore(project_path: 'osu.Game.Rulesets.Mania/osu.Game.Rulesets.Mania.csproj')
-
- souyuz(
- build_configuration: 'Release',
- solution_path: 'osu.sln',
- platform: "android",
- output_path: "osu.Android/bin/Release/",
- keystore_path: options[:keystore_path],
- keystore_alias: options[:keystore_alias],
- keystore_password: ENV["KEYSTORE_PASSWORD"]
- )
- end
-
- lane :update_version do |options|
-
- split = options[:build].split('.')
- split[1] = split[1].to_s.rjust(4, '0')
- android_build = split.join('')
-
- app_version(
- solution_path: 'osu.sln',
- version: options[:version],
- build: android_build,
- )
- end
-
-end
-
-platform :ios do
- desc 'Deploy to testflight'
- lane :beta do |options|
- update_version(options)
-
- provision(
- type: 'appstore'
- )
-
- build(
- build_configuration: 'Release',
- build_platform: 'iPhone'
- )
-
- client = HTTPClient.new
- changelog = client.get_content 'https://gist.githubusercontent.com/peppy/ab89c29dcc0dce95f39eb218e8fad197/raw'
- changelog.gsub!('$BUILD_ID', options[:build])
-
- pilot(
- wait_processing_interval: 900,
- changelog: changelog,
- groups: ['osu! supporters', 'public'],
- distribute_external: true,
- ipa: './osu.iOS/bin/iPhone/Release/osu.iOS.ipa'
- )
- end
-
- desc 'Compile the project'
- lane :build do
- nuget_restore(project_path: 'osu.iOS/osu.iOS.csproj')
- nuget_restore(project_path: 'osu.Game/osu.Game.csproj')
- nuget_restore(project_path: 'osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj')
- nuget_restore(project_path: 'osu.Game.Rulesets.Taiko/osu.Game.Rulesets.Taiko.csproj')
- nuget_restore(project_path: 'osu.Game.Rulesets.Catch/osu.Game.Rulesets.Catch.csproj')
- nuget_restore(project_path: 'osu.Game.Rulesets.Mania/osu.Game.Rulesets.Mania.csproj')
-
- souyuz(
- platform: "ios",
- plist_path: "osu.iOS/Info.plist"
- )
- end
-
- desc 'Install provisioning profiles using match'
- lane :provision do |options|
- if Helper.is_ci?
- options[:readonly] = true
- end
-
- match(options)
- end
-
- lane :update_version do |options|
- options[:plist_path] = 'osu.iOS/Info.plist'
- app_version(options)
- end
-
- lane :testflight_prune_dry do
- clean_testflight_testers(days_of_inactivity:30, dry_run: true)
- end
-
- lane :testflight_prune do
- clean_testflight_testers(days_of_inactivity: 30)
- end
-end
diff --git a/fastlane/Matchfile b/fastlane/Matchfile
deleted file mode 100644
index 40c974b09e..0000000000
--- a/fastlane/Matchfile
+++ /dev/null
@@ -1 +0,0 @@
-git_url('https://github.com/peppy/apple-certificates')
diff --git a/fastlane/Pluginfile b/fastlane/Pluginfile
deleted file mode 100644
index 9f4f47f213..0000000000
--- a/fastlane/Pluginfile
+++ /dev/null
@@ -1,7 +0,0 @@
-# Autogenerated by fastlane
-#
-# Ensure this file is checked in to source control!
-
-gem 'fastlane-plugin-clean_testflight_testers'
-gem 'fastlane-plugin-souyuz'
-gem 'fastlane-plugin-xamarin'
diff --git a/fastlane/README.md b/fastlane/README.md
deleted file mode 100644
index 9d5e11f7cb..0000000000
--- a/fastlane/README.md
+++ /dev/null
@@ -1,109 +0,0 @@
-fastlane documentation
-----
-
-# Installation
-
-Make sure you have the latest version of the Xcode command line tools installed:
-
-```sh
-xcode-select --install
-```
-
-For _fastlane_ installation instructions, see [Installing _fastlane_](https://docs.fastlane.tools/#installing-fastlane)
-
-# Available Actions
-
-## Android
-
-### android beta
-
-```sh
-[bundle exec] fastlane android beta
-```
-
-Deploy to play store
-
-### android build_github
-
-```sh
-[bundle exec] fastlane android build_github
-```
-
-Deploy to github release
-
-### android build
-
-```sh
-[bundle exec] fastlane android build
-```
-
-Compile the project
-
-### android update_version
-
-```sh
-[bundle exec] fastlane android update_version
-```
-
-
-
-----
-
-
-## iOS
-
-### ios beta
-
-```sh
-[bundle exec] fastlane ios beta
-```
-
-Deploy to testflight
-
-### ios build
-
-```sh
-[bundle exec] fastlane ios build
-```
-
-Compile the project
-
-### ios provision
-
-```sh
-[bundle exec] fastlane ios provision
-```
-
-Install provisioning profiles using match
-
-### ios update_version
-
-```sh
-[bundle exec] fastlane ios update_version
-```
-
-
-
-### ios testflight_prune_dry
-
-```sh
-[bundle exec] fastlane ios testflight_prune_dry
-```
-
-
-
-### ios testflight_prune
-
-```sh
-[bundle exec] fastlane ios testflight_prune
-```
-
-
-
-----
-
-This README.md is auto-generated and will be re-generated every time [_fastlane_](https://fastlane.tools) is run.
-
-More information about _fastlane_ can be found on [fastlane.tools](https://fastlane.tools).
-
-The documentation of _fastlane_ can be found on [docs.fastlane.tools](https://docs.fastlane.tools).
diff --git a/osu.Android.props b/osu.Android.props
index 8f4750e831..4bdf5d68c5 100644
--- a/osu.Android.props
+++ b/osu.Android.props
@@ -1,61 +1,20 @@
- 8.0
- bin\$(Configuration)
- 4
- 2.0
- false
- false
- Library
- 512
- Off
- True
- Xamarin.Android.Net.AndroidClientHandler
- v10.0
- false
- true
- armeabi-v7a;x86;arm64-v8a
- true
- cjk,mideast,other,rare,west
- SdkOnly
- prompt
-
-
- True
- portable
- False
- DEBUG;TRACE
- false
- true
- false
-
-
- false
- None
- True
- false
- False
+ 21.0
+ android-x86;android-arm;android-arm64
+ apk
+ CJK;Mideast;Rare;West;Other;
+ Xamarin.Android.Net.AndroidMessageHandler
+
+ true
true
-
- osu.licenseheader
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+ true
+
diff --git a/osu.Android/AndroidImportTask.cs b/osu.Android/AndroidImportTask.cs
new file mode 100644
index 0000000000..7273a6da5c
--- /dev/null
+++ b/osu.Android/AndroidImportTask.cs
@@ -0,0 +1,63 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System.IO;
+using System.Threading.Tasks;
+using Android.Content;
+using Android.Net;
+using Android.Provider;
+using osu.Game.Database;
+
+namespace osu.Android
+{
+ public class AndroidImportTask : ImportTask
+ {
+ private readonly ContentResolver contentResolver;
+
+ private readonly Uri uri;
+
+ private AndroidImportTask(Stream stream, string filename, ContentResolver contentResolver, Uri uri)
+ : base(stream, filename)
+ {
+ this.contentResolver = contentResolver;
+ this.uri = uri;
+ }
+
+ public override void DeleteFile()
+ {
+ contentResolver.Delete(uri, null, null);
+ }
+
+ public static async Task Create(ContentResolver contentResolver, Uri uri)
+ {
+ // there are more performant overloads of this method, but this one is the most backwards-compatible
+ // (dates back to API 1).
+
+ var cursor = contentResolver.Query(uri, null, null, null, null);
+
+ if (cursor == null)
+ return null;
+
+ if (!cursor.MoveToFirst())
+ return null;
+
+ int filenameColumn = cursor.GetColumnIndex(IOpenableColumns.DisplayName);
+ string filename = cursor.GetString(filenameColumn) ?? uri.Path ?? string.Empty;
+
+ // SharpCompress requires archive streams to be seekable, which the stream opened by
+ // OpenInputStream() seems to not necessarily be.
+ // copy to an arbitrary-access memory stream to be able to proceed with the import.
+ var copy = new MemoryStream();
+
+ using (var stream = contentResolver.OpenInputStream(uri))
+ {
+ if (stream == null)
+ return null;
+
+ await stream.CopyToAsync(copy).ConfigureAwait(false);
+ }
+
+ return new AndroidImportTask(copy, filename, contentResolver, uri);
+ }
+ }
+}
diff --git a/osu.Android/Properties/AndroidManifest.xml b/osu.Android/AndroidManifest.xml
similarity index 68%
rename from osu.Android/Properties/AndroidManifest.xml
rename to osu.Android/AndroidManifest.xml
index 165a64a424..bc2f49b1a9 100644
--- a/osu.Android/Properties/AndroidManifest.xml
+++ b/osu.Android/AndroidManifest.xml
@@ -1,5 +1,5 @@
-
-
+
+
\ No newline at end of file
diff --git a/osu.Android/OsuGameActivity.cs b/osu.Android/OsuGameActivity.cs
index be40db7508..f0a6e4733c 100644
--- a/osu.Android/OsuGameActivity.cs
+++ b/osu.Android/OsuGameActivity.cs
@@ -5,15 +5,14 @@
using System;
using System.Collections.Generic;
-using System.IO;
using System.Linq;
+using System.Reflection;
using System.Threading.Tasks;
using Android.App;
using Android.Content;
using Android.Content.PM;
using Android.Graphics;
using Android.OS;
-using Android.Provider;
using Android.Views;
using osu.Framework.Android;
using osu.Game.Database;
@@ -74,11 +73,23 @@ namespace osu.Android
Debug.Assert(Resources?.DisplayMetrics != null);
Point displaySize = new Point();
+#pragma warning disable 618 // GetSize is deprecated
WindowManager.DefaultDisplay.GetSize(displaySize);
+#pragma warning restore 618
float smallestWidthDp = Math.Min(displaySize.X, displaySize.Y) / Resources.DisplayMetrics.Density;
bool isTablet = smallestWidthDp >= 600f;
RequestedOrientation = DefaultOrientation = isTablet ? ScreenOrientation.FullUser : ScreenOrientation.SensorLandscape;
+
+ // Currently (SDK 6.0.200), BundleAssemblies is not runnable for net6-android.
+ // The assembly files are not available as files either after native AOT.
+ // Manually load them so that they can be loaded by RulesetStore.loadFromAppDomain.
+ // REMEMBER to fully uninstall previous version every time when investigating this!
+ // Don't forget osu.Game.Tests.Android too.
+ Assembly.Load("osu.Game.Rulesets.Osu");
+ Assembly.Load("osu.Game.Rulesets.Taiko");
+ Assembly.Load("osu.Game.Rulesets.Catch");
+ Assembly.Load("osu.Game.Rulesets.Mania");
}
protected override void OnNewIntent(Intent intent) => handleIntent(intent);
@@ -118,28 +129,14 @@ namespace osu.Android
await Task.WhenAll(uris.Select(async uri =>
{
- // there are more performant overloads of this method, but this one is the most backwards-compatible
- // (dates back to API 1).
- var cursor = ContentResolver?.Query(uri, null, null, null, null);
+ var task = await AndroidImportTask.Create(ContentResolver!, uri).ConfigureAwait(false);
- if (cursor == null)
- return;
-
- cursor.MoveToFirst();
-
- int filenameColumn = cursor.GetColumnIndex(OpenableColumns.DisplayName);
- string filename = cursor.GetString(filenameColumn);
-
- // SharpCompress requires archive streams to be seekable, which the stream opened by
- // OpenInputStream() seems to not necessarily be.
- // copy to an arbitrary-access memory stream to be able to proceed with the import.
- var copy = new MemoryStream();
- using (var stream = ContentResolver.OpenInputStream(uri))
- await stream.CopyToAsync(copy).ConfigureAwait(false);
-
- lock (tasks)
+ if (task != null)
{
- tasks.Add(new ImportTask(copy, filename));
+ lock (tasks)
+ {
+ tasks.Add(task);
+ }
}
})).ConfigureAwait(false);
diff --git a/osu.Android/OsuGameAndroid.cs b/osu.Android/OsuGameAndroid.cs
index 1c6f41a7ec..0227d2aec2 100644
--- a/osu.Android/OsuGameAndroid.cs
+++ b/osu.Android/OsuGameAndroid.cs
@@ -5,7 +5,7 @@
using System;
using Android.App;
-using Android.OS;
+using Microsoft.Maui.Devices;
using osu.Framework.Allocation;
using osu.Framework.Android.Input;
using osu.Framework.Input.Handlers;
@@ -14,7 +14,6 @@ using osu.Game;
using osu.Game.Overlays.Settings;
using osu.Game.Updater;
using osu.Game.Utils;
-using Xamarin.Essentials;
namespace osu.Android
{
@@ -48,7 +47,7 @@ namespace osu.Android
// https://stackoverflow.com/questions/52977079/android-sdk-28-versioncode-in-packageinfo-has-been-deprecated
string versionName = string.Empty;
- if (Build.VERSION.SdkInt >= BuildVersionCodes.P)
+ if (OperatingSystem.IsAndroidVersionAtLeast(28))
{
versionName = packageInfo.LongVersionCode.ToString();
// ensure we only read the trailing portion of long (the part we are interested in).
diff --git a/osu.Android/osu.Android.csproj b/osu.Android/osu.Android.csproj
index 004cc8c39c..1507bfaa29 100644
--- a/osu.Android/osu.Android.csproj
+++ b/osu.Android/osu.Android.csproj
@@ -1,73 +1,22 @@
-
-
+
- Debug
- AnyCPU
- 8.0.30703
- 2.0
- {D1D5F9A8-B40B-40E6-B02F-482D03346D3D}
- {EFBA0AD7-5A72-4C68-AF49-83D382785DCF};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
- {122416d6-6b49-4ee2-a1e8-b825f31c79fe}
+ net6.0-android
+ Exe
osu.Android
osu.Android
- Properties\AndroidManifest.xml
- armeabi-v7a;x86;arm64-v8a
- false
-
-
- cjk;mideast;other;rare;west
- d8
- r8
-
-
- None
- cjk;mideast;other;rare;west
- true
+ true
+
+ false
+ 0.0.0
+ 1
+ $(Version)
-
-
-
-
-
-
+
+
+
+
+
-
-
-
-
-
- {58f6c80c-1253-4a0e-a465-b8c85ebeadf3}
- osu.Game.Rulesets.Catch
-
-
- {48f4582b-7687-4621-9cbe-5c24197cb536}
- osu.Game.Rulesets.Mania
-
-
- {c92a607b-1fdd-4954-9f92-03ff547d9080}
- osu.Game.Rulesets.Osu
-
-
- {f167e17a-7de6-4af5-b920-a5112296c695}
- osu.Game.Rulesets.Taiko
-
-
- {2a66dd92-adb1-4994-89e2-c94e04acda0d}
- osu.Game
-
-
-
-
-
-
-
- 5.0.0
-
-
-
-
-
-
-
\ No newline at end of file
+
diff --git a/osu.Desktop/DiscordRichPresence.cs b/osu.Desktop/DiscordRichPresence.cs
index 2c4577f239..fe3e08537e 100644
--- a/osu.Desktop/DiscordRichPresence.cs
+++ b/osu.Desktop/DiscordRichPresence.cs
@@ -98,7 +98,7 @@ namespace osu.Desktop
if (status.Value is UserStatusOnline && activity.Value != null)
{
- presence.State = truncate(activity.Value.Status);
+ presence.State = truncate(activity.Value.GetStatus(privacyMode.Value == DiscordRichPresenceMode.Limited));
presence.Details = truncate(getDetails(activity.Value));
if (getBeatmap(activity.Value) is IBeatmapInfo beatmap && beatmap.OnlineID > 0)
@@ -169,7 +169,7 @@ namespace osu.Desktop
case UserActivity.InGame game:
return game.BeatmapInfo;
- case UserActivity.Editing edit:
+ case UserActivity.EditingBeatmap edit:
return edit.BeatmapInfo;
}
@@ -183,9 +183,12 @@ namespace osu.Desktop
case UserActivity.InGame game:
return game.BeatmapInfo.ToString() ?? string.Empty;
- case UserActivity.Editing edit:
+ case UserActivity.EditingBeatmap edit:
return edit.BeatmapInfo.ToString() ?? string.Empty;
+ case UserActivity.WatchingReplay watching:
+ return watching.BeatmapInfo.ToString();
+
case UserActivity.InLobby lobby:
return privacyMode.Value == DiscordRichPresenceMode.Limited ? string.Empty : lobby.Room.Name.Value;
}
diff --git a/osu.Desktop/OsuGameDesktop.cs b/osu.Desktop/OsuGameDesktop.cs
index 4c93c2286f..b3f6370ccb 100644
--- a/osu.Desktop/OsuGameDesktop.cs
+++ b/osu.Desktop/OsuGameDesktop.cs
@@ -29,6 +29,7 @@ namespace osu.Desktop
internal partial class OsuGameDesktop : OsuGame
{
private OsuSchemeLinkIPCChannel? osuSchemeLinkIPCChannel;
+ private ArchiveImportIPCChannel? archiveImportIPCChannel;
public OsuGameDesktop(string[]? args = null)
: base(args)
@@ -123,6 +124,7 @@ namespace osu.Desktop
LoadComponentAsync(new ElevatedPrivilegesChecker(), Add);
osuSchemeLinkIPCChannel = new OsuSchemeLinkIPCChannel(Host, this);
+ archiveImportIPCChannel = new ArchiveImportIPCChannel(Host, this);
}
public override void SetHost(GameHost host)
@@ -181,6 +183,7 @@ namespace osu.Desktop
{
base.Dispose(isDisposing);
osuSchemeLinkIPCChannel?.Dispose();
+ archiveImportIPCChannel?.Dispose();
}
private class SDL2BatteryInfo : BatteryInfo
diff --git a/osu.Desktop/osu.Desktop.csproj b/osu.Desktop/osu.Desktop.csproj
index 1f4544098b..f1b9c92429 100644
--- a/osu.Desktop/osu.Desktop.csproj
+++ b/osu.Desktop/osu.Desktop.csproj
@@ -26,8 +26,8 @@
-
-
+
+
diff --git a/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj b/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj
index f47b069373..4719d54138 100644
--- a/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj
+++ b/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj
@@ -7,9 +7,9 @@
-
+
-
+
diff --git a/osu.Game.Rulesets.Catch.Tests.Android/Properties/AndroidManifest.xml b/osu.Game.Rulesets.Catch.Tests.Android/AndroidManifest.xml
similarity index 96%
rename from osu.Game.Rulesets.Catch.Tests.Android/Properties/AndroidManifest.xml
rename to osu.Game.Rulesets.Catch.Tests.Android/AndroidManifest.xml
index f8c3fcd894..bf7c0bfeca 100644
--- a/osu.Game.Rulesets.Catch.Tests.Android/Properties/AndroidManifest.xml
+++ b/osu.Game.Rulesets.Catch.Tests.Android/AndroidManifest.xml
@@ -1,6 +1,6 @@
-
+
\ No newline at end of file
diff --git a/osu.Game.Rulesets.Catch.Tests.Android/osu.Game.Rulesets.Catch.Tests.Android.csproj b/osu.Game.Rulesets.Catch.Tests.Android/osu.Game.Rulesets.Catch.Tests.Android.csproj
index 94fdba4a3e..4ee3219442 100644
--- a/osu.Game.Rulesets.Catch.Tests.Android/osu.Game.Rulesets.Catch.Tests.Android.csproj
+++ b/osu.Game.Rulesets.Catch.Tests.Android/osu.Game.Rulesets.Catch.Tests.Android.csproj
@@ -1,49 +1,24 @@
-
-
+
- Debug
- AnyCPU
- 8.0.30703
- 2.0
- {C5379ECB-3A94-4D2F-AC3B-2615AC23EB0D}
- {EFBA0AD7-5A72-4C68-AF49-83D382785DCF};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
- {122416d6-6b49-4ee2-a1e8-b825f31c79fe}
+ net6.0-android
+ Exe
osu.Game.Rulesets.Catch.Tests
osu.Game.Rulesets.Catch.Tests.Android
- Properties\AndroidManifest.xml
- armeabi-v7a;x86;arm64-v8a
-
-
- None
- cjk;mideast;other;rare;west
- true
-
-
-
-
-
-
-
+
%(RecursiveDir)%(Filename)%(Extension)
+
+
+ %(RecursiveDir)%(Filename)%(Extension)
+ Android\%(RecursiveDir)%(Filename)%(Extension)
+
-
- {58f6c80c-1253-4a0e-a465-b8c85ebeadf3}
- osu.Game.Rulesets.Catch
-
-
- {2A66DD92-ADB1-4994-89E2-C94E04ACDA0D}
- osu.Game
-
+
+
-
-
- 5.0.0
-
-
-
\ No newline at end of file
diff --git a/osu.Game.Rulesets.Catch.Tests.iOS/Application.cs b/osu.Game.Rulesets.Catch.Tests.iOS/Application.cs
index 71d943ece1..1fcb0aa427 100644
--- a/osu.Game.Rulesets.Catch.Tests.iOS/Application.cs
+++ b/osu.Game.Rulesets.Catch.Tests.iOS/Application.cs
@@ -3,7 +3,6 @@
#nullable disable
-using osu.Framework.iOS;
using UIKit;
namespace osu.Game.Rulesets.Catch.Tests.iOS
@@ -12,7 +11,7 @@ namespace osu.Game.Rulesets.Catch.Tests.iOS
{
public static void Main(string[] args)
{
- UIApplication.Main(args, typeof(GameUIApplication), typeof(AppDelegate));
+ UIApplication.Main(args, null, typeof(AppDelegate));
}
}
}
diff --git a/osu.Game.Rulesets.Catch.Tests.iOS/Info.plist b/osu.Game.Rulesets.Catch.Tests.iOS/Info.plist
index 16a2b99997..5ace6c07f5 100644
--- a/osu.Game.Rulesets.Catch.Tests.iOS/Info.plist
+++ b/osu.Game.Rulesets.Catch.Tests.iOS/Info.plist
@@ -13,7 +13,7 @@
LSRequiresIPhoneOS
MinimumOSVersion
- 10.0
+ 13.4
UIDeviceFamily
1
diff --git a/osu.Game.Rulesets.Catch.Tests.iOS/osu.Game.Rulesets.Catch.Tests.iOS.csproj b/osu.Game.Rulesets.Catch.Tests.iOS/osu.Game.Rulesets.Catch.Tests.iOS.csproj
index be6044bbd0..acf12bb0ac 100644
--- a/osu.Game.Rulesets.Catch.Tests.iOS/osu.Game.Rulesets.Catch.Tests.iOS.csproj
+++ b/osu.Game.Rulesets.Catch.Tests.iOS/osu.Game.Rulesets.Catch.Tests.iOS.csproj
@@ -1,35 +1,19 @@
-
-
+
- Debug
- iPhoneSimulator
- {4004C7B7-1A62-43F1-9DF2-52450FA67E70}
Exe
+ net6.0-ios
+ 13.4
osu.Game.Rulesets.Catch.Tests
osu.Game.Rulesets.Catch.Tests.iOS
-
-
-
- Linker.xml
-
-
-
%(RecursiveDir)%(Filename)%(Extension)
-
- {2A66DD92-ADB1-4994-89E2-C94E04ACDA0D}
- osu.Game
-
-
- {58F6C80C-1253-4A0E-A465-B8C85EBEADF3}
- osu.Game.Rulesets.Catch
-
+
+
-
-
\ No newline at end of file
+
diff --git a/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModNoScope.cs b/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModNoScope.cs
index 50b928d509..c48bf7adc9 100644
--- a/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModNoScope.cs
+++ b/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModNoScope.cs
@@ -18,6 +18,36 @@ namespace osu.Game.Rulesets.Catch.Tests.Mods
{
protected override Ruleset CreatePlayerRuleset() => new CatchRuleset();
+ [Test]
+ public void TestAlwaysHidden()
+ {
+ CreateModTest(new ModTestData
+ {
+ Mod = new CatchModNoScope
+ {
+ HiddenComboCount = { Value = 0 },
+ },
+ Autoplay = true,
+ PassCondition = () => Player.ScoreProcessor.Combo.Value == 2,
+ Beatmap = new Beatmap
+ {
+ HitObjects = new List
+ {
+ new Fruit
+ {
+ X = CatchPlayfield.CENTER_X * 0.5f,
+ StartTime = 1000,
+ },
+ new Fruit
+ {
+ X = CatchPlayfield.CENTER_X * 1.5f,
+ StartTime = 2000,
+ }
+ }
+ }
+ });
+ }
+
[Test]
public void TestVisibleDuringBreak()
{
diff --git a/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModRelax.cs b/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModRelax.cs
index 8472b995e8..5835ccaf78 100644
--- a/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModRelax.cs
+++ b/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModRelax.cs
@@ -4,8 +4,10 @@
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
+using osu.Framework.Graphics.Containers;
using osu.Framework.Testing;
using osu.Game.Beatmaps;
+using osu.Game.Graphics.Cursor;
using osu.Game.Rulesets.Catch.Mods;
using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Catch.UI;
@@ -55,6 +57,21 @@ namespace osu.Game.Rulesets.Catch.Tests.Mods
}
});
+ [Test]
+ public void TestGameCursorHidden()
+ {
+ CreateModTest(new ModTestData
+ {
+ Mod = new CatchModRelax(),
+ Autoplay = false,
+ PassCondition = () =>
+ {
+ InputManager.MoveMouseTo(this.ChildrenOfType().Single());
+ return this.ChildrenOfType().Single().State.Value == Visibility.Hidden;
+ }
+ });
+ }
+
private bool passCondition()
{
var playfield = this.ChildrenOfType().Single();
diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatchPlayerLegacySkin.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchPlayerLegacySkin.cs
index 8f3f39be9b..4c1ba33aa2 100644
--- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatchPlayerLegacySkin.cs
+++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchPlayerLegacySkin.cs
@@ -26,7 +26,7 @@ namespace osu.Game.Rulesets.Catch.Tests
if (withModifiedSkin)
{
AddStep("change component scale", () => Player.ChildrenOfType().First().Scale = new Vector2(2f));
- AddStep("update target", () => Player.ChildrenOfType().ForEach(LegacySkin.UpdateDrawableTarget));
+ AddStep("update target", () => Player.ChildrenOfType().ForEach(LegacySkin.UpdateDrawableTarget));
AddStep("exit player", () => Player.Exit());
CreateTest();
}
diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs
index e8231b07ad..f60ae29f77 100644
--- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs
+++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs
@@ -60,26 +60,24 @@ namespace osu.Game.Rulesets.Catch.Tests
[Test]
public void TestCatcherHyperStateReverted()
{
- DrawableCatchHitObject drawableObject1 = null;
- DrawableCatchHitObject drawableObject2 = null;
JudgementResult result1 = null;
JudgementResult result2 = null;
AddStep("catch hyper fruit", () =>
{
- attemptCatch(new Fruit { HyperDashTarget = new Fruit { X = 100 } }, out drawableObject1, out result1);
+ result1 = attemptCatch(new Fruit { HyperDashTarget = new Fruit { X = 100 } });
});
AddStep("catch normal fruit", () =>
{
- attemptCatch(new Fruit(), out drawableObject2, out result2);
+ result2 = attemptCatch(new Fruit());
});
AddStep("revert second result", () =>
{
- catcher.OnRevertResult(drawableObject2, result2);
+ catcher.OnRevertResult(result2);
});
checkHyperDash(true);
AddStep("revert first result", () =>
{
- catcher.OnRevertResult(drawableObject1, result1);
+ catcher.OnRevertResult(result1);
});
checkHyperDash(false);
}
@@ -87,16 +85,15 @@ namespace osu.Game.Rulesets.Catch.Tests
[Test]
public void TestCatcherAnimationStateReverted()
{
- DrawableCatchHitObject drawableObject = null;
JudgementResult result = null;
AddStep("catch kiai fruit", () =>
{
- attemptCatch(new TestKiaiFruit(), out drawableObject, out result);
+ result = attemptCatch(new TestKiaiFruit());
});
checkState(CatcherAnimationState.Kiai);
AddStep("revert result", () =>
{
- catcher.OnRevertResult(drawableObject, result);
+ catcher.OnRevertResult(result);
});
checkState(CatcherAnimationState.Idle);
}
@@ -268,23 +265,19 @@ namespace osu.Game.Rulesets.Catch.Tests
private void checkHyperDash(bool state) => AddAssert($"catcher is {(state ? "" : "not ")}hyper dashing", () => catcher.HyperDashing == state);
- private void attemptCatch(CatchHitObject hitObject)
- {
- attemptCatch(() => hitObject, 1);
- }
-
private void attemptCatch(Func hitObject, int count)
{
for (int i = 0; i < count; i++)
- attemptCatch(hitObject(), out _, out _);
+ attemptCatch(hitObject());
}
- private void attemptCatch(CatchHitObject hitObject, out DrawableCatchHitObject drawableObject, out JudgementResult result)
+ private JudgementResult attemptCatch(CatchHitObject hitObject)
{
hitObject.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
- drawableObject = createDrawableObject(hitObject);
- result = createResult(hitObject);
+ var drawableObject = createDrawableObject(hitObject);
+ var result = createResult(hitObject);
applyResult(drawableObject, result);
+ return result;
}
private void applyResult(DrawableCatchHitObject drawableObject, JudgementResult result)
diff --git a/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj b/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj
index 5a2e8e0bf0..01922b2a96 100644
--- a/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj
+++ b/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj
@@ -1,9 +1,9 @@
-
+
-
+
WinExe
diff --git a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmap.cs b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmap.cs
index ac39b91f00..f009c10a9c 100644
--- a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmap.cs
+++ b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmap.cs
@@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using System.Collections.Generic;
using System.Linq;
using osu.Game.Beatmaps;
diff --git a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs
index 9f5d007114..7774a7da09 100644
--- a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs
+++ b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs
@@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Catch.Objects;
using System.Collections.Generic;
diff --git a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs
index 835f7c2d27..ab61b14ac4 100644
--- a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs
+++ b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs
@@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using System;
using System.Collections.Generic;
using System.Linq;
diff --git a/osu.Game.Rulesets.Catch/CatchRuleset.cs b/osu.Game.Rulesets.Catch/CatchRuleset.cs
index e0f7820262..8a0b8250d5 100644
--- a/osu.Game.Rulesets.Catch/CatchRuleset.cs
+++ b/osu.Game.Rulesets.Catch/CatchRuleset.cs
@@ -113,6 +113,7 @@ namespace osu.Game.Rulesets.Catch
new MultiMod(new CatchModDoubleTime(), new CatchModNightcore()),
new CatchModHidden(),
new CatchModFlashlight(),
+ new ModAccuracyChallenge(),
};
case ModType.Conversion:
diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs
index f37479f84a..42cfde268e 100644
--- a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs
+++ b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs
@@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using System;
using System.Collections.Generic;
using System.Linq;
@@ -51,7 +49,7 @@ namespace osu.Game.Rulesets.Catch.Difficulty
protected override IEnumerable CreateDifficultyHitObjects(IBeatmap beatmap, double clockRate)
{
- CatchHitObject lastObject = null;
+ CatchHitObject? lastObject = null;
List objects = new List();
diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceAttributes.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceAttributes.cs
index ccdfd30200..1335fc2d23 100644
--- a/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceAttributes.cs
+++ b/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceAttributes.cs
@@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using osu.Game.Rulesets.Difficulty;
namespace osu.Game.Rulesets.Catch.Difficulty
diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs
index 2a07b8019e..b30b85be2d 100644
--- a/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs
+++ b/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs
@@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using System;
using System.Collections.Generic;
using System.Linq;
diff --git a/osu.Game.Rulesets.Catch/Difficulty/Preprocessing/CatchDifficultyHitObject.cs b/osu.Game.Rulesets.Catch/Difficulty/Preprocessing/CatchDifficultyHitObject.cs
index c44480776f..3bcfce3a56 100644
--- a/osu.Game.Rulesets.Catch/Difficulty/Preprocessing/CatchDifficultyHitObject.cs
+++ b/osu.Game.Rulesets.Catch/Difficulty/Preprocessing/CatchDifficultyHitObject.cs
@@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using System;
using System.Collections.Generic;
using osu.Game.Rulesets.Catch.Objects;
diff --git a/osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs b/osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs
index 827c28f7de..cfb3fe40be 100644
--- a/osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs
+++ b/osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs
@@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using System;
using osu.Game.Rulesets.Catch.Difficulty.Preprocessing;
using osu.Game.Rulesets.Difficulty.Preprocessing;
diff --git a/osu.Game.Rulesets.Catch/Edit/BananaShowerCompositionTool.cs b/osu.Game.Rulesets.Catch/Edit/BananaShowerCompositionTool.cs
index e64a51f03a..31075db7d1 100644
--- a/osu.Game.Rulesets.Catch/Edit/BananaShowerCompositionTool.cs
+++ b/osu.Game.Rulesets.Catch/Edit/BananaShowerCompositionTool.cs
@@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using osu.Framework.Graphics;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Catch.Edit.Blueprints;
diff --git a/osu.Game.Rulesets.Catch/Edit/Blueprints/BananaShowerPlacementBlueprint.cs b/osu.Game.Rulesets.Catch/Edit/Blueprints/BananaShowerPlacementBlueprint.cs
index 166fa44303..5f22ef5c12 100644
--- a/osu.Game.Rulesets.Catch/Edit/Blueprints/BananaShowerPlacementBlueprint.cs
+++ b/osu.Game.Rulesets.Catch/Edit/Blueprints/BananaShowerPlacementBlueprint.cs
@@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using System;
using osu.Framework.Input.Events;
using osu.Game.Rulesets.Catch.Edit.Blueprints.Components;
diff --git a/osu.Game.Rulesets.Catch/Edit/Blueprints/BananaShowerSelectionBlueprint.cs b/osu.Game.Rulesets.Catch/Edit/Blueprints/BananaShowerSelectionBlueprint.cs
index 2c545e8f0e..f6dd67889e 100644
--- a/osu.Game.Rulesets.Catch/Edit/Blueprints/BananaShowerSelectionBlueprint.cs
+++ b/osu.Game.Rulesets.Catch/Edit/Blueprints/BananaShowerSelectionBlueprint.cs
@@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using osu.Game.Rulesets.Catch.Objects;
namespace osu.Game.Rulesets.Catch.Edit.Blueprints
diff --git a/osu.Game.Rulesets.Catch/Edit/Blueprints/CatchPlacementBlueprint.cs b/osu.Game.Rulesets.Catch/Edit/Blueprints/CatchPlacementBlueprint.cs
index 94373147d2..d2d605a6fe 100644
--- a/osu.Game.Rulesets.Catch/Edit/Blueprints/CatchPlacementBlueprint.cs
+++ b/osu.Game.Rulesets.Catch/Edit/Blueprints/CatchPlacementBlueprint.cs
@@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using osu.Framework.Allocation;
using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Edit;
@@ -20,7 +18,7 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints
protected ScrollingHitObjectContainer HitObjectContainer => (ScrollingHitObjectContainer)playfield.HitObjectContainer;
[Resolved]
- private Playfield playfield { get; set; }
+ private Playfield playfield { get; set; } = null!;
public CatchPlacementBlueprint()
: base(new THitObject())
diff --git a/osu.Game.Rulesets.Catch/Edit/Blueprints/CatchSelectionBlueprint.cs b/osu.Game.Rulesets.Catch/Edit/Blueprints/CatchSelectionBlueprint.cs
index 87c33a9cb8..8220fb88b4 100644
--- a/osu.Game.Rulesets.Catch/Edit/Blueprints/CatchSelectionBlueprint.cs
+++ b/osu.Game.Rulesets.Catch/Edit/Blueprints/CatchSelectionBlueprint.cs
@@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using osu.Framework.Allocation;
using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Edit;
@@ -31,7 +29,7 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints
protected ScrollingHitObjectContainer HitObjectContainer => (ScrollingHitObjectContainer)playfield.HitObjectContainer;
[Resolved]
- private Playfield playfield { get; set; }
+ private Playfield playfield { get; set; } = null!;
protected CatchSelectionBlueprint(THitObject hitObject)
: base(hitObject)
diff --git a/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/EditablePath.cs b/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/EditablePath.cs
index 006ea6e9cf..74d6565600 100644
--- a/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/EditablePath.cs
+++ b/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/EditablePath.cs
@@ -1,13 +1,10 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
-using JetBrains.Annotations;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
@@ -42,9 +39,8 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components
private readonly List previousVertexStates = new List();
- [Resolved(CanBeNull = true)]
- [CanBeNull]
- private IBeatSnapProvider beatSnapProvider { get; set; }
+ [Resolved]
+ private IBeatSnapProvider? beatSnapProvider { get; set; }
protected EditablePath(Func positionToTime)
{
diff --git a/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/FruitOutline.cs b/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/FruitOutline.cs
index dc2b038e01..c7805544ea 100644
--- a/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/FruitOutline.cs
+++ b/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/FruitOutline.cs
@@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
diff --git a/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/NestedOutlineContainer.cs b/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/NestedOutlineContainer.cs
index ac0c850bca..c1f46539fa 100644
--- a/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/NestedOutlineContainer.cs
+++ b/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/NestedOutlineContainer.cs
@@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Graphics;
diff --git a/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/PlacementEditablePath.cs b/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/PlacementEditablePath.cs
index ba2f5ed0eb..3a7d6d87f2 100644
--- a/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/PlacementEditablePath.cs
+++ b/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/PlacementEditablePath.cs
@@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using System;
using osu.Game.Rulesets.Catch.Objects;
using osuTK;
diff --git a/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/ScrollingPath.cs b/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/ScrollingPath.cs
index 6deb5a174f..a22abcb76d 100644
--- a/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/ScrollingPath.cs
+++ b/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/ScrollingPath.cs
@@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Graphics;
diff --git a/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/SelectionEditablePath.cs b/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/SelectionEditablePath.cs
index 3a44f7ac8a..c7a26ca15a 100644
--- a/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/SelectionEditablePath.cs
+++ b/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/SelectionEditablePath.cs
@@ -1,12 +1,9 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using System;
using System.Collections.Generic;
using System.Linq;
-using JetBrains.Annotations;
using osu.Framework.Allocation;
using osu.Framework.Graphics.Cursor;
using osu.Framework.Graphics.UserInterface;
@@ -25,9 +22,8 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components
// To handle when the editor is scrolled while dragging.
private Vector2 dragStartPosition;
- [Resolved(CanBeNull = true)]
- [CanBeNull]
- private IEditorChangeHandler changeHandler { get; set; }
+ [Resolved]
+ private IEditorChangeHandler? changeHandler { get; set; }
public SelectionEditablePath(Func positionToTime)
: base(positionToTime)
diff --git a/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/TimeSpanOutline.cs b/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/TimeSpanOutline.cs
index a1d5d7ae3e..9d450cd355 100644
--- a/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/TimeSpanOutline.cs
+++ b/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/TimeSpanOutline.cs
@@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using System;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
diff --git a/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/VertexPiece.cs b/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/VertexPiece.cs
index 49570d3735..07d7c72698 100644
--- a/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/VertexPiece.cs
+++ b/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/VertexPiece.cs
@@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using osu.Framework.Allocation;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
@@ -15,7 +13,7 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components
public partial class VertexPiece : Circle
{
[Resolved]
- private OsuColour osuColour { get; set; }
+ private OsuColour osuColour { get; set; } = null!;
public VertexPiece()
{
diff --git a/osu.Game.Rulesets.Catch/Edit/Blueprints/FruitPlacementBlueprint.cs b/osu.Game.Rulesets.Catch/Edit/Blueprints/FruitPlacementBlueprint.cs
index af75023e68..72592891fb 100644
--- a/osu.Game.Rulesets.Catch/Edit/Blueprints/FruitPlacementBlueprint.cs
+++ b/osu.Game.Rulesets.Catch/Edit/Blueprints/FruitPlacementBlueprint.cs
@@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using osu.Framework.Input.Events;
using osu.Game.Rulesets.Catch.Edit.Blueprints.Components;
using osu.Game.Rulesets.Catch.Objects;
diff --git a/osu.Game.Rulesets.Catch/Edit/Blueprints/FruitSelectionBlueprint.cs b/osu.Game.Rulesets.Catch/Edit/Blueprints/FruitSelectionBlueprint.cs
index 319b1b5e20..2737b283ef 100644
--- a/osu.Game.Rulesets.Catch/Edit/Blueprints/FruitSelectionBlueprint.cs
+++ b/osu.Game.Rulesets.Catch/Edit/Blueprints/FruitSelectionBlueprint.cs
@@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using osu.Game.Rulesets.Catch.Edit.Blueprints.Components;
using osu.Game.Rulesets.Catch.Objects;
diff --git a/osu.Game.Rulesets.Catch/Edit/Blueprints/JuiceStreamPlacementBlueprint.cs b/osu.Game.Rulesets.Catch/Edit/Blueprints/JuiceStreamPlacementBlueprint.cs
index 0e2ee334ff..03ec674abb 100644
--- a/osu.Game.Rulesets.Catch/Edit/Blueprints/JuiceStreamPlacementBlueprint.cs
+++ b/osu.Game.Rulesets.Catch/Edit/Blueprints/JuiceStreamPlacementBlueprint.cs
@@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using osu.Framework.Graphics;
using osu.Framework.Input;
using osu.Framework.Input.Events;
@@ -24,7 +22,7 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints
private int lastEditablePathId = -1;
- private InputManager inputManager;
+ private InputManager inputManager = null!;
public JuiceStreamPlacementBlueprint()
{
diff --git a/osu.Game.Rulesets.Catch/Edit/Blueprints/JuiceStreamSelectionBlueprint.cs b/osu.Game.Rulesets.Catch/Edit/Blueprints/JuiceStreamSelectionBlueprint.cs
index 99ec5e2b0c..49d778ad08 100644
--- a/osu.Game.Rulesets.Catch/Edit/Blueprints/JuiceStreamSelectionBlueprint.cs
+++ b/osu.Game.Rulesets.Catch/Edit/Blueprints/JuiceStreamSelectionBlueprint.cs
@@ -1,11 +1,8 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using System.Collections.Generic;
using System.Linq;
-using JetBrains.Annotations;
using osu.Framework.Allocation;
using osu.Framework.Caching;
using osu.Framework.Graphics;
@@ -53,9 +50,8 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints
private Vector2 rightMouseDownPosition;
- [Resolved(CanBeNull = true)]
- [CanBeNull]
- private EditorBeatmap editorBeatmap { get; set; }
+ [Resolved]
+ private EditorBeatmap? editorBeatmap { get; set; }
public JuiceStreamSelectionBlueprint(JuiceStream hitObject)
: base(hitObject)
diff --git a/osu.Game.Rulesets.Catch/Edit/CatchBeatmapVerifier.cs b/osu.Game.Rulesets.Catch/Edit/CatchBeatmapVerifier.cs
index 6570a19a92..c7a41a4e22 100644
--- a/osu.Game.Rulesets.Catch/Edit/CatchBeatmapVerifier.cs
+++ b/osu.Game.Rulesets.Catch/Edit/CatchBeatmapVerifier.cs
@@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using System.Collections.Generic;
using System.Linq;
using osu.Game.Rulesets.Catch.Edit.Checks;
diff --git a/osu.Game.Rulesets.Catch/Edit/CatchBlueprintContainer.cs b/osu.Game.Rulesets.Catch/Edit/CatchBlueprintContainer.cs
index 9408a9f95c..3979d30616 100644
--- a/osu.Game.Rulesets.Catch/Edit/CatchBlueprintContainer.cs
+++ b/osu.Game.Rulesets.Catch/Edit/CatchBlueprintContainer.cs
@@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using osu.Game.Rulesets.Catch.Edit.Blueprints;
using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Edit;
@@ -20,7 +18,7 @@ namespace osu.Game.Rulesets.Catch.Edit
protected override SelectionHandler CreateSelectionHandler() => new CatchSelectionHandler();
- public override HitObjectSelectionBlueprint CreateHitObjectBlueprintFor(HitObject hitObject)
+ public override HitObjectSelectionBlueprint? CreateHitObjectBlueprintFor(HitObject hitObject)
{
switch (hitObject)
{
diff --git a/osu.Game.Rulesets.Catch/Edit/CatchDistanceSnapGrid.cs b/osu.Game.Rulesets.Catch/Edit/CatchDistanceSnapGrid.cs
index e5cdcf706c..cf6ddc66ed 100644
--- a/osu.Game.Rulesets.Catch/Edit/CatchDistanceSnapGrid.cs
+++ b/osu.Game.Rulesets.Catch/Edit/CatchDistanceSnapGrid.cs
@@ -1,12 +1,9 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using System;
using System.Collections.Generic;
using System.Linq;
-using JetBrains.Annotations;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
@@ -39,7 +36,7 @@ namespace osu.Game.Rulesets.Catch.Edit
private readonly List verticalLineVertices = new List();
[Resolved]
- private Playfield playfield { get; set; }
+ private Playfield playfield { get; set; } = null!;
private ScrollingHitObjectContainer hitObjectContainer => (ScrollingHitObjectContainer)playfield.HitObjectContainer;
@@ -106,8 +103,7 @@ namespace osu.Game.Rulesets.Catch.Edit
}
}
- [CanBeNull]
- public SnapResult GetSnappedPosition(Vector2 screenSpacePosition)
+ public SnapResult? GetSnappedPosition(Vector2 screenSpacePosition)
{
double time = hitObjectContainer.TimeAtScreenSpacePosition(screenSpacePosition);
@@ -121,9 +117,7 @@ namespace osu.Game.Rulesets.Catch.Edit
return new SnapResult(originPosition, StartTime);
}
- return enumerateSnappingCandidates(time)
- .OrderBy(pos => Vector2.DistanceSquared(screenSpacePosition, pos.ScreenSpacePosition))
- .FirstOrDefault();
+ return enumerateSnappingCandidates(time).MinBy(pos => Vector2.DistanceSquared(screenSpacePosition, pos.ScreenSpacePosition));
}
private IEnumerable enumerateSnappingCandidates(double time)
diff --git a/osu.Game.Rulesets.Catch/Edit/CatchEditorPlayfield.cs b/osu.Game.Rulesets.Catch/Edit/CatchEditorPlayfield.cs
index bca89c6024..c9481c2757 100644
--- a/osu.Game.Rulesets.Catch/Edit/CatchEditorPlayfield.cs
+++ b/osu.Game.Rulesets.Catch/Edit/CatchEditorPlayfield.cs
@@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Catch.UI;
diff --git a/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs b/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs
index bbdffbf39c..ea5f54a775 100644
--- a/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs
+++ b/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs
@@ -1,12 +1,9 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using System;
using System.Collections.Generic;
using System.Linq;
-using JetBrains.Annotations;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions.EnumExtensions;
@@ -32,9 +29,9 @@ namespace osu.Game.Rulesets.Catch.Edit
{
private const float distance_snap_radius = 50;
- private CatchDistanceSnapGrid distanceSnapGrid;
+ private CatchDistanceSnapGrid distanceSnapGrid = null!;
- private InputManager inputManager;
+ private InputManager inputManager = null!;
private readonly BindableDouble timeRangeMultiplier = new BindableDouble(1)
{
@@ -117,7 +114,7 @@ namespace osu.Game.Rulesets.Catch.Edit
return base.OnPressed(e);
}
- protected override DrawableRuleset CreateDrawableRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList mods = null) =>
+ protected override DrawableRuleset CreateDrawableRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList? mods = null) =>
new DrawableCatchEditorRuleset(ruleset, beatmap, mods)
{
TimeRangeMultiplier = { BindTarget = timeRangeMultiplier, }
@@ -150,8 +147,7 @@ namespace osu.Game.Rulesets.Catch.Edit
protected override ComposeBlueprintContainer CreateBlueprintContainer() => new CatchBlueprintContainer(this);
- [CanBeNull]
- private PalpableCatchHitObject getLastSnappableHitObject(double time)
+ private PalpableCatchHitObject? getLastSnappableHitObject(double time)
{
var hitObject = EditorBeatmap.HitObjects.OfType().LastOrDefault(h => h.GetEndTime() < time && !(h is BananaShower));
@@ -168,8 +164,7 @@ namespace osu.Game.Rulesets.Catch.Edit
}
}
- [CanBeNull]
- private PalpableCatchHitObject getDistanceSnapGridSourceHitObject()
+ private PalpableCatchHitObject? getDistanceSnapGridSourceHitObject()
{
switch (BlueprintContainer.CurrentTool)
{
@@ -188,7 +183,8 @@ namespace osu.Game.Rulesets.Catch.Edit
if (EditorBeatmap.PlacementObject.Value is JuiceStream)
{
// Juice stream path is not subject to snapping.
- return null;
+ if (BlueprintContainer.CurrentPlacement.PlacementActive is PlacementBlueprint.PlacementState.Active)
+ return null;
}
double timeAtCursor = ((CatchPlayfield)Playfield).TimeAtScreenSpacePosition(inputManager.CurrentState.Mouse.Position);
diff --git a/osu.Game.Rulesets.Catch/Edit/CatchHitObjectUtils.cs b/osu.Game.Rulesets.Catch/Edit/CatchHitObjectUtils.cs
index 889d3909bd..bd33080109 100644
--- a/osu.Game.Rulesets.Catch/Edit/CatchHitObjectUtils.cs
+++ b/osu.Game.Rulesets.Catch/Edit/CatchHitObjectUtils.cs
@@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using System.Collections.Generic;
using System.Linq;
using osu.Game.Rulesets.Catch.Objects;
diff --git a/osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs b/osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs
index d9d7047920..418351e2f3 100644
--- a/osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs
+++ b/osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs
@@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using System;
using System.Collections.Generic;
using System.Linq;
@@ -23,7 +21,7 @@ namespace osu.Game.Rulesets.Catch.Edit
protected ScrollingHitObjectContainer HitObjectContainer => (ScrollingHitObjectContainer)playfield.HitObjectContainer;
[Resolved]
- private Playfield playfield { get; set; }
+ private Playfield playfield { get; set; } = null!;
public override bool HandleMovement(MoveSelectionEvent moveEvent)
{
diff --git a/osu.Game.Rulesets.Catch/Edit/DrawableCatchEditorRuleset.cs b/osu.Game.Rulesets.Catch/Edit/DrawableCatchEditorRuleset.cs
index 0a50ad1df4..7ad2106ab9 100644
--- a/osu.Game.Rulesets.Catch/Edit/DrawableCatchEditorRuleset.cs
+++ b/osu.Game.Rulesets.Catch/Edit/DrawableCatchEditorRuleset.cs
@@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using System.Collections.Generic;
using osu.Framework.Bindables;
using osu.Game.Beatmaps;
@@ -16,7 +14,7 @@ namespace osu.Game.Rulesets.Catch.Edit
{
public readonly BindableDouble TimeRangeMultiplier = new BindableDouble(1);
- public DrawableCatchEditorRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList mods = null)
+ public DrawableCatchEditorRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList? mods = null)
: base(ruleset, beatmap, mods)
{
}
diff --git a/osu.Game.Rulesets.Catch/Edit/FruitCompositionTool.cs b/osu.Game.Rulesets.Catch/Edit/FruitCompositionTool.cs
index 5c13692b51..f776fe39c1 100644
--- a/osu.Game.Rulesets.Catch/Edit/FruitCompositionTool.cs
+++ b/osu.Game.Rulesets.Catch/Edit/FruitCompositionTool.cs
@@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using osu.Framework.Graphics;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Catch.Edit.Blueprints;
diff --git a/osu.Game.Rulesets.Catch/Edit/JuiceStreamCompositionTool.cs b/osu.Game.Rulesets.Catch/Edit/JuiceStreamCompositionTool.cs
index 85cf89f700..cb66e2952e 100644
--- a/osu.Game.Rulesets.Catch/Edit/JuiceStreamCompositionTool.cs
+++ b/osu.Game.Rulesets.Catch/Edit/JuiceStreamCompositionTool.cs
@@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using osu.Framework.Graphics;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Catch.Edit.Blueprints;
diff --git a/osu.Game.Rulesets.Catch/Judgements/CatchBananaJudgement.cs b/osu.Game.Rulesets.Catch/Judgements/CatchBananaJudgement.cs
index 15f6e4a64d..b919102215 100644
--- a/osu.Game.Rulesets.Catch/Judgements/CatchBananaJudgement.cs
+++ b/osu.Game.Rulesets.Catch/Judgements/CatchBananaJudgement.cs
@@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Scoring;
diff --git a/osu.Game.Rulesets.Catch/Judgements/CatchDropletJudgement.cs b/osu.Game.Rulesets.Catch/Judgements/CatchDropletJudgement.cs
index 90aa6f41a1..8fd7b93e4c 100644
--- a/osu.Game.Rulesets.Catch/Judgements/CatchDropletJudgement.cs
+++ b/osu.Game.Rulesets.Catch/Judgements/CatchDropletJudgement.cs
@@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Catch.Judgements
diff --git a/osu.Game.Rulesets.Catch/Judgements/CatchJudgement.cs b/osu.Game.Rulesets.Catch/Judgements/CatchJudgement.cs
index e5d6429660..ccafe0abc4 100644
--- a/osu.Game.Rulesets.Catch/Judgements/CatchJudgement.cs
+++ b/osu.Game.Rulesets.Catch/Judgements/CatchJudgement.cs
@@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Scoring;
diff --git a/osu.Game.Rulesets.Catch/Judgements/CatchJudgementResult.cs b/osu.Game.Rulesets.Catch/Judgements/CatchJudgementResult.cs
index 6cc79f9619..4cec61d016 100644
--- a/osu.Game.Rulesets.Catch/Judgements/CatchJudgementResult.cs
+++ b/osu.Game.Rulesets.Catch/Judgements/CatchJudgementResult.cs
@@ -1,9 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
-using JetBrains.Annotations;
using osu.Game.Rulesets.Catch.UI;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects;
@@ -22,7 +19,7 @@ namespace osu.Game.Rulesets.Catch.Judgements
///
public bool CatcherHyperDash;
- public CatchJudgementResult([NotNull] HitObject hitObject, [NotNull] Judgement judgement)
+ public CatchJudgementResult(HitObject hitObject, Judgement judgement)
: base(hitObject, judgement)
{
}
diff --git a/osu.Game.Rulesets.Catch/Judgements/CatchTinyDropletJudgement.cs b/osu.Game.Rulesets.Catch/Judgements/CatchTinyDropletJudgement.cs
index c9052e3c39..d957d4171b 100644
--- a/osu.Game.Rulesets.Catch/Judgements/CatchTinyDropletJudgement.cs
+++ b/osu.Game.Rulesets.Catch/Judgements/CatchTinyDropletJudgement.cs
@@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Catch.Judgements
diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModNoScope.cs b/osu.Game.Rulesets.Catch/Mods/CatchModNoScope.cs
index 19b4a39f97..ddeea51ecb 100644
--- a/osu.Game.Rulesets.Catch/Mods/CatchModNoScope.cs
+++ b/osu.Game.Rulesets.Catch/Mods/CatchModNoScope.cs
@@ -26,6 +26,9 @@ namespace osu.Game.Rulesets.Catch.Mods
var catchPlayfield = (CatchPlayfield)playfield;
bool shouldAlwaysShowCatcher = IsBreakTime.Value;
float targetAlpha = shouldAlwaysShowCatcher ? 1 : ComboBasedAlpha;
+
+ // AlwaysPresent required for catcher to still act on input when fully hidden.
+ catchPlayfield.CatcherArea.AlwaysPresent = true;
catchPlayfield.CatcherArea.Alpha = (float)Interpolation.Lerp(catchPlayfield.CatcherArea.Alpha, targetAlpha, Math.Clamp(catchPlayfield.Time.Elapsed / TRANSITION_DURATION, 0, 1));
}
}
diff --git a/osu.Game.Rulesets.Catch/Objects/BananaShower.cs b/osu.Game.Rulesets.Catch/Objects/BananaShower.cs
index e5541e49c1..b45f95a8e6 100644
--- a/osu.Game.Rulesets.Catch/Objects/BananaShower.cs
+++ b/osu.Game.Rulesets.Catch/Objects/BananaShower.cs
@@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using System.Threading;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects.Types;
diff --git a/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs
index cd2b8348e2..f4bd515995 100644
--- a/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs
+++ b/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs
@@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using System;
using Newtonsoft.Json;
using osu.Framework.Bindables;
diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/CaughtBanana.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/CaughtBanana.cs
index 65d91bffe2..bfeb37b1b7 100644
--- a/osu.Game.Rulesets.Catch/Objects/Drawables/CaughtBanana.cs
+++ b/osu.Game.Rulesets.Catch/Objects/Drawables/CaughtBanana.cs
@@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using osu.Game.Rulesets.Catch.Skinning.Default;
namespace osu.Game.Rulesets.Catch.Objects.Drawables
diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/CaughtDroplet.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/CaughtDroplet.cs
index ed8bf17747..d228c629c0 100644
--- a/osu.Game.Rulesets.Catch/Objects/Drawables/CaughtDroplet.cs
+++ b/osu.Game.Rulesets.Catch/Objects/Drawables/CaughtDroplet.cs
@@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using osu.Game.Rulesets.Catch.Skinning.Default;
namespace osu.Game.Rulesets.Catch.Objects.Drawables
diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/CaughtFruit.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/CaughtFruit.cs
index d296052220..99dcac5268 100644
--- a/osu.Game.Rulesets.Catch/Objects/Drawables/CaughtFruit.cs
+++ b/osu.Game.Rulesets.Catch/Objects/Drawables/CaughtFruit.cs
@@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using osu.Game.Rulesets.Catch.Skinning.Default;
namespace osu.Game.Rulesets.Catch.Objects.Drawables
diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/CaughtObject.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/CaughtObject.cs
index 436edf6367..0c26c52171 100644
--- a/osu.Game.Rulesets.Catch/Objects/Drawables/CaughtObject.cs
+++ b/osu.Game.Rulesets.Catch/Objects/Drawables/CaughtObject.cs
@@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using System;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
@@ -19,7 +17,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
[Cached(typeof(IHasCatchObjectState))]
public abstract partial class CaughtObject : SkinnableDrawable, IHasCatchObjectState
{
- public PalpableCatchHitObject HitObject { get; private set; }
+ public PalpableCatchHitObject HitObject { get; private set; } = null!;
public Bindable AccentColour { get; } = new Bindable();
public Bindable HyperDash { get; } = new Bindable();
public Bindable IndexInBeatmap { get; } = new Bindable();
diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableBanana.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableBanana.cs
index 51addaebd5..26e304cf3f 100644
--- a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableBanana.cs
+++ b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableBanana.cs
@@ -1,9 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
-using JetBrains.Annotations;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Game.Rulesets.Catch.Skinning.Default;
@@ -18,7 +15,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
{
}
- public DrawableBanana([CanBeNull] Banana h)
+ public DrawableBanana(Banana? h)
: base(h)
{
}
diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableBananaShower.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableBananaShower.cs
index c5ae1b5526..03adbce885 100644
--- a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableBananaShower.cs
+++ b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableBananaShower.cs
@@ -1,9 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
-using JetBrains.Annotations;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Objects.Drawables;
@@ -19,7 +16,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
{
}
- public DrawableBananaShower([CanBeNull] BananaShower s)
+ public DrawableBananaShower(BananaShower? s)
: base(s)
{
RelativeSizeAxes = Axes.X;
diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableCatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableCatchHitObject.cs
index c25bc7d076..7f8c17861d 100644
--- a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableCatchHitObject.cs
+++ b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableCatchHitObject.cs
@@ -53,6 +53,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
XOffsetBindable.UnbindFrom(HitObject.XOffsetBindable);
}
+ [CanBeNull]
public Func CheckPosition;
protected override JudgementResult CreateResult(Judgement judgement) => new CatchJudgementResult(HitObject, judgement);
diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableDroplet.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableDroplet.cs
index e8b0c4a9fb..8f32cdcc31 100644
--- a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableDroplet.cs
+++ b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableDroplet.cs
@@ -1,9 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
-using JetBrains.Annotations;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Game.Rulesets.Catch.Skinning.Default;
@@ -18,7 +15,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
{
}
- public DrawableDroplet([CanBeNull] CatchHitObject h)
+ public DrawableDroplet(CatchHitObject? h)
: base(h)
{
}
diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableFruit.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableFruit.cs
index 4347c77383..52c53523e6 100644
--- a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableFruit.cs
+++ b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableFruit.cs
@@ -1,9 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
-using JetBrains.Annotations;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Game.Rulesets.Catch.Skinning.Default;
@@ -18,7 +15,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
{
}
- public DrawableFruit([CanBeNull] Fruit h)
+ public DrawableFruit(Fruit? h)
: base(h)
{
}
diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableJuiceStream.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableJuiceStream.cs
index 1ad1664122..41ecf59276 100644
--- a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableJuiceStream.cs
+++ b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableJuiceStream.cs
@@ -1,9 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
-using JetBrains.Annotations;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Objects.Drawables;
@@ -19,7 +16,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
{
}
- public DrawableJuiceStream([CanBeNull] JuiceStream s)
+ public DrawableJuiceStream(JuiceStream? s)
: base(s)
{
RelativeSizeAxes = Axes.X;
diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawablePalpableCatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawablePalpableCatchHitObject.cs
index 8468cc0a6a..4a9661f108 100644
--- a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawablePalpableCatchHitObject.cs
+++ b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawablePalpableCatchHitObject.cs
@@ -1,9 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
-using JetBrains.Annotations;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
@@ -42,7 +39,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
public float DisplayRotation => ScalingContainer.Rotation;
- protected DrawablePalpableCatchHitObject([CanBeNull] CatchHitObject h)
+ protected DrawablePalpableCatchHitObject(CatchHitObject? h)
: base(h)
{
Origin = Anchor.Centre;
diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableTinyDroplet.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableTinyDroplet.cs
index 8e98efdbda..f820ccdc62 100644
--- a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableTinyDroplet.cs
+++ b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableTinyDroplet.cs
@@ -1,10 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
-using JetBrains.Annotations;
-
namespace osu.Game.Rulesets.Catch.Objects.Drawables
{
public partial class DrawableTinyDroplet : DrawableDroplet
@@ -16,7 +12,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
{
}
- public DrawableTinyDroplet([CanBeNull] TinyDroplet h)
+ public DrawableTinyDroplet(TinyDroplet? h)
: base(h)
{
}
diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/IHasCatchObjectState.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/IHasCatchObjectState.cs
index f30ef0831a..18fc0db6e3 100644
--- a/osu.Game.Rulesets.Catch/Objects/Drawables/IHasCatchObjectState.cs
+++ b/osu.Game.Rulesets.Catch/Objects/Drawables/IHasCatchObjectState.cs
@@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using osu.Framework.Bindables;
using osuTK;
using osuTK.Graphics;
diff --git a/osu.Game.Rulesets.Catch/Objects/Droplet.cs b/osu.Game.Rulesets.Catch/Objects/Droplet.cs
index ecaa4bfaf4..9c1004a04b 100644
--- a/osu.Game.Rulesets.Catch/Objects/Droplet.cs
+++ b/osu.Game.Rulesets.Catch/Objects/Droplet.cs
@@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using osu.Game.Rulesets.Catch.Judgements;
using osu.Game.Rulesets.Judgements;
diff --git a/osu.Game.Rulesets.Catch/Objects/Fruit.cs b/osu.Game.Rulesets.Catch/Objects/Fruit.cs
index bdf8b3f28d..4818fe2cad 100644
--- a/osu.Game.Rulesets.Catch/Objects/Fruit.cs
+++ b/osu.Game.Rulesets.Catch/Objects/Fruit.cs
@@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using osu.Game.Rulesets.Catch.Judgements;
using osu.Game.Rulesets.Judgements;
diff --git a/osu.Game.Rulesets.Catch/Objects/FruitVisualRepresentation.cs b/osu.Game.Rulesets.Catch/Objects/FruitVisualRepresentation.cs
index e5d013dafc..7ec7050245 100644
--- a/osu.Game.Rulesets.Catch/Objects/FruitVisualRepresentation.cs
+++ b/osu.Game.Rulesets.Catch/Objects/FruitVisualRepresentation.cs
@@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
namespace osu.Game.Rulesets.Catch.Objects
{
public enum FruitVisualRepresentation
diff --git a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs
index 015457e84f..96e2d5c4e5 100644
--- a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs
+++ b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs
@@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using System;
using System.Collections.Generic;
using System.Linq;
@@ -142,13 +140,8 @@ namespace osu.Game.Rulesets.Catch.Objects
set
{
path.ControlPoints.Clear();
- path.ExpectedDistance.Value = null;
-
- if (value != null)
- {
- path.ControlPoints.AddRange(value.ControlPoints.Select(c => new PathControlPoint(c.Position, c.Type)));
- path.ExpectedDistance.Value = value.ExpectedDistance.Value;
- }
+ path.ControlPoints.AddRange(value.ControlPoints.Select(c => new PathControlPoint(c.Position, c.Type)));
+ path.ExpectedDistance.Value = value.ExpectedDistance.Value;
}
}
diff --git a/osu.Game.Rulesets.Catch/Objects/PalpableCatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/PalpableCatchHitObject.cs
index c9bc9ca2ac..197029aeeb 100644
--- a/osu.Game.Rulesets.Catch/Objects/PalpableCatchHitObject.cs
+++ b/osu.Game.Rulesets.Catch/Objects/PalpableCatchHitObject.cs
@@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using Newtonsoft.Json;
using osu.Framework.Bindables;
using osu.Game.Rulesets.Objects;
@@ -34,13 +32,13 @@ namespace osu.Game.Rulesets.Catch.Objects
///
public bool HyperDash => hyperDash.Value;
- private CatchHitObject hyperDashTarget;
+ private CatchHitObject? hyperDashTarget;
///
/// The target fruit if we are to initiate a hyperdash.
///
[JsonIgnore]
- public CatchHitObject HyperDashTarget
+ public CatchHitObject? HyperDashTarget
{
get => hyperDashTarget;
set
diff --git a/osu.Game.Rulesets.Catch/Objects/TinyDroplet.cs b/osu.Game.Rulesets.Catch/Objects/TinyDroplet.cs
index 6bd5f0ac2a..1bf160b5a6 100644
--- a/osu.Game.Rulesets.Catch/Objects/TinyDroplet.cs
+++ b/osu.Game.Rulesets.Catch/Objects/TinyDroplet.cs
@@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using osu.Game.Rulesets.Catch.Judgements;
using osu.Game.Rulesets.Judgements;
diff --git a/osu.Game.Rulesets.Catch/Properties/AssemblyInfo.cs b/osu.Game.Rulesets.Catch/Properties/AssemblyInfo.cs
index 6c7f0478a7..26f20b223a 100644
--- a/osu.Game.Rulesets.Catch/Properties/AssemblyInfo.cs
+++ b/osu.Game.Rulesets.Catch/Properties/AssemblyInfo.cs
@@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using System.Runtime.CompilerServices;
// We publish our internal attributes to other sub-projects of the framework.
diff --git a/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs b/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs
index b784fc4c19..b6a42407da 100644
--- a/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs
+++ b/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs
@@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Catch.Scoring
diff --git a/osu.Game.Rulesets.Catch/Skinning/Argon/ArgonJudgementPiece.cs b/osu.Game.Rulesets.Catch/Skinning/Argon/ArgonJudgementPiece.cs
deleted file mode 100644
index 82d10e500d..0000000000
--- a/osu.Game.Rulesets.Catch/Skinning/Argon/ArgonJudgementPiece.cs
+++ /dev/null
@@ -1,193 +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 System;
-using osu.Framework.Allocation;
-using osu.Framework.Extensions;
-using osu.Framework.Graphics;
-using osu.Framework.Graphics.Containers;
-using osu.Framework.Graphics.Shapes;
-using osu.Framework.Graphics.Sprites;
-using osu.Framework.Utils;
-using osu.Game.Graphics;
-using osu.Game.Graphics.Sprites;
-using osu.Game.Rulesets.Judgements;
-using osu.Game.Rulesets.Scoring;
-using osuTK;
-using osuTK.Graphics;
-
-namespace osu.Game.Rulesets.Catch.Skinning.Argon
-{
- public partial class ArgonJudgementPiece : CompositeDrawable, IAnimatableJudgement
- {
- protected readonly HitResult Result;
-
- protected SpriteText JudgementText { get; private set; } = null!;
-
- private RingExplosion? ringExplosion;
-
- [Resolved]
- private OsuColour colours { get; set; } = null!;
-
- public ArgonJudgementPiece(HitResult result)
- {
- Result = result;
- Origin = Anchor.Centre;
- Y = 160;
- }
-
- [BackgroundDependencyLoader]
- private void load()
- {
- AutoSizeAxes = Axes.Both;
-
- InternalChildren = new Drawable[]
- {
- JudgementText = new OsuSpriteText
- {
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- Text = Result.GetDescription().ToUpperInvariant(),
- Colour = colours.ForHitResult(Result),
- Blending = BlendingParameters.Additive,
- Spacing = new Vector2(10, 0),
- Font = OsuFont.Default.With(size: 28, weight: FontWeight.Regular),
- },
- };
-
- if (Result.IsHit())
- {
- AddInternal(ringExplosion = new RingExplosion(Result)
- {
- Colour = colours.ForHitResult(Result),
- });
- }
- }
-
- ///
- /// Plays the default animation for this judgement piece.
- ///
- ///
- /// The base implementation only handles fade (for all result types) and misses.
- /// Individual rulesets are recommended to implement their appropriate hit animations.
- ///
- public virtual void PlayAnimation()
- {
- switch (Result)
- {
- default:
- JudgementText
- .ScaleTo(Vector2.One)
- .ScaleTo(new Vector2(1.4f), 1800, Easing.OutQuint);
- break;
-
- case HitResult.Miss:
- this.ScaleTo(1.6f);
- this.ScaleTo(1, 100, Easing.In);
-
- this.MoveTo(Vector2.Zero);
- this.MoveToOffset(new Vector2(0, 100), 800, Easing.InQuint);
-
- this.RotateTo(0);
- this.RotateTo(40, 800, Easing.InQuint);
- break;
- }
-
- this.FadeOutFromOne(800);
-
- ringExplosion?.PlayAnimation();
- }
-
- public Drawable? GetAboveHitObjectsProxiedContent() => null;
-
- private partial class RingExplosion : CompositeDrawable
- {
- private readonly float travel = 52;
-
- public RingExplosion(HitResult result)
- {
- const float thickness = 4;
-
- const float small_size = 9;
- const float large_size = 14;
-
- Anchor = Anchor.Centre;
- Origin = Anchor.Centre;
-
- Blending = BlendingParameters.Additive;
-
- int countSmall = 0;
- int countLarge = 0;
-
- switch (result)
- {
- case HitResult.Meh:
- countSmall = 3;
- travel *= 0.3f;
- break;
-
- case HitResult.Ok:
- case HitResult.Good:
- countSmall = 4;
- travel *= 0.6f;
- break;
-
- case HitResult.Great:
- case HitResult.Perfect:
- countSmall = 4;
- countLarge = 4;
- break;
- }
-
- for (int i = 0; i < countSmall; i++)
- AddInternal(new RingPiece(thickness) { Size = new Vector2(small_size) });
-
- for (int i = 0; i < countLarge; i++)
- AddInternal(new RingPiece(thickness) { Size = new Vector2(large_size) });
- }
-
- public void PlayAnimation()
- {
- foreach (var c in InternalChildren)
- {
- const float start_position_ratio = 0.3f;
-
- float direction = RNG.NextSingle(0, 360);
- float distance = RNG.NextSingle(travel / 2, travel);
-
- c.MoveTo(new Vector2(
- MathF.Cos(direction) * distance * start_position_ratio,
- MathF.Sin(direction) * distance * start_position_ratio
- ));
-
- c.MoveTo(new Vector2(
- MathF.Cos(direction) * distance,
- MathF.Sin(direction) * distance
- ), 600, Easing.OutQuint);
- }
-
- this.FadeOutFromOne(1000, Easing.OutQuint);
- }
-
- public partial class RingPiece : CircularContainer
- {
- public RingPiece(float thickness = 9)
- {
- Anchor = Anchor.Centre;
- Origin = Anchor.Centre;
-
- Masking = true;
- BorderThickness = thickness;
- BorderColour = Color4.White;
-
- Child = new Box
- {
- AlwaysPresent = true,
- Alpha = 0,
- RelativeSizeAxes = Axes.Both
- };
- }
- }
- }
- }
-}
diff --git a/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs b/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs
index 08ac55508a..fb8af9bdb6 100644
--- a/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs
+++ b/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs
@@ -4,6 +4,7 @@
using System.Linq;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
using osu.Game.Skinning;
using osuTK.Graphics;
@@ -27,12 +28,12 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy
public override Drawable? GetDrawableComponent(ISkinComponentLookup lookup)
{
- if (lookup is GlobalSkinComponentLookup targetComponent)
+ if (lookup is SkinComponentsContainerLookup containerLookup)
{
- switch (targetComponent.Lookup)
+ switch (containerLookup.Target)
{
- case GlobalSkinComponentLookup.LookupType.MainHUDComponents:
- var components = base.GetDrawableComponent(lookup) as SkinnableTargetComponentsContainer;
+ case SkinComponentsContainerLookup.TargetArea.MainHUDComponents:
+ var components = base.GetDrawableComponent(lookup) as Container;
if (providesComboCounter && components != null)
{
diff --git a/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyCatcherNew.cs b/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyCatcherNew.cs
index b36d7f11cb..f6b2c52498 100644
--- a/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyCatcherNew.cs
+++ b/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyCatcherNew.cs
@@ -3,7 +3,6 @@
using System;
using System.Collections.Generic;
-using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
@@ -32,7 +31,7 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy
[BackgroundDependencyLoader]
private void load(ISkinSource skin)
{
- foreach (var state in Enum.GetValues(typeof(CatcherAnimationState)).Cast())
+ foreach (var state in Enum.GetValues())
{
AddInternal(drawables[state] = getDrawableFor(state).With(d =>
{
diff --git a/osu.Game.Rulesets.Catch/UI/CatchComboDisplay.cs b/osu.Game.Rulesets.Catch/UI/CatchComboDisplay.cs
index ffdc5299d0..3d0062d32f 100644
--- a/osu.Game.Rulesets.Catch/UI/CatchComboDisplay.cs
+++ b/osu.Game.Rulesets.Catch/UI/CatchComboDisplay.cs
@@ -29,7 +29,7 @@ namespace osu.Game.Rulesets.Catch.UI
{
}
- [Resolved(canBeNull: true)]
+ [Resolved]
private Player? player { get; set; }
protected override void LoadComplete()
@@ -63,12 +63,12 @@ namespace osu.Game.Rulesets.Catch.UI
updateCombo(result.ComboAtJudgement + 1, judgedObject.AccentColour.Value);
}
- public void OnRevertResult(DrawableCatchHitObject judgedObject, JudgementResult result)
+ public void OnRevertResult(JudgementResult result)
{
if (!result.Type.AffectsCombo() || !result.HasResult)
return;
- updateCombo(result.ComboAtJudgement, judgedObject.AccentColour.Value);
+ updateCombo(result.ComboAtJudgement, null);
}
private void updateCombo(int newCombo, Color4? hitObjectColour)
diff --git a/osu.Game.Rulesets.Catch/UI/CatchCursorContainer.cs b/osu.Game.Rulesets.Catch/UI/CatchCursorContainer.cs
new file mode 100644
index 0000000000..4ae61ef8c7
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/UI/CatchCursorContainer.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.Framework.Graphics;
+using osu.Game.Rulesets.UI;
+
+namespace osu.Game.Rulesets.Catch.UI
+{
+ public partial class CatchCursorContainer : GameplayCursorContainer
+ {
+ // Just hide the cursor.
+ // The main goal here is to show that we have a cursor so the game never shows the global one.
+ protected override Drawable CreateCursor() => Empty();
+ }
+}
diff --git a/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs
index 4df297565e..cf7337fd0d 100644
--- a/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs
+++ b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs
@@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Game.Beatmaps;
@@ -10,6 +8,7 @@ using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Catch.Objects.Drawables;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects.Drawables;
+using osu.Game.Rulesets.UI;
using osu.Game.Rulesets.UI.Scrolling;
using osuTK;
@@ -38,9 +37,9 @@ namespace osu.Game.Rulesets.Catch.UI
// only check the X position; handle all vertical space.
base.ReceivePositionalInputAt(new Vector2(screenSpacePos.X, ScreenSpaceDrawQuad.Centre.Y));
- internal Catcher Catcher { get; private set; }
+ internal Catcher Catcher { get; private set; } = null!;
- internal CatcherArea CatcherArea { get; private set; }
+ internal CatcherArea CatcherArea { get; private set; } = null!;
private readonly IBeatmapDifficultyInfo difficulty;
@@ -49,6 +48,8 @@ namespace osu.Game.Rulesets.Catch.UI
this.difficulty = difficulty;
}
+ protected override GameplayCursorContainer CreateCursor() => new CatchCursorContainer();
+
[BackgroundDependencyLoader]
private void load()
{
@@ -102,7 +103,7 @@ namespace osu.Game.Rulesets.Catch.UI
private void onNewResult(DrawableHitObject judgedObject, JudgementResult result)
=> CatcherArea.OnNewResult((DrawableCatchHitObject)judgedObject, result);
- private void onRevertResult(DrawableHitObject judgedObject, JudgementResult result)
- => CatcherArea.OnRevertResult((DrawableCatchHitObject)judgedObject, result);
+ private void onRevertResult(JudgementResult result)
+ => CatcherArea.OnRevertResult(result);
}
}
diff --git a/osu.Game.Rulesets.Catch/UI/CatchPlayfieldAdjustmentContainer.cs b/osu.Game.Rulesets.Catch/UI/CatchPlayfieldAdjustmentContainer.cs
index c03179dc50..74cbc665c0 100644
--- a/osu.Game.Rulesets.Catch/UI/CatchPlayfieldAdjustmentContainer.cs
+++ b/osu.Game.Rulesets.Catch/UI/CatchPlayfieldAdjustmentContainer.cs
@@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.UI;
diff --git a/osu.Game.Rulesets.Catch/UI/CatchReplayRecorder.cs b/osu.Game.Rulesets.Catch/UI/CatchReplayRecorder.cs
index 9ea150a2cf..32ede8f205 100644
--- a/osu.Game.Rulesets.Catch/UI/CatchReplayRecorder.cs
+++ b/osu.Game.Rulesets.Catch/UI/CatchReplayRecorder.cs
@@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using System.Collections.Generic;
using osu.Game.Rulesets.Catch.Replays;
using osu.Game.Rulesets.Replays;
diff --git a/osu.Game.Rulesets.Catch/UI/CatchTouchInputMapper.cs b/osu.Game.Rulesets.Catch/UI/CatchTouchInputMapper.cs
index d23913136d..10e43cf74a 100644
--- a/osu.Game.Rulesets.Catch/UI/CatchTouchInputMapper.cs
+++ b/osu.Game.Rulesets.Catch/UI/CatchTouchInputMapper.cs
@@ -11,7 +11,6 @@ using osu.Framework.Input.Bindings;
using osu.Framework.Input.Events;
using osu.Game.Graphics;
using osuTK;
-using osuTK.Input;
namespace osu.Game.Rulesets.Catch.UI
{
@@ -106,41 +105,17 @@ namespace osu.Game.Rulesets.Catch.UI
return false;
}
- protected override bool OnMouseDown(MouseDownEvent e)
- {
- return updateAction(e.Button, getTouchCatchActionFromInput(e.ScreenSpaceMousePosition));
- }
-
protected override bool OnTouchDown(TouchDownEvent e)
{
return updateAction(e.Touch.Source, getTouchCatchActionFromInput(e.ScreenSpaceTouch.Position));
}
- protected override bool OnMouseMove(MouseMoveEvent e)
- {
- Show();
-
- TouchCatchAction? action = getTouchCatchActionFromInput(e.ScreenSpaceMousePosition);
-
- // multiple mouse buttons may be pressed and handling the same action.
- foreach (MouseButton button in e.PressedButtons)
- updateAction(button, action);
-
- return false;
- }
-
protected override void OnTouchMove(TouchMoveEvent e)
{
updateAction(e.Touch.Source, getTouchCatchActionFromInput(e.ScreenSpaceTouch.Position));
base.OnTouchMove(e);
}
- protected override void OnMouseUp(MouseUpEvent e)
- {
- updateAction(e.Button, null);
- base.OnMouseUp(e);
- }
-
protected override void OnTouchUp(TouchUpEvent e)
{
updateAction(e.Touch.Source, null);
diff --git a/osu.Game.Rulesets.Catch/UI/Catcher.cs b/osu.Game.Rulesets.Catch/UI/Catcher.cs
index 086b4ff285..1c52c092ec 100644
--- a/osu.Game.Rulesets.Catch/UI/Catcher.cs
+++ b/osu.Game.Rulesets.Catch/UI/Catcher.cs
@@ -1,11 +1,9 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using System;
+using System.Diagnostics;
using System.Linq;
-using JetBrains.Annotations;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
@@ -123,7 +121,7 @@ namespace osu.Game.Rulesets.Catch.UI
private double hyperDashModifier = 1;
private int hyperDashDirection;
private float hyperDashTargetPosition;
- private Bindable hitLighting;
+ private Bindable hitLighting = null!;
private readonly HitExplosionContainer hitExplosionContainer;
@@ -131,7 +129,7 @@ namespace osu.Game.Rulesets.Catch.UI
private readonly DrawablePool caughtBananaPool;
private readonly DrawablePool caughtDropletPool;
- public Catcher([NotNull] DroppedObjectContainer droppedObjectTarget, IBeatmapDifficultyInfo difficulty = null)
+ public Catcher(DroppedObjectContainer droppedObjectTarget, IBeatmapDifficultyInfo? difficulty = null)
{
this.droppedObjectTarget = droppedObjectTarget;
@@ -231,9 +229,8 @@ namespace osu.Game.Rulesets.Catch.UI
// droplet doesn't affect the catcher state
if (hitObject is TinyDroplet) return;
- if (result.IsHit && hitObject.HyperDash)
+ if (result.IsHit && hitObject.HyperDashTarget is CatchHitObject target)
{
- var target = hitObject.HyperDashTarget;
double timeDifference = target.StartTime - hitObject.StartTime;
double positionDifference = target.EffectiveX - X;
double velocity = positionDifference / Math.Max(1.0, timeDifference - 1000.0 / 60.0);
@@ -257,7 +254,7 @@ namespace osu.Game.Rulesets.Catch.UI
}
}
- public void OnRevertResult(DrawableCatchHitObject drawableObject, JudgementResult result)
+ public void OnRevertResult(JudgementResult result)
{
var catchResult = (CatchJudgementResult)result;
@@ -271,8 +268,8 @@ namespace osu.Game.Rulesets.Catch.UI
SetHyperDashState();
}
- caughtObjectContainer.RemoveAll(d => d.HitObject == drawableObject.HitObject, false);
- droppedObjectTarget.RemoveAll(d => d.HitObject == drawableObject.HitObject, false);
+ caughtObjectContainer.RemoveAll(d => d.HitObject == result.HitObject, false);
+ droppedObjectTarget.RemoveAll(d => d.HitObject == result.HitObject, false);
}
///
@@ -385,7 +382,7 @@ namespace osu.Game.Rulesets.Catch.UI
private void addLighting(JudgementResult judgementResult, Color4 colour, float x) =>
hitExplosionContainer.Add(new HitExplosionEntry(Time.Current, judgementResult, colour, x));
- private CaughtObject getCaughtObject(PalpableCatchHitObject source)
+ private CaughtObject? getCaughtObject(PalpableCatchHitObject source)
{
switch (source)
{
@@ -406,6 +403,7 @@ namespace osu.Game.Rulesets.Catch.UI
private CaughtObject getDroppedObject(CaughtObject caughtObject)
{
var droppedObject = getCaughtObject(caughtObject.HitObject);
+ Debug.Assert(droppedObject != null);
droppedObject.CopyStateFrom(caughtObject);
droppedObject.Anchor = Anchor.TopLeft;
diff --git a/osu.Game.Rulesets.Catch/UI/CatcherAnimationState.cs b/osu.Game.Rulesets.Catch/UI/CatcherAnimationState.cs
index 82591eb47f..566e9d1911 100644
--- a/osu.Game.Rulesets.Catch/UI/CatcherAnimationState.cs
+++ b/osu.Game.Rulesets.Catch/UI/CatcherAnimationState.cs
@@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
namespace osu.Game.Rulesets.Catch.UI
{
public enum CatcherAnimationState
diff --git a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs
index d0da05bc44..1b99270b65 100644
--- a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs
+++ b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs
@@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using System;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
@@ -35,7 +33,7 @@ namespace osu.Game.Rulesets.Catch.UI
private readonly CatcherTrailDisplay catcherTrails;
- private Catcher catcher;
+ private Catcher catcher = null!;
///
/// -1 when only left button is pressed.
@@ -75,10 +73,10 @@ namespace osu.Game.Rulesets.Catch.UI
comboDisplay.OnNewResult(hitObject, result);
}
- public void OnRevertResult(DrawableCatchHitObject hitObject, JudgementResult result)
+ public void OnRevertResult(JudgementResult result)
{
- comboDisplay.OnRevertResult(hitObject, result);
- Catcher.OnRevertResult(hitObject, result);
+ comboDisplay.OnRevertResult(result);
+ Catcher.OnRevertResult(result);
}
protected override void Update()
diff --git a/osu.Game.Rulesets.Catch/UI/CatcherTrail.cs b/osu.Game.Rulesets.Catch/UI/CatcherTrail.cs
index f486633e12..762f95828a 100644
--- a/osu.Game.Rulesets.Catch/UI/CatcherTrail.cs
+++ b/osu.Game.Rulesets.Catch/UI/CatcherTrail.cs
@@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using osu.Framework.Graphics;
using osu.Framework.Timing;
using osu.Game.Rulesets.Objects.Pooling;
diff --git a/osu.Game.Rulesets.Catch/UI/CatcherTrailAnimation.cs b/osu.Game.Rulesets.Catch/UI/CatcherTrailAnimation.cs
index 02bc5be863..0a5281cd10 100644
--- a/osu.Game.Rulesets.Catch/UI/CatcherTrailAnimation.cs
+++ b/osu.Game.Rulesets.Catch/UI/CatcherTrailAnimation.cs
@@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
namespace osu.Game.Rulesets.Catch.UI
{
public enum CatcherTrailAnimation
diff --git a/osu.Game.Rulesets.Catch/UI/CatcherTrailDisplay.cs b/osu.Game.Rulesets.Catch/UI/CatcherTrailDisplay.cs
index e982be53d8..e3e01c1b39 100644
--- a/osu.Game.Rulesets.Catch/UI/CatcherTrailDisplay.cs
+++ b/osu.Game.Rulesets.Catch/UI/CatcherTrailDisplay.cs
@@ -1,10 +1,9 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using System;
using osu.Framework.Allocation;
+using osu.Framework.Extensions.ObjectExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Pooling;
@@ -41,7 +40,7 @@ namespace osu.Game.Rulesets.Catch.UI
private readonly Container hyperDashAfterImages;
[Resolved]
- private ISkinSource skin { get; set; }
+ private ISkinSource skin { get; set; } = null!;
public CatcherTrailDisplay()
{
@@ -130,7 +129,7 @@ namespace osu.Game.Rulesets.Catch.UI
{
base.Dispose(isDisposing);
- if (skin != null)
+ if (skin.IsNotNull())
skin.SourceChanged -= skinSourceChanged;
}
}
diff --git a/osu.Game.Rulesets.Catch/UI/CatcherTrailEntry.cs b/osu.Game.Rulesets.Catch/UI/CatcherTrailEntry.cs
index 78d6979b78..3a40ab26cc 100644
--- a/osu.Game.Rulesets.Catch/UI/CatcherTrailEntry.cs
+++ b/osu.Game.Rulesets.Catch/UI/CatcherTrailEntry.cs
@@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using osu.Framework.Graphics.Performance;
using osuTK;
diff --git a/osu.Game.Rulesets.Catch/UI/Direction.cs b/osu.Game.Rulesets.Catch/UI/Direction.cs
index 15e4aed86b..65f064b7fb 100644
--- a/osu.Game.Rulesets.Catch/UI/Direction.cs
+++ b/osu.Game.Rulesets.Catch/UI/Direction.cs
@@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
namespace osu.Game.Rulesets.Catch.UI
{
public enum Direction
diff --git a/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs b/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs
index 0be271b236..7930a07551 100644
--- a/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs
+++ b/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs
@@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Allocation;
@@ -27,7 +25,7 @@ namespace osu.Game.Rulesets.Catch.UI
protected override bool UserScrollSpeedAdjustment => false;
- public DrawableCatchRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList mods = null)
+ public DrawableCatchRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList? mods = null)
: base(ruleset, beatmap, mods)
{
Direction.Value = ScrollingDirection.Down;
@@ -54,6 +52,6 @@ namespace osu.Game.Rulesets.Catch.UI
protected override PassThroughInputManager CreateInputManager() => new CatchInputManager(Ruleset.RulesetInfo);
- public override DrawableHitObject CreateDrawableRepresentation(CatchHitObject h) => null;
+ public override DrawableHitObject? CreateDrawableRepresentation(CatchHitObject h) => null;
}
}
diff --git a/osu.Game.Rulesets.Catch/UI/DroppedObjectContainer.cs b/osu.Game.Rulesets.Catch/UI/DroppedObjectContainer.cs
index cb2d8498cc..df1e932ad5 100644
--- a/osu.Game.Rulesets.Catch/UI/DroppedObjectContainer.cs
+++ b/osu.Game.Rulesets.Catch/UI/DroppedObjectContainer.cs
@@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Catch.Objects.Drawables;
diff --git a/osu.Game.Rulesets.Catch/UI/HitExplosionContainer.cs b/osu.Game.Rulesets.Catch/UI/HitExplosionContainer.cs
index e1dd665bf2..1e2d94433c 100644
--- a/osu.Game.Rulesets.Catch/UI/HitExplosionContainer.cs
+++ b/osu.Game.Rulesets.Catch/UI/HitExplosionContainer.cs
@@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using osu.Framework.Graphics;
using osu.Framework.Graphics.Pooling;
using osu.Game.Rulesets.Objects.Pooling;
diff --git a/osu.Game.Rulesets.Catch/UI/ICatchComboCounter.cs b/osu.Game.Rulesets.Catch/UI/ICatchComboCounter.cs
index 5d027edbaa..cfb6879067 100644
--- a/osu.Game.Rulesets.Catch/UI/ICatchComboCounter.cs
+++ b/osu.Game.Rulesets.Catch/UI/ICatchComboCounter.cs
@@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using osu.Framework.Graphics;
using osuTK.Graphics;
diff --git a/osu.Game.Rulesets.Catch/UI/SkinnableCatcher.cs b/osu.Game.Rulesets.Catch/UI/SkinnableCatcher.cs
index 78a71f26a2..bcc59a5e4f 100644
--- a/osu.Game.Rulesets.Catch/UI/SkinnableCatcher.cs
+++ b/osu.Game.Rulesets.Catch/UI/SkinnableCatcher.cs
@@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
diff --git a/osu.Game.Rulesets.Catch/osu.Game.Rulesets.Catch.csproj b/osu.Game.Rulesets.Catch/osu.Game.Rulesets.Catch.csproj
index e2f95ca177..ecce7c1b3f 100644
--- a/osu.Game.Rulesets.Catch/osu.Game.Rulesets.Catch.csproj
+++ b/osu.Game.Rulesets.Catch/osu.Game.Rulesets.Catch.csproj
@@ -1,6 +1,6 @@
- netstandard2.1
+ net6.0
Library
true
catch the fruit. to the beat.
@@ -15,4 +15,4 @@
-
\ No newline at end of file
+
diff --git a/osu.Game.Rulesets.Mania.Tests.Android/Properties/AndroidManifest.xml b/osu.Game.Rulesets.Mania.Tests.Android/AndroidManifest.xml
similarity index 95%
rename from osu.Game.Rulesets.Mania.Tests.Android/Properties/AndroidManifest.xml
rename to osu.Game.Rulesets.Mania.Tests.Android/AndroidManifest.xml
index de7935b2ef..4a1545a423 100644
--- a/osu.Game.Rulesets.Mania.Tests.Android/Properties/AndroidManifest.xml
+++ b/osu.Game.Rulesets.Mania.Tests.Android/AndroidManifest.xml
@@ -1,5 +1,5 @@
-
+
\ No newline at end of file
diff --git a/osu.Game.Rulesets.Mania.Tests.Android/osu.Game.Rulesets.Mania.Tests.Android.csproj b/osu.Game.Rulesets.Mania.Tests.Android/osu.Game.Rulesets.Mania.Tests.Android.csproj
index 9674186039..25335754d2 100644
--- a/osu.Game.Rulesets.Mania.Tests.Android/osu.Game.Rulesets.Mania.Tests.Android.csproj
+++ b/osu.Game.Rulesets.Mania.Tests.Android/osu.Game.Rulesets.Mania.Tests.Android.csproj
@@ -1,49 +1,24 @@
-
-
+
- Debug
- AnyCPU
- 8.0.30703
- 2.0
- {531F1092-DB27-445D-AA33-2A77C7187C99}
- {EFBA0AD7-5A72-4C68-AF49-83D382785DCF};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
- {122416d6-6b49-4ee2-a1e8-b825f31c79fe}
+ net6.0-android
+ Exe
osu.Game.Rulesets.Mania.Tests
osu.Game.Rulesets.Mania.Tests.Android
- Properties\AndroidManifest.xml
- armeabi-v7a;x86;arm64-v8a
-
-
- None
- cjk;mideast;other;rare;west
- true
-
-
-
-
-
-
-
+
%(RecursiveDir)%(Filename)%(Extension)
+
+
+ %(RecursiveDir)%(Filename)%(Extension)
+ Android\%(RecursiveDir)%(Filename)%(Extension)
+
-
- {48f4582b-7687-4621-9cbe-5c24197cb536}
- osu.Game.Rulesets.Mania
-
-
- {2A66DD92-ADB1-4994-89E2-C94E04ACDA0D}
- osu.Game
-
+
+
-
-
- 5.0.0
-
-
-
\ No newline at end of file
diff --git a/osu.Game.Rulesets.Mania.Tests.iOS/Application.cs b/osu.Game.Rulesets.Mania.Tests.iOS/Application.cs
index 2d1015387a..a508198f7f 100644
--- a/osu.Game.Rulesets.Mania.Tests.iOS/Application.cs
+++ b/osu.Game.Rulesets.Mania.Tests.iOS/Application.cs
@@ -3,7 +3,6 @@
#nullable disable
-using osu.Framework.iOS;
using UIKit;
namespace osu.Game.Rulesets.Mania.Tests.iOS
@@ -12,7 +11,7 @@ namespace osu.Game.Rulesets.Mania.Tests.iOS
{
public static void Main(string[] args)
{
- UIApplication.Main(args, typeof(GameUIApplication), typeof(AppDelegate));
+ UIApplication.Main(args, null, typeof(AppDelegate));
}
}
}
diff --git a/osu.Game.Rulesets.Mania.Tests.iOS/Info.plist b/osu.Game.Rulesets.Mania.Tests.iOS/Info.plist
index 82d1c8ea24..ff5dde856e 100644
--- a/osu.Game.Rulesets.Mania.Tests.iOS/Info.plist
+++ b/osu.Game.Rulesets.Mania.Tests.iOS/Info.plist
@@ -13,7 +13,7 @@
LSRequiresIPhoneOS
MinimumOSVersion
- 10.0
+ 13.4
UIDeviceFamily
1
diff --git a/osu.Game.Rulesets.Mania.Tests.iOS/osu.Game.Rulesets.Mania.Tests.iOS.csproj b/osu.Game.Rulesets.Mania.Tests.iOS/osu.Game.Rulesets.Mania.Tests.iOS.csproj
index 88ad484bc1..51e07dd6c1 100644
--- a/osu.Game.Rulesets.Mania.Tests.iOS/osu.Game.Rulesets.Mania.Tests.iOS.csproj
+++ b/osu.Game.Rulesets.Mania.Tests.iOS/osu.Game.Rulesets.Mania.Tests.iOS.csproj
@@ -1,35 +1,19 @@
-
-
+
- Debug
- iPhoneSimulator
- {39FD990E-B6CE-4B2A-999F-BC008CF2C64C}
Exe
+ net6.0-ios
+ 13.4
osu.Game.Rulesets.Mania.Tests
osu.Game.Rulesets.Mania.Tests.iOS
-
-
-
- Linker.xml
-
-
-
%(RecursiveDir)%(Filename)%(Extension)
-
- {2A66DD92-ADB1-4994-89E2-C94E04ACDA0D}
- osu.Game
-
-
- {48F4582B-7687-4621-9CBE-5C24197CB536}
- osu.Game.Rulesets.Mania
-
+
+
-
-
\ No newline at end of file
+
diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaBeatSnapGrid.cs b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaBeatSnapGrid.cs
index 653c75baac..aca555552f 100644
--- a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaBeatSnapGrid.cs
+++ b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaBeatSnapGrid.cs
@@ -90,6 +90,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor
public override bool CursorInPlacementArea => false;
public TestHitObjectComposer(Playfield playfield)
+ : base(new ManiaRuleset())
{
Playfield = playfield;
}
diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneObjectPlacement.cs b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneObjectPlacement.cs
new file mode 100644
index 0000000000..13a116b209
--- /dev/null
+++ b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneObjectPlacement.cs
@@ -0,0 +1,67 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System.Linq;
+using NUnit.Framework;
+using osu.Framework.Allocation;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Testing;
+using osu.Game.Configuration;
+using osu.Game.Rulesets.Mania.UI;
+using osu.Game.Tests.Visual;
+using osuTK.Input;
+
+namespace osu.Game.Rulesets.Mania.Tests.Editor
+{
+ public partial class TestSceneObjectPlacement : EditorTestScene
+ {
+ protected override Ruleset CreateEditorRuleset() => new ManiaRuleset();
+
+ [Resolved]
+ private OsuConfigManager config { get; set; } = null!;
+
+ [Test]
+ public void TestPlacementBeforeTrackStart()
+ {
+ AddStep("Seek to 0", () => EditorClock.Seek(0));
+ AddStep("Select note", () => InputManager.Key(Key.Number2));
+ AddStep("Hover negative span", () =>
+ {
+ InputManager.MoveMouseTo(this.ChildrenOfType().First(x => x.Name == "Icons").Children[0]);
+ });
+ AddStep("Click", () => InputManager.Click(MouseButton.Left));
+ AddAssert("No notes placed", () => EditorBeatmap.HitObjects.All(x => x.StartTime >= 0));
+ }
+
+ [Test]
+ public void TestSeekOnNotePlacement()
+ {
+ double? initialTime = null;
+
+ AddStep("store initial time", () => initialTime = EditorClock.CurrentTime);
+ AddStep("change seek setting to true", () => config.SetValue(OsuSetting.EditorAutoSeekOnPlacement, true));
+ placeObject();
+ AddUntilStep("wait for seek to complete", () => !EditorClock.IsSeeking);
+ AddAssert("seeked forward to object", () => EditorClock.CurrentTime, () => Is.GreaterThan(initialTime));
+ }
+
+ [Test]
+ public void TestNoSeekOnNotePlacement()
+ {
+ double? initialTime = null;
+
+ AddStep("store initial time", () => initialTime = EditorClock.CurrentTime);
+ AddStep("change seek setting to false", () => config.SetValue(OsuSetting.EditorAutoSeekOnPlacement, false));
+ placeObject();
+ AddAssert("not seeking", () => !EditorClock.IsSeeking);
+ AddAssert("time is unchanged", () => EditorClock.CurrentTime, () => Is.EqualTo(initialTime));
+ }
+
+ private void placeObject()
+ {
+ AddStep("select note placement tool", () => InputManager.Key(Key.Number2));
+ AddStep("move mouse to centre of last column", () => InputManager.MoveMouseTo(this.ChildrenOfType().Last().ScreenSpaceDrawQuad.Centre));
+ AddStep("place note", () => InputManager.Click(MouseButton.Left));
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModFadeIn.cs b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModFadeIn.cs
new file mode 100644
index 0000000000..2c8c151e7f
--- /dev/null
+++ b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModFadeIn.cs
@@ -0,0 +1,19 @@
+// 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.Rulesets.Mania.Mods;
+using osu.Game.Tests.Visual;
+
+namespace osu.Game.Rulesets.Mania.Tests.Mods
+{
+ public partial class TestSceneManiaModFadeIn : ModTestScene
+ {
+ protected override Ruleset CreatePlayerRuleset() => new ManiaRuleset();
+
+ [TestCase(0.5f)]
+ [TestCase(0.1f)]
+ [TestCase(0.7f)]
+ public void TestCoverage(float coverage) => CreateModTest(new ModTestData { Mod = new ManiaModFadeIn { Coverage = { Value = coverage } }, PassCondition = () => true });
+ }
+}
diff --git a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModHidden.cs b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModHidden.cs
new file mode 100644
index 0000000000..204f26f151
--- /dev/null
+++ b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModHidden.cs
@@ -0,0 +1,19 @@
+// 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.Rulesets.Mania.Mods;
+using osu.Game.Tests.Visual;
+
+namespace osu.Game.Rulesets.Mania.Tests.Mods
+{
+ public partial class TestSceneManiaModHidden : ModTestScene
+ {
+ protected override Ruleset CreatePlayerRuleset() => new ManiaRuleset();
+
+ [TestCase(0.5f)]
+ [TestCase(0.2f)]
+ [TestCase(0.8f)]
+ public void TestCoverage(float coverage) => CreateModTest(new ModTestData { Mod = new ManiaModHidden { Coverage = { Value = coverage } }, PassCondition = () => true });
+ }
+}
diff --git a/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/LongNoteTailWang.png b/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/LongNoteTailWang.png
new file mode 100644
index 0000000000..982cc1d259
Binary files /dev/null and b/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/LongNoteTailWang.png differ
diff --git a/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/skin.ini b/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/skin.ini
index 9c987efc60..7c51036d69 100644
--- a/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/skin.ini
+++ b/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/skin.ini
@@ -14,4 +14,6 @@ Hit200: mania/hit200@2x
Hit300: mania/hit300@2x
Hit300g: mania/hit300g@2x
StageLeft: mania/stage-left
-StageRight: mania/stage-right
\ No newline at end of file
+StageRight: mania/stage-right
+NoteImage0L: LongNoteTailWang
+NoteImage1L: LongNoteTailWang
diff --git a/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj b/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj
index be51dc0e4c..027bf60a0c 100644
--- a/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj
+++ b/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj
@@ -1,9 +1,9 @@
-
+
-
+
WinExe
diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs
index 308238d87a..77f93b4ef9 100644
--- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs
+++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs
@@ -35,8 +35,8 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
protected PatternGenerator(LegacyRandom random, HitObject hitObject, ManiaBeatmap beatmap, Pattern previousPattern, IBeatmap originalBeatmap)
: base(hitObject, beatmap, previousPattern)
{
- if (random == null) throw new ArgumentNullException(nameof(random));
- if (originalBeatmap == null) throw new ArgumentNullException(nameof(originalBeatmap));
+ ArgumentNullException.ThrowIfNull(random);
+ ArgumentNullException.ThrowIfNull(originalBeatmap);
Random = random;
OriginalBeatmap = originalBeatmap;
diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/PatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/PatternGenerator.cs
index b2e89c3410..931673f337 100644
--- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/PatternGenerator.cs
+++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/PatternGenerator.cs
@@ -33,9 +33,9 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns
protected PatternGenerator(HitObject hitObject, ManiaBeatmap beatmap, Pattern previousPattern)
{
- if (hitObject == null) throw new ArgumentNullException(nameof(hitObject));
- if (beatmap == null) throw new ArgumentNullException(nameof(beatmap));
- if (previousPattern == null) throw new ArgumentNullException(nameof(previousPattern));
+ ArgumentNullException.ThrowIfNull(hitObject);
+ ArgumentNullException.ThrowIfNull(beatmap);
+ ArgumentNullException.ThrowIfNull(previousPattern);
HitObject = hitObject;
Beatmap = beatmap;
diff --git a/osu.Game.Rulesets.Mania/Configuration/ManiaRulesetConfigManager.cs b/osu.Game.Rulesets.Mania/Configuration/ManiaRulesetConfigManager.cs
index d367c82ed8..99a80ef28d 100644
--- a/osu.Game.Rulesets.Mania/Configuration/ManiaRulesetConfigManager.cs
+++ b/osu.Game.Rulesets.Mania/Configuration/ManiaRulesetConfigManager.cs
@@ -4,6 +4,7 @@
using System;
using osu.Framework.Configuration.Tracking;
using osu.Game.Configuration;
+using osu.Game.Localisation;
using osu.Game.Rulesets.Configuration;
using osu.Game.Rulesets.Mania.UI;
@@ -30,8 +31,8 @@ namespace osu.Game.Rulesets.Mania.Configuration
new TrackedSetting(ManiaRulesetSetting.ScrollTime,
scrollTime => new SettingDescription(
rawValue: scrollTime,
- name: "Scroll Speed",
- value: $"{scrollTime}ms (speed {(int)Math.Round(DrawableManiaRuleset.MAX_TIME_RANGE / scrollTime)})"
+ name: RulesetSettingsStrings.ScrollSpeed,
+ value: RulesetSettingsStrings.ScrollSpeedTooltip(scrollTime, (int)Math.Round(DrawableManiaRuleset.MAX_TIME_RANGE / scrollTime))
)
)
};
diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs
index 6162184c9a..d324682989 100644
--- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs
+++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs
@@ -52,7 +52,7 @@ namespace osu.Game.Rulesets.Mania
public override ScoreProcessor CreateScoreProcessor() => new ManiaScoreProcessor();
- public override HealthProcessor CreateHealthProcessor(double drainStartTime) => new ManiaHealthProcessor(drainStartTime, 0.5);
+ public override HealthProcessor CreateHealthProcessor(double drainStartTime) => new ManiaHealthProcessor(drainStartTime);
public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new ManiaBeatmapConverter(beatmap, this);
@@ -245,6 +245,7 @@ namespace osu.Game.Rulesets.Mania
new MultiMod(new ManiaModDoubleTime(), new ManiaModNightcore()),
new MultiMod(new ManiaModFadeIn(), new ManiaModHidden()),
new ManiaModFlashlight(),
+ new ModAccuracyChallenge(),
};
case ModType.Conversion:
diff --git a/osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs b/osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs
index 9ed555da51..fc0b4a9ed9 100644
--- a/osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs
+++ b/osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs
@@ -6,6 +6,7 @@ using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Localisation;
using osu.Game.Graphics.UserInterface;
+using osu.Game.Localisation;
using osu.Game.Overlays.Settings;
using osu.Game.Rulesets.Mania.Configuration;
using osu.Game.Rulesets.Mania.UI;
@@ -30,26 +31,26 @@ namespace osu.Game.Rulesets.Mania
{
new SettingsEnumDropdown
{
- LabelText = "Scrolling direction",
+ LabelText = RulesetSettingsStrings.ScrollingDirection,
Current = config.GetBindable(ManiaRulesetSetting.ScrollDirection)
},
new SettingsSlider
{
- LabelText = "Scroll speed",
+ LabelText = RulesetSettingsStrings.ScrollSpeed,
Current = config.GetBindable(ManiaRulesetSetting.ScrollTime),
KeyboardStep = 5
},
new SettingsCheckbox
{
- LabelText = "Timing-based note colouring",
+ LabelText = RulesetSettingsStrings.TimingBasedColouring,
Current = config.GetBindable(ManiaRulesetSetting.TimingBasedNoteColouring),
}
};
}
- private partial class ManiaScrollSlider : OsuSliderBar
+ private partial class ManiaScrollSlider : RoundedSliderBar
{
- public override LocalisableString TooltipText => $"{Current.Value}ms (speed {(int)Math.Round(DrawableManiaRuleset.MAX_TIME_RANGE / Current.Value)})";
+ public override LocalisableString TooltipText => RulesetSettingsStrings.ScrollSpeedTooltip(Current.Value, (int)Math.Round(DrawableManiaRuleset.MAX_TIME_RANGE / Current.Value));
}
}
}
diff --git a/osu.Game.Rulesets.Mania/MathUtils/LegacySortHelper.cs b/osu.Game.Rulesets.Mania/MathUtils/LegacySortHelper.cs
index 1a67117c03..4d93826240 100644
--- a/osu.Game.Rulesets.Mania/MathUtils/LegacySortHelper.cs
+++ b/osu.Game.Rulesets.Mania/MathUtils/LegacySortHelper.cs
@@ -22,8 +22,7 @@ namespace osu.Game.Rulesets.Mania.MathUtils
public static void Sort(T[] keys, IComparer comparer)
{
- if (keys == null)
- throw new ArgumentNullException(nameof(keys));
+ ArgumentNullException.ThrowIfNull(keys);
if (keys.Length == 0)
return;
diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModFadeIn.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModFadeIn.cs
index c6e9c339f4..196514c7b1 100644
--- a/osu.Game.Rulesets.Mania/Mods/ManiaModFadeIn.cs
+++ b/osu.Game.Rulesets.Mania/Mods/ManiaModFadeIn.cs
@@ -3,6 +3,7 @@
using System;
using System.Linq;
+using osu.Framework.Bindables;
using osu.Framework.Localisation;
using osu.Game.Rulesets.Mania.UI;
@@ -18,5 +19,13 @@ namespace osu.Game.Rulesets.Mania.Mods
public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ManiaModHidden)).ToArray();
protected override CoverExpandDirection ExpandDirection => CoverExpandDirection.AlongScroll;
+
+ public override BindableNumber Coverage { get; } = new BindableFloat(0.5f)
+ {
+ Precision = 0.1f,
+ MinValue = 0.1f,
+ MaxValue = 0.7f,
+ Default = 0.5f,
+ };
}
}
diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModHidden.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModHidden.cs
index eeb6e94fc7..f23cb335a5 100644
--- a/osu.Game.Rulesets.Mania/Mods/ManiaModHidden.cs
+++ b/osu.Game.Rulesets.Mania/Mods/ManiaModHidden.cs
@@ -5,6 +5,7 @@ using System;
using System.Linq;
using osu.Framework.Localisation;
using osu.Game.Rulesets.Mania.UI;
+using osu.Framework.Bindables;
namespace osu.Game.Rulesets.Mania.Mods
{
@@ -13,6 +14,14 @@ namespace osu.Game.Rulesets.Mania.Mods
public override LocalisableString Description => @"Keys fade out before you hit them!";
public override double ScoreMultiplier => 1;
+ public override BindableNumber Coverage { get; } = new BindableFloat(0.5f)
+ {
+ Precision = 0.1f,
+ MinValue = 0.2f,
+ MaxValue = 0.8f,
+ Default = 0.5f,
+ };
+
public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ManiaModFadeIn)).ToArray();
protected override CoverExpandDirection ExpandDirection => CoverExpandDirection.AgainstScroll;
diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModPlayfieldCover.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModPlayfieldCover.cs
index 6a94e5d371..09abe8d7f4 100644
--- a/osu.Game.Rulesets.Mania/Mods/ManiaModPlayfieldCover.cs
+++ b/osu.Game.Rulesets.Mania/Mods/ManiaModPlayfieldCover.cs
@@ -3,8 +3,10 @@
using System;
using System.Linq;
+using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
+using osu.Game.Configuration;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.UI;
using osu.Game.Rulesets.Mods;
@@ -22,6 +24,9 @@ namespace osu.Game.Rulesets.Mania.Mods
///
protected abstract CoverExpandDirection ExpandDirection { get; }
+ [SettingSource("Coverage", "The proportion of playfield height that notes will be hidden for.")]
+ public abstract BindableNumber Coverage { get; }
+
public virtual void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset)
{
ManiaPlayfield maniaPlayfield = (ManiaPlayfield)drawableRuleset.Playfield;
@@ -36,7 +41,7 @@ namespace osu.Game.Rulesets.Mania.Mods
{
c.RelativeSizeAxes = Axes.Both;
c.Direction = ExpandDirection;
- c.Coverage = 0.5f;
+ c.Coverage = Coverage.Value;
}));
}
}
diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs
index 759d2346e8..25d0573a82 100644
--- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs
+++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs
@@ -69,8 +69,6 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
///
private double? releaseTime;
- public override double MaximumJudgementOffset => Tail.MaximumJudgementOffset;
-
public DrawableHoldNote()
: this(null)
{
@@ -376,7 +374,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
protected override void OnFree()
{
- slidingSample.Samples = null;
+ slidingSample.ClearSamples();
base.OnFree();
}
}
diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTail.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTail.cs
index bf477277c6..20ea962994 100644
--- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTail.cs
+++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTail.cs
@@ -15,13 +15,6 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
///
public partial class DrawableHoldNoteTail : DrawableNote
{
- ///
- /// Lenience of release hit windows. This is to make cases where the hold note release
- /// is timed alongside presses of other hit objects less awkward.
- /// Todo: This shouldn't exist for non-LegacyBeatmapDecoder beatmaps
- ///
- private const double release_window_lenience = 1.5;
-
protected override ManiaSkinComponents Component => ManiaSkinComponents.HoldNoteTail;
protected DrawableHoldNote HoldNote => (DrawableHoldNote)ParentHitObject;
@@ -40,14 +33,12 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
public void UpdateResult() => base.UpdateResult(true);
- public override double MaximumJudgementOffset => base.MaximumJudgementOffset * release_window_lenience;
-
protected override void CheckForResult(bool userTriggered, double timeOffset)
{
Debug.Assert(HitObject.HitWindows != null);
// Factor in the release lenience
- timeOffset /= release_window_lenience;
+ timeOffset /= TailNote.RELEASE_WINDOW_LENIENCE;
if (!userTriggered)
{
diff --git a/osu.Game.Rulesets.Mania/Objects/HoldNote.cs b/osu.Game.Rulesets.Mania/Objects/HoldNote.cs
index 22fab15c1b..c367886efe 100644
--- a/osu.Game.Rulesets.Mania/Objects/HoldNote.cs
+++ b/osu.Game.Rulesets.Mania/Objects/HoldNote.cs
@@ -81,6 +81,8 @@ namespace osu.Game.Rulesets.Mania.Objects
///
public TailNote Tail { get; private set; }
+ public override double MaximumJudgementOffset => Tail.MaximumJudgementOffset;
+
///
/// The time between ticks of this hold.
///
diff --git a/osu.Game.Rulesets.Mania/Objects/TailNote.cs b/osu.Game.Rulesets.Mania/Objects/TailNote.cs
index cda8e2fa31..d6dc25079a 100644
--- a/osu.Game.Rulesets.Mania/Objects/TailNote.cs
+++ b/osu.Game.Rulesets.Mania/Objects/TailNote.cs
@@ -10,6 +10,15 @@ namespace osu.Game.Rulesets.Mania.Objects
{
public class TailNote : Note
{
+ ///
+ /// Lenience of release hit windows. This is to make cases where the hold note release
+ /// is timed alongside presses of other hit objects less awkward.
+ /// Todo: This shouldn't exist for non-LegacyBeatmapDecoder beatmaps
+ ///
+ public const double RELEASE_WINDOW_LENIENCE = 1.5;
+
public override Judgement CreateJudgement() => new ManiaJudgement();
+
+ public override double MaximumJudgementOffset => base.MaximumJudgementOffset * RELEASE_WINDOW_LENIENCE;
}
}
diff --git a/osu.Game.Rulesets.Mania/Scoring/ManiaHealthProcessor.cs b/osu.Game.Rulesets.Mania/Scoring/ManiaHealthProcessor.cs
index 5c6682ed73..16f7af0d0a 100644
--- a/osu.Game.Rulesets.Mania/Scoring/ManiaHealthProcessor.cs
+++ b/osu.Game.Rulesets.Mania/Scoring/ManiaHealthProcessor.cs
@@ -11,8 +11,8 @@ namespace osu.Game.Rulesets.Mania.Scoring
public partial class ManiaHealthProcessor : DrainingHealthProcessor
{
///
- public ManiaHealthProcessor(double drainStartTime, double drainLenience = 0)
- : base(drainStartTime, drainLenience)
+ public ManiaHealthProcessor(double drainStartTime)
+ : base(drainStartTime, 1.0)
{
}
diff --git a/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonHoldNoteTailPiece.cs b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonHoldNoteTailPiece.cs
index a2166a6708..428439d52c 100644
--- a/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonHoldNoteTailPiece.cs
+++ b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonHoldNoteTailPiece.cs
@@ -5,11 +5,11 @@ using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
-using osu.Framework.Graphics.Colour;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.UI.Scrolling;
+using osuTK;
using osuTK.Graphics;
namespace osu.Game.Rulesets.Mania.Skinning.Argon
@@ -19,8 +19,8 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
private readonly IBindable direction = new Bindable();
private readonly IBindable accentColour = new Bindable();
- private readonly Box colouredBox;
- private readonly Box shadow;
+ private readonly Box shadeBackground;
+ private readonly Box shadeForeground;
public ArgonHoldNoteTailPiece()
{
@@ -32,32 +32,25 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
InternalChildren = new Drawable[]
{
- shadow = new Box
+ shadeBackground = new Box
{
RelativeSizeAxes = Axes.Both,
},
new Container
{
- Anchor = Anchor.BottomLeft,
- Origin = Anchor.BottomLeft,
RelativeSizeAxes = Axes.Both,
- Height = 0.82f,
- Masking = true,
+ Height = ArgonNotePiece.NOTE_ACCENT_RATIO,
+ Anchor = Anchor.BottomCentre,
+ Origin = Anchor.BottomCentre,
CornerRadius = ArgonNotePiece.CORNER_RADIUS,
+ Masking = true,
Children = new Drawable[]
{
- colouredBox = new Box
+ shadeForeground = new Box
{
RelativeSizeAxes = Axes.Both,
- }
- }
- },
- new Circle
- {
- RelativeSizeAxes = Axes.X,
- Height = ArgonNotePiece.CORNER_RADIUS * 2,
- Anchor = Anchor.BottomLeft,
- Origin = Anchor.BottomLeft,
+ },
+ },
},
};
}
@@ -77,19 +70,13 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
private void onDirectionChanged(ValueChangedEvent direction)
{
- colouredBox.Anchor = colouredBox.Origin = direction.NewValue == ScrollingDirection.Up
- ? Anchor.TopCentre
- : Anchor.BottomCentre;
+ Scale = new Vector2(1, direction.NewValue == ScrollingDirection.Up ? -1 : 1);
}
private void onAccentChanged(ValueChangedEvent accent)
{
- colouredBox.Colour = ColourInfo.GradientVertical(
- accent.NewValue,
- accent.NewValue.Darken(0.1f)
- );
-
- shadow.Colour = accent.NewValue.Darken(0.5f);
+ shadeBackground.Colour = accent.NewValue.Darken(1.7f);
+ shadeForeground.Colour = accent.NewValue.Darken(1.1f);
}
}
}
diff --git a/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonJudgementPiece.cs b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonJudgementPiece.cs
index 2dbf475c7e..4ce3c50f7c 100644
--- a/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonJudgementPiece.cs
+++ b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonJudgementPiece.cs
@@ -3,7 +3,6 @@
using System;
using osu.Framework.Allocation;
-using osu.Framework.Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
@@ -18,20 +17,18 @@ using osuTK.Graphics;
namespace osu.Game.Rulesets.Mania.Skinning.Argon
{
- public partial class ArgonJudgementPiece : CompositeDrawable, IAnimatableJudgement
+ public partial class ArgonJudgementPiece : JudgementPiece, IAnimatableJudgement
{
- protected readonly HitResult Result;
-
- protected SpriteText JudgementText { get; private set; } = null!;
-
private RingExplosion? ringExplosion;
[Resolved]
private OsuColour colours { get; set; } = null!;
public ArgonJudgementPiece(HitResult result)
+ : base(result)
{
- Result = result;
+ AutoSizeAxes = Axes.Both;
+
Origin = Anchor.Centre;
Y = 160;
}
@@ -39,22 +36,6 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
[BackgroundDependencyLoader]
private void load()
{
- AutoSizeAxes = Axes.Both;
-
- InternalChildren = new Drawable[]
- {
- JudgementText = new OsuSpriteText
- {
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- Text = Result.GetDescription().ToUpperInvariant(),
- Colour = colours.ForHitResult(Result),
- Blending = BlendingParameters.Additive,
- Spacing = new Vector2(10, 0),
- Font = OsuFont.Default.With(size: 28, weight: FontWeight.Regular),
- },
- };
-
if (Result.IsHit())
{
AddInternal(ringExplosion = new RingExplosion(Result)
@@ -64,6 +45,16 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
}
}
+ protected override SpriteText CreateJudgementText() =>
+ new OsuSpriteText
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Blending = BlendingParameters.Additive,
+ Spacing = new Vector2(10, 0),
+ Font = OsuFont.Default.With(size: 28, weight: FontWeight.Regular),
+ };
+
///
/// Plays the default animation for this judgement piece.
///
diff --git a/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonNotePiece.cs b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonNotePiece.cs
index 25b1965c18..2a5bce255c 100644
--- a/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonNotePiece.cs
+++ b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonNotePiece.cs
@@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
internal partial class ArgonNotePiece : CompositeDrawable
{
public const float NOTE_HEIGHT = 42;
-
+ public const float NOTE_ACCENT_RATIO = 0.82f;
public const float CORNER_RADIUS = 3.4f;
private readonly IBindable direction = new Bindable();
@@ -47,7 +47,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
RelativeSizeAxes = Axes.Both,
- Height = 0.82f,
+ Height = NOTE_ACCENT_RATIO,
Masking = true,
CornerRadius = CORNER_RADIUS,
Children = new Drawable[]
@@ -95,6 +95,8 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
colouredBox.Anchor = colouredBox.Origin = direction.NewValue == ScrollingDirection.Up
? Anchor.TopCentre
: Anchor.BottomCentre;
+
+ Scale = new Vector2(1, direction.NewValue == ScrollingDirection.Up ? -1 : 1);
}
private void onAccentChanged(ValueChangedEvent accent)
diff --git a/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs b/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs
index eb7f63fbe2..057b7eb0d9 100644
--- a/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs
+++ b/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs
@@ -27,6 +27,10 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
switch (lookup)
{
case GameplaySkinComponentLookup resultComponent:
+ // This should eventually be moved to a skin setting, when supported.
+ if (Skin is ArgonProSkin && resultComponent.Component >= HitResult.Great)
+ return Drawable.Empty();
+
return new ArgonJudgementPiece(resultComponent.Component);
case ManiaSkinComponentLookup maniaComponent:
diff --git a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyBodyPiece.cs b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyBodyPiece.cs
index c928ebb3e0..69eacda541 100644
--- a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyBodyPiece.cs
+++ b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyBodyPiece.cs
@@ -2,12 +2,15 @@
// See the LICENCE file in the repository root for full licence text.
using System;
+using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions.ObjectExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Animations;
+using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Textures;
+using osu.Framework.Testing;
using osu.Game.Rulesets.Mania.Objects.Drawables;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.UI.Scrolling;
@@ -34,6 +37,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
private Drawable? lightContainer;
private Drawable? light;
+ private LegacyNoteBodyStyle? bodyStyle;
public LegacyBodyPiece()
{
@@ -80,7 +84,14 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
};
}
- bodySprite = skin.GetAnimation(imageName, WrapMode.ClampToEdge, WrapMode.ClampToEdge, true, true).With(d =>
+ bodyStyle = skin.GetConfig(new ManiaSkinConfigurationLookup(LegacyManiaSkinConfigurationLookups.NoteBodyStyle))?.Value;
+
+ var wrapMode = bodyStyle == LegacyNoteBodyStyle.Stretch ? WrapMode.ClampToEdge : WrapMode.Repeat;
+
+ direction.BindTo(scrollingInfo.Direction);
+ isHitting.BindTo(holdNote.IsHitting);
+
+ bodySprite = skin.GetAnimation(imageName, wrapMode, wrapMode, true, true).With(d =>
{
if (d == null)
return;
@@ -91,15 +102,11 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
d.Anchor = Anchor.TopCentre;
d.RelativeSizeAxes = Axes.Both;
d.Size = Vector2.One;
- d.FillMode = FillMode.Stretch;
- // Todo: Wrap
+ // Todo: Wrap?
});
if (bodySprite != null)
InternalChild = bodySprite;
-
- direction.BindTo(scrollingInfo.Direction);
- isHitting.BindTo(holdNote.IsHitting);
}
protected override void LoadComplete()
@@ -161,7 +168,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
if (bodySprite != null)
{
bodySprite.Origin = Anchor.BottomCentre;
- bodySprite.Scale = new Vector2(1, -1);
+ bodySprite.Scale = new Vector2(bodySprite.Scale.X, Math.Abs(bodySprite.Scale.Y) * -1);
}
if (light != null)
@@ -172,7 +179,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
if (bodySprite != null)
{
bodySprite.Origin = Anchor.TopCentre;
- bodySprite.Scale = Vector2.One;
+ bodySprite.Scale = new Vector2(bodySprite.Scale.X, Math.Abs(bodySprite.Scale.Y));
}
if (light != null)
@@ -203,6 +210,29 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
{
base.Update();
missFadeTime.Value ??= holdNote.HoldBrokenTime;
+
+ // here we go...
+ switch (bodyStyle)
+ {
+ case LegacyNoteBodyStyle.Stretch:
+ // this is how lazer works by default. nothing required.
+ break;
+
+ default:
+ // this is where things get fucked up.
+ // honestly there's three modes to handle here but they seem really pointless?
+ // let's wait to see if anyone actually uses them in skins.
+ if (bodySprite != null)
+ {
+ var sprite = bodySprite as Sprite ?? bodySprite.ChildrenOfType().Single();
+
+ bodySprite.FillMode = FillMode.Stretch;
+ // i dunno this looks about right??
+ bodySprite.Scale = new Vector2(1, 32800 / sprite.DrawHeight);
+ }
+
+ break;
+ }
}
protected override void Dispose(bool isDisposing)
diff --git a/osu.Game.Rulesets.Mania/UI/Column.cs b/osu.Game.Rulesets.Mania/UI/Column.cs
index 6a31fb3fda..6ca830a82f 100644
--- a/osu.Game.Rulesets.Mania/UI/Column.cs
+++ b/osu.Game.Rulesets.Mania/UI/Column.cs
@@ -134,6 +134,9 @@ namespace osu.Game.Rulesets.Mania.UI
protected override void Dispose(bool isDisposing)
{
+ // must happen before children are disposed in base call to prevent illegal accesses to the hit explosion pool.
+ NewResult -= OnNewResult;
+
base.Dispose(isDisposing);
if (skin != null)
@@ -206,18 +209,6 @@ namespace osu.Game.Rulesets.Mania.UI
keyBindingContainer = maniaInputManager?.KeyBindingContainer;
}
- protected override bool OnMouseDown(MouseDownEvent e)
- {
- keyBindingContainer?.TriggerPressed(column.Action.Value);
- return base.OnMouseDown(e);
- }
-
- protected override void OnMouseUp(MouseUpEvent e)
- {
- keyBindingContainer?.TriggerReleased(column.Action.Value);
- base.OnMouseUp(e);
- }
-
protected override bool OnTouchDown(TouchDownEvent e)
{
keyBindingContainer?.TriggerPressed(column.Action.Value);
diff --git a/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs b/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs
index 01e9926ad7..e3ebadc836 100644
--- a/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs
+++ b/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs
@@ -29,8 +29,7 @@ namespace osu.Game.Rulesets.Mania.UI
public ManiaPlayfield(List stageDefinitions)
{
- if (stageDefinitions == null)
- throw new ArgumentNullException(nameof(stageDefinitions));
+ ArgumentNullException.ThrowIfNull(stageDefinitions);
if (stageDefinitions.Count <= 0)
throw new ArgumentException("Can't have zero or fewer stages.");
diff --git a/osu.Game.Rulesets.Mania/UI/ManiaScrollingDirection.cs b/osu.Game.Rulesets.Mania/UI/ManiaScrollingDirection.cs
index 44fe4b1b30..4a8843c999 100644
--- a/osu.Game.Rulesets.Mania/UI/ManiaScrollingDirection.cs
+++ b/osu.Game.Rulesets.Mania/UI/ManiaScrollingDirection.cs
@@ -1,15 +1,18 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
+using osu.Framework.Localisation;
+using osu.Game.Localisation;
using osu.Game.Rulesets.UI.Scrolling;
namespace osu.Game.Rulesets.Mania.UI
{
public enum ManiaScrollingDirection
{
+ [LocalisableDescription(typeof(RulesetSettingsStrings), nameof(RulesetSettingsStrings.ScrollingDirectionUp))]
Up = ScrollingDirection.Up,
+
+ [LocalisableDescription(typeof(RulesetSettingsStrings), nameof(RulesetSettingsStrings.ScrollingDirectionDown))]
Down = ScrollingDirection.Down
}
}
diff --git a/osu.Game.Rulesets.Mania/UI/Stage.cs b/osu.Game.Rulesets.Mania/UI/Stage.cs
index fc38a96a35..c1d3e85bf1 100644
--- a/osu.Game.Rulesets.Mania/UI/Stage.cs
+++ b/osu.Game.Rulesets.Mania/UI/Stage.cs
@@ -156,6 +156,9 @@ namespace osu.Game.Rulesets.Mania.UI
protected override void Dispose(bool isDisposing)
{
+ // must happen before children are disposed in base call to prevent illegal accesses to the judgement pool.
+ NewResult -= OnNewResult;
+
base.Dispose(isDisposing);
if (currentSkin != null)
diff --git a/osu.Game.Rulesets.Mania/osu.Game.Rulesets.Mania.csproj b/osu.Game.Rulesets.Mania/osu.Game.Rulesets.Mania.csproj
index 4f6840f9ca..72f172188e 100644
--- a/osu.Game.Rulesets.Mania/osu.Game.Rulesets.Mania.csproj
+++ b/osu.Game.Rulesets.Mania/osu.Game.Rulesets.Mania.csproj
@@ -1,6 +1,6 @@
- netstandard2.1
+ net6.0
Library
true
smash the keys. to the beat.
@@ -15,4 +15,4 @@
-
\ No newline at end of file
+
diff --git a/osu.Game.Rulesets.Osu.Tests.Android/Properties/AndroidManifest.xml b/osu.Game.Rulesets.Osu.Tests.Android/AndroidManifest.xml
similarity index 95%
rename from osu.Game.Rulesets.Osu.Tests.Android/Properties/AndroidManifest.xml
rename to osu.Game.Rulesets.Osu.Tests.Android/AndroidManifest.xml
index 3ce17ccc27..45d27dda70 100644
--- a/osu.Game.Rulesets.Osu.Tests.Android/Properties/AndroidManifest.xml
+++ b/osu.Game.Rulesets.Osu.Tests.Android/AndroidManifest.xml
@@ -1,5 +1,5 @@
-
+
\ No newline at end of file
diff --git a/osu.Game.Rulesets.Osu.Tests.Android/osu.Game.Rulesets.Osu.Tests.Android.csproj b/osu.Game.Rulesets.Osu.Tests.Android/osu.Game.Rulesets.Osu.Tests.Android.csproj
index f4b673f10b..e8a46a9828 100644
--- a/osu.Game.Rulesets.Osu.Tests.Android/osu.Game.Rulesets.Osu.Tests.Android.csproj
+++ b/osu.Game.Rulesets.Osu.Tests.Android/osu.Game.Rulesets.Osu.Tests.Android.csproj
@@ -1,49 +1,27 @@
-
-
+
- Debug
- AnyCPU
- 8.0.30703
- 2.0
- {90CAB706-39CB-4B93-9629-3218A6FF8E9B}
- {EFBA0AD7-5A72-4C68-AF49-83D382785DCF};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
- {122416d6-6b49-4ee2-a1e8-b825f31c79fe}
+ net6.0-android
+ Exe
osu.Game.Rulesets.Osu.Tests
osu.Game.Rulesets.Osu.Tests.Android
- Properties\AndroidManifest.xml
- armeabi-v7a;x86;arm64-v8a
-
-
- None
- cjk;mideast;other;rare;west
- true
-
-
-
-
-
-
-
+
%(RecursiveDir)%(Filename)%(Extension)
+
+
+ %(RecursiveDir)%(Filename)%(Extension)
+ Android\%(RecursiveDir)%(Filename)%(Extension)
+
-
- {c92a607b-1fdd-4954-9f92-03ff547d9080}
- osu.Game.Rulesets.Osu
-
-
- {2a66dd92-adb1-4994-89e2-c94e04acda0d}
- osu.Game
-
+
+
-
- 5.0.0
-
+
-
\ No newline at end of file
diff --git a/osu.Game.Rulesets.Osu.Tests.iOS/Application.cs b/osu.Game.Rulesets.Osu.Tests.iOS/Application.cs
index ad23f3ee33..6ef29fa68e 100644
--- a/osu.Game.Rulesets.Osu.Tests.iOS/Application.cs
+++ b/osu.Game.Rulesets.Osu.Tests.iOS/Application.cs
@@ -3,7 +3,6 @@
#nullable disable
-using osu.Framework.iOS;
using UIKit;
namespace osu.Game.Rulesets.Osu.Tests.iOS
@@ -12,7 +11,7 @@ namespace osu.Game.Rulesets.Osu.Tests.iOS
{
public static void Main(string[] args)
{
- UIApplication.Main(args, typeof(GameUIApplication), typeof(AppDelegate));
+ UIApplication.Main(args, null, typeof(AppDelegate));
}
}
}
diff --git a/osu.Game.Rulesets.Osu.Tests.iOS/Info.plist b/osu.Game.Rulesets.Osu.Tests.iOS/Info.plist
index a88b74695c..1e33f2ff16 100644
--- a/osu.Game.Rulesets.Osu.Tests.iOS/Info.plist
+++ b/osu.Game.Rulesets.Osu.Tests.iOS/Info.plist
@@ -13,7 +13,7 @@
LSRequiresIPhoneOS
MinimumOSVersion
- 10.0
+ 13.4
UIDeviceFamily
1
diff --git a/osu.Game.Rulesets.Osu.Tests.iOS/osu.Game.Rulesets.Osu.Tests.iOS.csproj b/osu.Game.Rulesets.Osu.Tests.iOS/osu.Game.Rulesets.Osu.Tests.iOS.csproj
index 545abcec6c..7d50deb8ba 100644
--- a/osu.Game.Rulesets.Osu.Tests.iOS/osu.Game.Rulesets.Osu.Tests.iOS.csproj
+++ b/osu.Game.Rulesets.Osu.Tests.iOS/osu.Game.Rulesets.Osu.Tests.iOS.csproj
@@ -1,35 +1,20 @@
-
-
+
- Debug
- iPhoneSimulator
- {6653CA6F-DB06-4604-A3FD-762E25C2AF96}
+ Exe
+ net6.0-ios
+ 13.4
Exe
osu.Game.Rulesets.Osu.Tests
osu.Game.Rulesets.Osu.Tests.iOS
-
-
-
- Linker.xml
-
-
-
%(RecursiveDir)%(Filename)%(Extension)
-
- {2A66DD92-ADB1-4994-89E2-C94E04ACDA0D}
- osu.Game
-
-
- {C92A607B-1FDD-4954-9F92-03FF547D9080}
- osu.Game.Rulesets.Osu
-
+
+
-
-
\ No newline at end of file
+
diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestScenePathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestScenePathControlPointVisualiser.cs
index d1a04e28e5..37561fda85 100644
--- a/osu.Game.Rulesets.Osu.Tests/Editor/TestScenePathControlPointVisualiser.cs
+++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestScenePathControlPointVisualiser.cs
@@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
public partial class TestScenePathControlPointVisualiser : OsuManualInputManagerTestScene
{
private Slider slider;
- private PathControlPointVisualiser visualiser;
+ private PathControlPointVisualiser visualiser;
[SetUp]
public void Setup() => Schedule(() =>
@@ -148,7 +148,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
assertControlPointPathType(3, null);
}
- private void createVisualiser(bool allowSelection) => AddStep("create visualiser", () => Child = visualiser = new PathControlPointVisualiser(slider, allowSelection)
+ private void createVisualiser(bool allowSelection) => AddStep("create visualiser", () => Child = visualiser = new PathControlPointVisualiser(slider, allowSelection)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre
diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderControlPointPiece.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderControlPointPiece.cs
index 112aab884b..db9eea4127 100644
--- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderControlPointPiece.cs
+++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderControlPointPiece.cs
@@ -159,11 +159,11 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
}
private void assertSelectionCount(int count) =>
- AddAssert($"{count} control point pieces selected", () => this.ChildrenOfType().Count(piece => piece.IsSelected.Value) == count);
+ AddAssert($"{count} control point pieces selected", () => this.ChildrenOfType>().Count(piece => piece.IsSelected.Value) == count);
private void assertSelected(int index) =>
AddAssert($"{(index + 1).ToOrdinalWords()} control point piece selected",
- () => this.ChildrenOfType().Single(piece => piece.ControlPoint == slider.Path.ControlPoints[index]).IsSelected.Value);
+ () => this.ChildrenOfType>().Single(piece => piece.ControlPoint == slider.Path.ControlPoints[index]).IsSelected.Value);
private void moveMouseToRelativePosition(Vector2 relativePosition) =>
AddStep($"move mouse to {relativePosition}", () =>
@@ -202,12 +202,12 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
moveMouseToControlPoint(2);
AddStep("hold left mouse", () => InputManager.PressButton(MouseButton.Left));
- AddAssert("three control point pieces selected", () => this.ChildrenOfType().Count(piece => piece.IsSelected.Value) == 3);
+ AddAssert("three control point pieces selected", () => this.ChildrenOfType>().Count(piece => piece.IsSelected.Value) == 3);
addMovementStep(new Vector2(450, 50));
AddStep("release left mouse", () => InputManager.ReleaseButton(MouseButton.Left));
- AddAssert("three control point pieces selected", () => this.ChildrenOfType().Count(piece => piece.IsSelected.Value) == 3);
+ AddAssert("three control point pieces selected", () => this.ChildrenOfType>().Count(piece => piece.IsSelected.Value) == 3);
assertControlPointPosition(2, new Vector2(450, 50));
assertControlPointType(2, PathType.PerfectCurve);
@@ -236,12 +236,12 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
moveMouseToControlPoint(3);
AddStep("hold left mouse", () => InputManager.PressButton(MouseButton.Left));
- AddAssert("three control point pieces selected", () => this.ChildrenOfType().Count(piece => piece.IsSelected.Value) == 3);
+ AddAssert("three control point pieces selected", () => this.ChildrenOfType>().Count(piece => piece.IsSelected.Value) == 3);
addMovementStep(new Vector2(550, 50));
AddStep("release left mouse", () => InputManager.ReleaseButton(MouseButton.Left));
- AddAssert("three control point pieces selected", () => this.ChildrenOfType().Count(piece => piece.IsSelected.Value) == 3);
+ AddAssert("three control point pieces selected", () => this.ChildrenOfType>().Count(piece => piece.IsSelected.Value) == 3);
// note: if the head is part of the selection being moved, the entire slider is moved.
// the unselected nodes will therefore change position relative to the slider head.
@@ -354,7 +354,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
public new SliderBodyPiece BodyPiece => base.BodyPiece;
public new TestSliderCircleOverlay HeadOverlay => (TestSliderCircleOverlay)base.HeadOverlay;
public new TestSliderCircleOverlay TailOverlay => (TestSliderCircleOverlay)base.TailOverlay;
- public new PathControlPointVisualiser ControlPointVisualiser => base.ControlPointVisualiser;
+ public new PathControlPointVisualiser ControlPointVisualiser => base.ControlPointVisualiser;
public TestSliderBlueprint(Slider slider)
: base(slider)
diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSelectionBlueprint.cs
index ad740b2977..8ed77d45d7 100644
--- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSelectionBlueprint.cs
+++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSelectionBlueprint.cs
@@ -199,7 +199,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
public new SliderBodyPiece BodyPiece => base.BodyPiece;
public new TestSliderCircleOverlay HeadOverlay => (TestSliderCircleOverlay)base.HeadOverlay;
public new TestSliderCircleOverlay TailOverlay => (TestSliderCircleOverlay)base.TailOverlay;
- public new PathControlPointVisualiser ControlPointVisualiser => base.ControlPointVisualiser;
+ public new PathControlPointVisualiser ControlPointVisualiser => base.ControlPointVisualiser;
public TestSliderBlueprint(Slider slider)
: base(slider)
diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSnapping.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSnapping.cs
index e9d50d5118..f262a4334a 100644
--- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSnapping.cs
+++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSnapping.cs
@@ -72,14 +72,14 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
[Test]
public void TestMovingUnsnappedSliderNodesSnaps()
{
- PathControlPointPiece sliderEnd = null;
+ PathControlPointPiece sliderEnd = null;
assertSliderSnapped(false);
AddStep("select slider", () => EditorBeatmap.SelectedHitObjects.Add(slider));
AddStep("select slider end", () =>
{
- sliderEnd = this.ChildrenOfType().Single(piece => piece.ControlPoint == slider.Path.ControlPoints.Last());
+ sliderEnd = this.ChildrenOfType>().Single(piece => piece.ControlPoint == slider.Path.ControlPoints.Last());
InputManager.MoveMouseTo(sliderEnd.ScreenSpaceDrawQuad.Centre);
});
AddStep("move slider end", () =>
@@ -99,7 +99,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
AddStep("select slider", () => EditorBeatmap.SelectedHitObjects.Add(slider));
AddStep("move mouse to new point location", () =>
{
- var firstPiece = this.ChildrenOfType().Single(piece => piece.ControlPoint == slider.Path.ControlPoints[0]);
+ var firstPiece = this.ChildrenOfType>().Single(piece => piece.ControlPoint == slider.Path.ControlPoints[0]);
var pos = slider.Path.PositionAt(0.25d) + slider.Position;
InputManager.MoveMouseTo(firstPiece.Parent.ToScreenSpace(pos));
});
@@ -120,7 +120,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
AddStep("select slider", () => EditorBeatmap.SelectedHitObjects.Add(slider));
AddStep("move mouse to second control point", () =>
{
- var secondPiece = this.ChildrenOfType().Single(piece => piece.ControlPoint == slider.Path.ControlPoints[1]);
+ var secondPiece = this.ChildrenOfType>().Single(piece => piece.ControlPoint == slider.Path.ControlPoints[1]);
InputManager.MoveMouseTo(secondPiece);
});
AddStep("quick delete", () =>
diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSplitting.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSplitting.cs
index b2ac462c8f..6cb77c7b92 100644
--- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSplitting.cs
+++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSplitting.cs
@@ -29,7 +29,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
=> Editor.ChildrenOfType().First();
private Slider? slider;
- private PathControlPointVisualiser? visualiser;
+ private PathControlPointVisualiser? visualiser;
private const double split_gap = 100;
@@ -61,7 +61,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
AddStep("select added slider", () =>
{
EditorBeatmap.SelectedHitObjects.Add(slider);
- visualiser = blueprintContainer.SelectionBlueprints.First(o => o.Item == slider).ChildrenOfType().First();
+ visualiser = blueprintContainer.SelectionBlueprints.First(o => o.Item == slider).ChildrenOfType>().First();
});
moveMouseToControlPoint(2);
@@ -122,7 +122,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
AddStep("select added slider", () =>
{
EditorBeatmap.SelectedHitObjects.Add(slider);
- visualiser = blueprintContainer.SelectionBlueprints.First(o => o.Item == slider).ChildrenOfType().First();
+ visualiser = blueprintContainer.SelectionBlueprints.First(o => o.Item == slider).ChildrenOfType>().First();
});
moveMouseToControlPoint(2);
@@ -190,7 +190,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
AddStep("select added slider", () =>
{
EditorBeatmap.SelectedHitObjects.Add(slider);
- visualiser = blueprintContainer.SelectionBlueprints.First(o => o.Item == slider).ChildrenOfType().First();
+ visualiser = blueprintContainer.SelectionBlueprints.First(o => o.Item == slider).ChildrenOfType>().First();
});
moveMouseToControlPoint(2);
diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAutopilot.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAutopilot.cs
new file mode 100644
index 0000000000..37b31d1d1a
--- /dev/null
+++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAutopilot.cs
@@ -0,0 +1,31 @@
+// 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.Rulesets.Osu.Mods;
+using osuTK.Input;
+
+namespace osu.Game.Rulesets.Osu.Tests.Mods
+{
+ public partial class TestSceneOsuModAutopilot : OsuModTestScene
+ {
+ [Test]
+ public void TestInstantResume()
+ {
+ CreateModTest(new ModTestData
+ {
+ Mod = new OsuModAutopilot(),
+ PassCondition = () => true,
+ Autoplay = false,
+ });
+
+ AddUntilStep("wait for gameplay start", () => Player.LocalUserPlaying.Value);
+ AddStep("press pause", () => InputManager.PressKey(Key.Escape));
+ AddUntilStep("wait until paused", () => Player.GameplayClockContainer.IsPaused.Value);
+ AddStep("release pause", () => InputManager.ReleaseKey(Key.Escape));
+ AddStep("press resume", () => InputManager.PressKey(Key.Escape));
+ AddUntilStep("wait for resume", () => !Player.IsResuming);
+ AddAssert("resumed", () => !Player.GameplayClockContainer.IsPaused.Value);
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Osu.Tests/OsuHitObjectGenerationUtilsTest.cs b/osu.Game.Rulesets.Osu.Tests/OsuHitObjectGenerationUtilsTest.cs
new file mode 100644
index 0000000000..daa914cac2
--- /dev/null
+++ b/osu.Game.Rulesets.Osu.Tests/OsuHitObjectGenerationUtilsTest.cs
@@ -0,0 +1,91 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System.Linq;
+using NUnit.Framework;
+using osu.Game.Beatmaps;
+using osu.Game.Beatmaps.ControlPoints;
+using osu.Game.Rulesets.Objects;
+using osu.Game.Rulesets.Objects.Types;
+using osu.Game.Rulesets.Osu.Objects;
+using osu.Game.Rulesets.Osu.UI;
+using osu.Game.Rulesets.Osu.Utils;
+using osuTK;
+
+namespace osu.Game.Rulesets.Osu.Tests
+{
+ [TestFixture]
+ public class OsuHitObjectGenerationUtilsTest
+ {
+ private static Slider createTestSlider()
+ {
+ var slider = new Slider
+ {
+ Position = new Vector2(128, 128),
+ Path = new SliderPath
+ {
+ ControlPoints =
+ {
+ new PathControlPoint(new Vector2(), PathType.Linear),
+ new PathControlPoint(new Vector2(-64, -128), PathType.Linear), // absolute position: (64, 0)
+ new PathControlPoint(new Vector2(-128, 0), PathType.Linear) // absolute position: (0, 128)
+ }
+ },
+ RepeatCount = 1
+ };
+ slider.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
+ return slider;
+ }
+
+ [Test]
+ public void TestReflectSliderHorizontallyAlongPlayfield()
+ {
+ var slider = createTestSlider();
+
+ OsuHitObjectGenerationUtils.ReflectHorizontallyAlongPlayfield(slider);
+
+ Assert.That(slider.Position, Is.EqualTo(new Vector2(OsuPlayfield.BASE_SIZE.X - 128, 128)));
+ Assert.That(slider.NestedHitObjects.OfType().Single().Position, Is.EqualTo(new Vector2(OsuPlayfield.BASE_SIZE.X - 0, 128)));
+ Assert.That(slider.Path.ControlPoints.Select(point => point.Position), Is.EquivalentTo(new[]
+ {
+ new Vector2(),
+ new Vector2(64, -128),
+ new Vector2(128, 0)
+ }));
+ }
+
+ [Test]
+ public void TestReflectSliderVerticallyAlongPlayfield()
+ {
+ var slider = createTestSlider();
+
+ OsuHitObjectGenerationUtils.ReflectVerticallyAlongPlayfield(slider);
+
+ Assert.That(slider.Position, Is.EqualTo(new Vector2(128, OsuPlayfield.BASE_SIZE.Y - 128)));
+ Assert.That(slider.NestedHitObjects.OfType().Single().Position, Is.EqualTo(new Vector2(0, OsuPlayfield.BASE_SIZE.Y - 128)));
+ Assert.That(slider.Path.ControlPoints.Select(point => point.Position), Is.EquivalentTo(new[]
+ {
+ new Vector2(),
+ new Vector2(-64, 128),
+ new Vector2(-128, 0)
+ }));
+ }
+
+ [Test]
+ public void TestFlipSliderInPlaceHorizontally()
+ {
+ var slider = createTestSlider();
+
+ OsuHitObjectGenerationUtils.FlipSliderInPlaceHorizontally(slider);
+
+ Assert.That(slider.Position, Is.EqualTo(new Vector2(128, 128)));
+ Assert.That(slider.NestedHitObjects.OfType().Single().Position, Is.EqualTo(new Vector2(256, 128)));
+ Assert.That(slider.Path.ControlPoints.Select(point => point.Position), Is.EquivalentTo(new[]
+ {
+ new Vector2(),
+ new Vector2(64, -128),
+ new Vector2(128, 0)
+ }));
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs
index a418df605f..50f9c5e775 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs
@@ -5,9 +5,11 @@
using System.Linq;
using NUnit.Framework;
+using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
+using osu.Game.Configuration;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables;
@@ -22,6 +24,9 @@ namespace osu.Game.Rulesets.Osu.Tests
{
private int depthIndex;
+ [Resolved]
+ private OsuConfigManager config { get; set; }
+
[Test]
public void TestHits()
{
@@ -56,6 +61,13 @@ namespace osu.Game.Rulesets.Osu.Tests
AddStep("Hit stream late", () => SetContents(_ => testStream(5, true, 150)));
}
+ [Test]
+ public void TestHitLighting()
+ {
+ AddToggleStep("toggle hit lighting", v => config.SetValue(OsuSetting.HitLighting, v));
+ AddStep("Hit Big Single", () => SetContents(_ => testSingle(2, true)));
+ }
+
private Drawable testSingle(float circleSize, bool auto = false, double timeOffset = 0, Vector2? positionOffset = null)
{
var playfield = new TestOsuPlayfield();
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleKiai.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleKiai.cs
index 2c9f1acd2c..718664d649 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleKiai.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleKiai.cs
@@ -4,29 +4,38 @@
#nullable disable
using NUnit.Framework;
+using osu.Framework.Audio;
+using osu.Framework.Audio.Track;
+using osu.Framework.Timing;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
namespace osu.Game.Rulesets.Osu.Tests
{
[TestFixture]
- public partial class TestSceneHitCircleKiai : TestSceneHitCircle
+ public partial class TestSceneHitCircleKiai : TestSceneHitCircle, IBeatSyncProvider
{
+ private ControlPointInfo controlPoints { get; set; }
+
[SetUp]
public void SetUp() => Schedule(() =>
{
- var controlPointInfo = new ControlPointInfo();
+ controlPoints = new ControlPointInfo();
- controlPointInfo.Add(0, new TimingControlPoint { BeatLength = 500 });
- controlPointInfo.Add(0, new EffectControlPoint { KiaiMode = true });
+ controlPoints.Add(0, new TimingControlPoint { BeatLength = 500 });
+ controlPoints.Add(0, new EffectControlPoint { KiaiMode = true });
Beatmap.Value = CreateWorkingBeatmap(new Beatmap
{
- ControlPointInfo = controlPointInfo
+ ControlPointInfo = controlPoints
});
// track needs to be playing for BeatSyncedContainer to work.
Beatmap.Value.Track.Start();
});
+
+ ChannelAmplitudes IHasAmplitudes.CurrentAmplitudes => new ChannelAmplitudes();
+ ControlPointInfo IBeatSyncProvider.ControlPoints => controlPoints;
+ IClock IBeatSyncProvider.Clock => Clock;
}
}
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleLateFade.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleLateFade.cs
new file mode 100644
index 0000000000..3c32b4fa65
--- /dev/null
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleLateFade.cs
@@ -0,0 +1,156 @@
+// 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.Linq;
+using NUnit.Framework;
+using osu.Framework.Extensions.ObjectExtensions;
+using osu.Game.Beatmaps;
+using osu.Game.Beatmaps.ControlPoints;
+using osu.Game.Rulesets.Mods;
+using osu.Game.Rulesets.Objects;
+using osu.Game.Rulesets.Objects.Types;
+using osu.Game.Rulesets.Osu.Mods;
+using osu.Game.Rulesets.Osu.Objects;
+using osu.Game.Rulesets.Osu.Objects.Drawables;
+using osu.Game.Tests.Visual;
+using osuTK;
+
+namespace osu.Game.Rulesets.Osu.Tests
+{
+ public partial class TestSceneHitCircleLateFade : OsuTestScene
+ {
+ private float? alphaAtMiss;
+
+ [Test]
+ public void TestHitCircleClassicMod()
+ {
+ AddStep("Create hit circle", () =>
+ {
+ SelectedMods.Value = new Mod[] { new OsuModClassic() };
+ createCircle();
+ });
+
+ AddUntilStep("Wait until circle is missed", () => alphaAtMiss.IsNotNull());
+ AddAssert("Transparent when missed", () => alphaAtMiss == 0);
+ }
+
+ [Test]
+ public void TestHitCircleClassicAndFullHiddenMods()
+ {
+ AddStep("Create hit circle", () =>
+ {
+ SelectedMods.Value = new Mod[] { new OsuModHidden(), new OsuModClassic() };
+ createCircle();
+ });
+
+ AddUntilStep("Wait until circle is missed", () => alphaAtMiss.IsNotNull());
+ AddAssert("Transparent when missed", () => alphaAtMiss == 0);
+ }
+
+ [Test]
+ public void TestHitCircleClassicAndApproachCircleOnlyHiddenMods()
+ {
+ AddStep("Create hit circle", () =>
+ {
+ SelectedMods.Value = new Mod[] { new OsuModHidden { OnlyFadeApproachCircles = { Value = true } }, new OsuModClassic() };
+ createCircle();
+ });
+
+ AddUntilStep("Wait until circle is missed", () => alphaAtMiss.IsNotNull());
+ AddAssert("Transparent when missed", () => alphaAtMiss == 0);
+ }
+
+ [Test]
+ public void TestHitCircleNoMod()
+ {
+ AddStep("Create hit circle", () =>
+ {
+ SelectedMods.Value = Array.Empty();
+ createCircle();
+ });
+
+ AddUntilStep("Wait until circle is missed", () => alphaAtMiss.IsNotNull());
+ AddAssert("Opaque when missed", () => alphaAtMiss == 1);
+ }
+
+ [Test]
+ public void TestSliderClassicMod()
+ {
+ AddStep("Create slider", () =>
+ {
+ SelectedMods.Value = new Mod[] { new OsuModClassic() };
+ createSlider();
+ });
+
+ AddUntilStep("Wait until head circle is missed", () => alphaAtMiss.IsNotNull());
+ AddAssert("Head circle transparent when missed", () => alphaAtMiss == 0);
+ }
+
+ [Test]
+ public void TestSliderNoMod()
+ {
+ AddStep("Create slider", () =>
+ {
+ SelectedMods.Value = Array.Empty();
+ createSlider();
+ });
+
+ AddUntilStep("Wait until head circle is missed", () => alphaAtMiss.IsNotNull());
+ AddAssert("Head circle opaque when missed", () => alphaAtMiss == 1);
+ }
+
+ private void createCircle()
+ {
+ alphaAtMiss = null;
+
+ DrawableHitCircle drawableHitCircle = new DrawableHitCircle(new HitCircle
+ {
+ StartTime = Time.Current + 500,
+ Position = new Vector2(250)
+ });
+
+ foreach (var mod in SelectedMods.Value.OfType())
+ mod.ApplyToDrawableHitObject(drawableHitCircle);
+
+ drawableHitCircle.HitObject.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
+
+ drawableHitCircle.OnNewResult += (_, _) =>
+ {
+ alphaAtMiss = drawableHitCircle.Alpha;
+ };
+
+ Child = drawableHitCircle;
+ }
+
+ private void createSlider()
+ {
+ alphaAtMiss = null;
+
+ DrawableSlider drawableSlider = new DrawableSlider(new Slider
+ {
+ StartTime = Time.Current + 500,
+ Position = new Vector2(250),
+ Path = new SliderPath(PathType.Linear, new[]
+ {
+ Vector2.Zero,
+ new Vector2(0, 100),
+ })
+ });
+
+ drawableSlider.HitObject.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
+
+ drawableSlider.OnLoadComplete += _ =>
+ {
+ foreach (var mod in SelectedMods.Value.OfType())
+ mod.ApplyToDrawableHitObject(drawableSlider.HeadCircle);
+
+ drawableSlider.HeadCircle.OnNewResult += (_, _) =>
+ {
+ alphaAtMiss = drawableSlider.HeadCircle.Alpha;
+ };
+ };
+ Child = drawableSlider;
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuTouchInput.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuTouchInput.cs
new file mode 100644
index 0000000000..72bcec6045
--- /dev/null
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuTouchInput.cs
@@ -0,0 +1,685 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System.Diagnostics;
+using System.Linq;
+using NUnit.Framework;
+using osu.Framework.Allocation;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Shapes;
+using osu.Framework.Input;
+using osu.Framework.Input.Bindings;
+using osu.Framework.Input.Events;
+using osu.Framework.Input.States;
+using osu.Framework.Testing;
+using osu.Framework.Timing;
+using osu.Framework.Utils;
+using osu.Game.Beatmaps;
+using osu.Game.Beatmaps.ControlPoints;
+using osu.Game.Configuration;
+using osu.Game.Rulesets.Osu.Objects;
+using osu.Game.Rulesets.Osu.Objects.Drawables;
+using osu.Game.Rulesets.Osu.UI.Cursor;
+using osu.Game.Screens.Play;
+using osu.Game.Tests.Visual;
+using osuTK;
+using osuTK.Graphics;
+
+namespace osu.Game.Rulesets.Osu.Tests
+{
+ [TestFixture]
+ public partial class TestSceneOsuTouchInput : OsuManualInputManagerTestScene
+ {
+ [Resolved]
+ private OsuConfigManager config { get; set; } = null!;
+
+ private TestActionKeyCounter leftKeyCounter = null!;
+
+ private TestActionKeyCounter rightKeyCounter = null!;
+
+ private OsuInputManager osuInputManager = null!;
+
+ private Container mainContent = null!;
+
+ [SetUpSteps]
+ public void SetUpSteps()
+ {
+ releaseAllTouches();
+
+ AddStep("Create tests", () =>
+ {
+ Children = new Drawable[]
+ {
+ osuInputManager = new OsuInputManager(new OsuRuleset().RulesetInfo)
+ {
+ Child = mainContent = new Container
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Children = new Drawable[]
+ {
+ leftKeyCounter = new TestActionKeyCounter(OsuAction.LeftButton)
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.CentreRight,
+ Depth = float.MinValue,
+ X = -100,
+ },
+ rightKeyCounter = new TestActionKeyCounter(OsuAction.RightButton)
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.CentreLeft,
+ Depth = float.MinValue,
+ X = 100,
+ },
+ new OsuCursorContainer
+ {
+ Depth = float.MinValue,
+ }
+ },
+ }
+ },
+ new TouchVisualiser(),
+ };
+ });
+ }
+
+ [Test]
+ public void TestStreamInputVisual()
+ {
+ addHitCircleAt(TouchSource.Touch1);
+ addHitCircleAt(TouchSource.Touch2);
+
+ beginTouch(TouchSource.Touch1);
+ beginTouch(TouchSource.Touch2);
+
+ endTouch(TouchSource.Touch1);
+
+ int i = 0;
+
+ AddRepeatStep("Alternate", () =>
+ {
+ TouchSource down = i % 2 == 0 ? TouchSource.Touch3 : TouchSource.Touch4;
+ TouchSource up = i % 2 == 0 ? TouchSource.Touch4 : TouchSource.Touch3;
+
+ // sometimes the user will end the previous touch before touching again, sometimes not.
+ if (RNG.NextBool())
+ {
+ InputManager.BeginTouch(new Touch(down, getSanePositionForSource(down)));
+ InputManager.EndTouch(new Touch(up, getSanePositionForSource(up)));
+ }
+ else
+ {
+ InputManager.EndTouch(new Touch(up, getSanePositionForSource(up)));
+ InputManager.BeginTouch(new Touch(down, getSanePositionForSource(down)));
+ }
+
+ i++;
+ }, 100);
+ }
+
+ [Test]
+ public void TestSimpleInput()
+ {
+ beginTouch(TouchSource.Touch1);
+
+ assertKeyCounter(1, 0);
+ checkPressed(OsuAction.LeftButton);
+ checkPosition(TouchSource.Touch1);
+
+ beginTouch(TouchSource.Touch2);
+
+ assertKeyCounter(1, 1);
+ checkPressed(OsuAction.LeftButton);
+ checkPressed(OsuAction.RightButton);
+ checkPosition(TouchSource.Touch2);
+
+ // Subsequent touches should be ignored (except position).
+ beginTouch(TouchSource.Touch3);
+ checkPosition(TouchSource.Touch3);
+
+ beginTouch(TouchSource.Touch4);
+ checkPosition(TouchSource.Touch4);
+
+ assertKeyCounter(1, 1);
+
+ checkPressed(OsuAction.LeftButton);
+ checkPressed(OsuAction.RightButton);
+
+ assertKeyCounter(1, 1);
+ }
+
+ [Test]
+ public void TestPositionalInputUpdatesOnlyFromMostRecentTouch()
+ {
+ beginTouch(TouchSource.Touch1);
+ checkPosition(TouchSource.Touch1);
+
+ beginTouch(TouchSource.Touch2);
+ checkPosition(TouchSource.Touch2);
+
+ beginTouch(TouchSource.Touch1, Vector2.One);
+ checkPosition(TouchSource.Touch2);
+
+ endTouch(TouchSource.Touch2);
+ checkPosition(TouchSource.Touch2);
+
+ // note that touch1 was never ended, but is no longer valid for touch input due to touch 2 occurring.
+ beginTouch(TouchSource.Touch1);
+ checkPosition(TouchSource.Touch2);
+ }
+
+ [Test]
+ public void TestStreamInput()
+ {
+ // In this scenario, the user is tapping on the first object in a stream,
+ // then using one or two fingers in empty space to continue the stream.
+
+ addHitCircleAt(TouchSource.Touch1);
+ beginTouch(TouchSource.Touch1);
+
+ // The first touch is handled as normal.
+ assertKeyCounter(1, 0);
+ checkPressed(OsuAction.LeftButton);
+ checkPosition(TouchSource.Touch1);
+
+ // The second touch should release the first, and also act as a right button.
+ beginTouch(TouchSource.Touch2);
+
+ assertKeyCounter(1, 1);
+ // Importantly, this is different from the simple case because an object was interacted with in the first touch, but not the second touch.
+ // left button is automatically released.
+ checkNotPressed(OsuAction.LeftButton);
+ checkPressed(OsuAction.RightButton);
+ // Also importantly, the positional part of the second touch is ignored.
+ checkPosition(TouchSource.Touch1);
+
+ // In this scenario, a third touch should be allowed, and handled similarly to the second.
+ beginTouch(TouchSource.Touch3);
+
+ assertKeyCounter(2, 1);
+ checkPressed(OsuAction.LeftButton);
+ checkPressed(OsuAction.RightButton);
+ // Position is still ignored.
+ checkPosition(TouchSource.Touch1);
+
+ endTouch(TouchSource.Touch2);
+
+ checkPressed(OsuAction.LeftButton);
+ checkNotPressed(OsuAction.RightButton);
+ // Position is still ignored.
+ checkPosition(TouchSource.Touch1);
+
+ // User continues streaming
+ beginTouch(TouchSource.Touch2);
+
+ assertKeyCounter(2, 2);
+ checkPressed(OsuAction.LeftButton);
+ checkPressed(OsuAction.RightButton);
+ // Position is still ignored.
+ checkPosition(TouchSource.Touch1);
+
+ // In this mode a maximum of three touches should be supported.
+ // A fourth touch should result in no changes anywhere.
+ beginTouch(TouchSource.Touch4);
+ assertKeyCounter(2, 2);
+ checkPressed(OsuAction.LeftButton);
+ checkPressed(OsuAction.RightButton);
+ checkPosition(TouchSource.Touch1);
+ endTouch(TouchSource.Touch4);
+ }
+
+ [Test]
+ public void TestStreamInputWithInitialTouchDownLeft()
+ {
+ // In this scenario, the user is wanting to use stream input but we start with one finger still on the screen.
+ // That finger is mapped to a left action.
+
+ addHitCircleAt(TouchSource.Touch2);
+
+ beginTouch(TouchSource.Touch1);
+ assertKeyCounter(1, 0);
+ checkPressed(OsuAction.LeftButton);
+ checkPosition(TouchSource.Touch1);
+
+ // hits circle as right action
+ beginTouch(TouchSource.Touch2);
+ assertKeyCounter(1, 1);
+ checkPressed(OsuAction.LeftButton);
+ checkPressed(OsuAction.RightButton);
+ checkPosition(TouchSource.Touch2);
+
+ endTouch(TouchSource.Touch1);
+ checkNotPressed(OsuAction.LeftButton);
+
+ // stream using other two fingers while touch2 tracks
+ beginTouch(TouchSource.Touch1);
+ assertKeyCounter(2, 1);
+ checkPressed(OsuAction.LeftButton);
+ // right button is automatically released
+ checkNotPressed(OsuAction.RightButton);
+ checkPosition(TouchSource.Touch2);
+
+ beginTouch(TouchSource.Touch3);
+ assertKeyCounter(2, 2);
+ checkPressed(OsuAction.LeftButton);
+ checkPressed(OsuAction.RightButton);
+ checkPosition(TouchSource.Touch2);
+
+ endTouch(TouchSource.Touch1);
+ checkNotPressed(OsuAction.LeftButton);
+
+ beginTouch(TouchSource.Touch1);
+ assertKeyCounter(3, 2);
+ checkPressed(OsuAction.LeftButton);
+ checkPressed(OsuAction.RightButton);
+ checkPosition(TouchSource.Touch2);
+ }
+
+ [Test]
+ public void TestStreamInputWithInitialTouchDownRight()
+ {
+ // In this scenario, the user is wanting to use stream input but we start with one finger still on the screen.
+ // That finger is mapped to a right action.
+
+ beginTouch(TouchSource.Touch1);
+ beginTouch(TouchSource.Touch2);
+
+ assertKeyCounter(1, 1);
+ checkPressed(OsuAction.LeftButton);
+ checkPressed(OsuAction.RightButton);
+
+ endTouch(TouchSource.Touch1);
+
+ addHitCircleAt(TouchSource.Touch1);
+
+ // hits circle as left action
+ beginTouch(TouchSource.Touch1);
+ assertKeyCounter(2, 1);
+ checkPressed(OsuAction.LeftButton);
+ checkPressed(OsuAction.RightButton);
+ checkPosition(TouchSource.Touch1);
+
+ endTouch(TouchSource.Touch2);
+
+ // stream using other two fingers while touch1 tracks
+ beginTouch(TouchSource.Touch2);
+ assertKeyCounter(2, 2);
+ checkPressed(OsuAction.RightButton);
+ // left button is automatically released
+ checkNotPressed(OsuAction.LeftButton);
+ checkPosition(TouchSource.Touch1);
+
+ beginTouch(TouchSource.Touch3);
+ assertKeyCounter(3, 2);
+ checkPressed(OsuAction.LeftButton);
+ checkPressed(OsuAction.RightButton);
+ checkPosition(TouchSource.Touch1);
+
+ endTouch(TouchSource.Touch2);
+ checkNotPressed(OsuAction.RightButton);
+
+ beginTouch(TouchSource.Touch2);
+ assertKeyCounter(3, 3);
+ checkPressed(OsuAction.LeftButton);
+ checkPressed(OsuAction.RightButton);
+ checkPosition(TouchSource.Touch1);
+ }
+
+ [Test]
+ public void TestNonStreamOverlappingDirectTouchesWithRelease()
+ {
+ // In this scenario, the user is tapping on three circles directly while correctly releasing the first touch.
+ // All three should be recognised.
+
+ addHitCircleAt(TouchSource.Touch1);
+ addHitCircleAt(TouchSource.Touch2);
+ addHitCircleAt(TouchSource.Touch3);
+
+ beginTouch(TouchSource.Touch1);
+ assertKeyCounter(1, 0);
+ checkPressed(OsuAction.LeftButton);
+ checkPosition(TouchSource.Touch1);
+
+ beginTouch(TouchSource.Touch2);
+ assertKeyCounter(1, 1);
+ checkPressed(OsuAction.LeftButton);
+ checkPressed(OsuAction.RightButton);
+ checkPosition(TouchSource.Touch2);
+
+ endTouch(TouchSource.Touch1);
+
+ beginTouch(TouchSource.Touch3);
+ assertKeyCounter(2, 1);
+ checkPressed(OsuAction.LeftButton);
+ checkPressed(OsuAction.RightButton);
+ checkPosition(TouchSource.Touch3);
+ }
+
+ [Test]
+ public void TestNonStreamOverlappingDirectTouchesWithoutRelease()
+ {
+ // In this scenario, the user is tapping on three circles directly without releasing any touches.
+ // The first two should be recognised, but a third should not (as the user already has two fingers down).
+
+ addHitCircleAt(TouchSource.Touch1);
+ addHitCircleAt(TouchSource.Touch2);
+ addHitCircleAt(TouchSource.Touch3);
+
+ beginTouch(TouchSource.Touch1);
+ assertKeyCounter(1, 0);
+ checkPressed(OsuAction.LeftButton);
+ checkPosition(TouchSource.Touch1);
+
+ beginTouch(TouchSource.Touch2);
+ assertKeyCounter(1, 1);
+ checkPressed(OsuAction.LeftButton);
+ checkPressed(OsuAction.RightButton);
+ checkPosition(TouchSource.Touch2);
+
+ beginTouch(TouchSource.Touch3);
+ assertKeyCounter(1, 1);
+ checkPressed(OsuAction.LeftButton);
+ checkPressed(OsuAction.RightButton);
+ checkPosition(TouchSource.Touch3);
+ }
+
+ [Test]
+ public void TestMovementWhileDisallowed()
+ {
+ // aka "autopilot" mod
+
+ AddStep("Disallow gameplay cursor movement", () => osuInputManager.AllowUserCursorMovement = false);
+
+ Vector2? positionBefore = null;
+
+ AddStep("Store cursor position", () => positionBefore = osuInputManager.CurrentState.Mouse.Position);
+ beginTouch(TouchSource.Touch1);
+
+ assertKeyCounter(1, 0);
+ checkPressed(OsuAction.LeftButton);
+ AddAssert("Cursor position unchanged", () => osuInputManager.CurrentState.Mouse.Position, () => Is.EqualTo(positionBefore));
+ }
+
+ [Test]
+ public void TestActionWhileDisallowed()
+ {
+ // aka "relax" mod
+
+ AddStep("Disallow gameplay actions", () => osuInputManager.AllowGameplayInputs = false);
+
+ beginTouch(TouchSource.Touch1);
+
+ assertKeyCounter(0, 0);
+ checkNotPressed(OsuAction.LeftButton);
+ checkPosition(TouchSource.Touch1);
+ }
+
+ [Test]
+ public void TestInputWhileMouseButtonsDisabled()
+ {
+ AddStep("Disable mouse buttons", () => config.SetValue(OsuSetting.MouseDisableButtons, true));
+
+ beginTouch(TouchSource.Touch1);
+
+ assertKeyCounter(0, 0);
+ checkNotPressed(OsuAction.LeftButton);
+ checkPosition(TouchSource.Touch1);
+
+ beginTouch(TouchSource.Touch2);
+
+ assertKeyCounter(0, 0);
+ checkNotPressed(OsuAction.LeftButton);
+ checkNotPressed(OsuAction.RightButton);
+ checkPosition(TouchSource.Touch2);
+ }
+
+ [Test]
+ public void TestAlternatingInput()
+ {
+ beginTouch(TouchSource.Touch1);
+
+ assertKeyCounter(1, 0);
+ checkPressed(OsuAction.LeftButton);
+
+ beginTouch(TouchSource.Touch2);
+
+ assertKeyCounter(1, 1);
+ checkPressed(OsuAction.LeftButton);
+ checkPressed(OsuAction.RightButton);
+
+ for (int i = 0; i < 2; i++)
+ {
+ endTouch(TouchSource.Touch1);
+
+ checkPressed(OsuAction.RightButton);
+ checkNotPressed(OsuAction.LeftButton);
+
+ beginTouch(TouchSource.Touch1);
+
+ checkPressed(OsuAction.LeftButton);
+ checkPressed(OsuAction.RightButton);
+
+ endTouch(TouchSource.Touch2);
+
+ checkPressed(OsuAction.LeftButton);
+ checkNotPressed(OsuAction.RightButton);
+
+ beginTouch(TouchSource.Touch2);
+
+ checkPressed(OsuAction.LeftButton);
+ checkPressed(OsuAction.RightButton);
+ }
+ }
+
+ [Test]
+ public void TestPressReleaseOrder()
+ {
+ beginTouch(TouchSource.Touch1);
+ beginTouch(TouchSource.Touch2);
+ beginTouch(TouchSource.Touch3);
+
+ assertKeyCounter(1, 1);
+ checkPressed(OsuAction.LeftButton);
+ checkPressed(OsuAction.RightButton);
+
+ // Touch 3 was ignored, but let's ensure that if 1 or 2 are released, 3 will be handled a second attempt.
+ endTouch(TouchSource.Touch1);
+
+ assertKeyCounter(1, 1);
+ checkPressed(OsuAction.RightButton);
+
+ endTouch(TouchSource.Touch3);
+
+ assertKeyCounter(1, 1);
+ checkPressed(OsuAction.RightButton);
+
+ beginTouch(TouchSource.Touch3);
+
+ assertKeyCounter(2, 1);
+ checkPressed(OsuAction.LeftButton);
+ checkPressed(OsuAction.RightButton);
+ }
+
+ [Test]
+ public void TestWithDisallowedUserCursor()
+ {
+ beginTouch(TouchSource.Touch1);
+
+ assertKeyCounter(1, 0);
+ checkPressed(OsuAction.LeftButton);
+
+ beginTouch(TouchSource.Touch2);
+
+ assertKeyCounter(1, 1);
+ checkPressed(OsuAction.RightButton);
+
+ // Subsequent touches should be ignored.
+ beginTouch(TouchSource.Touch3);
+ beginTouch(TouchSource.Touch4);
+
+ assertKeyCounter(1, 1);
+
+ checkPressed(OsuAction.LeftButton);
+ checkPressed(OsuAction.RightButton);
+
+ assertKeyCounter(1, 1);
+ }
+
+ private void addHitCircleAt(TouchSource source)
+ {
+ AddStep($"Add circle at {source}", () =>
+ {
+ var hitCircle = new HitCircle();
+
+ hitCircle.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
+
+ mainContent.Add(new DrawableHitCircle(hitCircle)
+ {
+ Clock = new FramedClock(new ManualClock()),
+ Position = mainContent.ToLocalSpace(getSanePositionForSource(source)),
+ });
+ });
+ }
+
+ private void beginTouch(TouchSource source, Vector2? screenSpacePosition = null) =>
+ AddStep($"Begin touch for {source}", () => InputManager.BeginTouch(new Touch(source, screenSpacePosition ??= getSanePositionForSource(source))));
+
+ private void endTouch(TouchSource source, Vector2? screenSpacePosition = null) =>
+ AddStep($"Release touch for {source}", () => InputManager.EndTouch(new Touch(source, screenSpacePosition ??= getSanePositionForSource(source))));
+
+ private Vector2 getSanePositionForSource(TouchSource source)
+ {
+ return new Vector2(
+ osuInputManager.ScreenSpaceDrawQuad.Centre.X + osuInputManager.ScreenSpaceDrawQuad.Width * (-1 + (int)source) / 8,
+ osuInputManager.ScreenSpaceDrawQuad.Centre.Y - 100
+ );
+ }
+
+ private void checkPosition(TouchSource touchSource) =>
+ AddAssert("Cursor position is correct", () => osuInputManager.CurrentState.Mouse.Position, () => Is.EqualTo(getSanePositionForSource(touchSource)));
+
+ private void assertKeyCounter(int left, int right)
+ {
+ AddAssert($"The left key was pressed {left} times", () => leftKeyCounter.CountPresses, () => Is.EqualTo(left));
+ AddAssert($"The right key was pressed {right} times", () => rightKeyCounter.CountPresses, () => Is.EqualTo(right));
+ }
+
+ private void releaseAllTouches()
+ {
+ AddStep("Release all touches", () =>
+ {
+ config.SetValue(OsuSetting.MouseDisableButtons, false);
+ foreach (TouchSource source in InputManager.CurrentState.Touch.ActiveSources)
+ InputManager.EndTouch(new Touch(source, osuInputManager.ScreenSpaceDrawQuad.Centre));
+ });
+ }
+
+ private void checkNotPressed(OsuAction action) => AddAssert($"Not pressing {action}", () => !osuInputManager.PressedActions.Contains(action));
+ private void checkPressed(OsuAction action) => AddAssert($"Is pressing {action}", () => osuInputManager.PressedActions.Contains(action));
+
+ public partial class TestActionKeyCounter : KeyCounter, IKeyBindingHandler
+ {
+ public OsuAction Action { get; }
+
+ public TestActionKeyCounter(OsuAction action)
+ : base(action.ToString())
+ {
+ Action = action;
+ }
+
+ public bool OnPressed(KeyBindingPressEvent e)
+ {
+ if (e.Action == Action)
+ {
+ IsLit = true;
+ Increment();
+ }
+
+ return false;
+ }
+
+ public void OnReleased(KeyBindingReleaseEvent e)
+ {
+ if (e.Action == Action) IsLit = false;
+ }
+ }
+
+ public partial class TouchVisualiser : CompositeDrawable
+ {
+ private readonly Drawable?[] drawableTouches = new Drawable?[TouchState.MAX_TOUCH_COUNT];
+
+ public TouchVisualiser()
+ {
+ RelativeSizeAxes = Axes.Both;
+ }
+
+ public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true;
+
+ protected override bool OnTouchDown(TouchDownEvent e)
+ {
+ if (IsDisposed)
+ return false;
+
+ var circle = new Circle
+ {
+ Alpha = 0.5f,
+ Origin = Anchor.Centre,
+ Size = new Vector2(20),
+ Position = e.Touch.Position,
+ Colour = colourFor(e.Touch.Source),
+ };
+
+ AddInternal(circle);
+ drawableTouches[(int)e.Touch.Source] = circle;
+ return false;
+ }
+
+ protected override void OnTouchMove(TouchMoveEvent e)
+ {
+ if (IsDisposed)
+ return;
+
+ var circle = drawableTouches[(int)e.Touch.Source];
+
+ Debug.Assert(circle != null);
+
+ AddInternal(new FadingCircle(circle));
+ circle.Position = e.Touch.Position;
+ }
+
+ protected override void OnTouchUp(TouchUpEvent e)
+ {
+ var circle = drawableTouches[(int)e.Touch.Source];
+
+ Debug.Assert(circle != null);
+
+ circle.FadeOut(200, Easing.OutQuint).Expire();
+ drawableTouches[(int)e.Touch.Source] = null;
+ }
+
+ private Color4 colourFor(TouchSource source)
+ {
+ return Color4.FromHsv(new Vector4((float)source / TouchState.MAX_TOUCH_COUNT, 1f, 1f, 1f));
+ }
+
+ private partial class FadingCircle : Circle
+ {
+ public FadingCircle(Drawable source)
+ {
+ Origin = Anchor.Centre;
+ Size = source.Size;
+ Position = source.Position;
+ Colour = source.Colour;
+ }
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+ this.FadeOut(200).Expire();
+ }
+ }
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs
index 32d0cc8939..1e9f931b74 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs
@@ -160,9 +160,9 @@ namespace osu.Game.Rulesets.Osu.Tests
static bool assertSamples(HitObject hitObject) => hitObject.Samples.All(s => s.Name != HitSampleInfo.HIT_CLAP && s.Name != HitSampleInfo.HIT_WHISTLE);
}
- private Drawable testSimpleBig(int repeats = 0) => createSlider(2, repeats: repeats);
+ private Drawable testSimpleBig(int repeats = 0) => createSlider(repeats: repeats);
- private Drawable testSimpleBigLargeStackOffset(int repeats = 0) => createSlider(2, repeats: repeats, stackHeight: 10);
+ private Drawable testSimpleBigLargeStackOffset(int repeats = 0) => createSlider(repeats: repeats, stackHeight: 10);
private Drawable testDistanceOverflow(int repeats = 0)
{
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderFollowCircleInput.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderFollowCircleInput.cs
index 0af0ff5604..a32f0a13b8 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderFollowCircleInput.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderFollowCircleInput.cs
@@ -31,10 +31,8 @@ namespace osu.Game.Rulesets.Osu.Tests
[Test]
public void TestMaximumDistanceTrackingWithoutMovement(
- [Values(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10)]
- float circleSize,
- [Values(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10)]
- double velocity)
+ [Values(0, 5, 10)] float circleSize,
+ [Values(0, 5, 10)] double velocity)
{
const double time_slider_start = 1000;
diff --git a/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj b/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj
index c10c3ffb15..57900bffd7 100644
--- a/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj
+++ b/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj
@@ -1,10 +1,10 @@
-
-
+
+
-
+
WinExe
diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointConnectionPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointConnectionPiece.cs
index 28e0d650c4..67685d21a7 100644
--- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointConnectionPiece.cs
+++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointConnectionPiece.cs
@@ -8,34 +8,36 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Lines;
using osu.Game.Rulesets.Objects;
+using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Osu.Objects;
using osuTK;
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
{
///
- /// A visualisation of the line between two s.
+ /// A visualisation of the line between two s.
///
- public partial class PathControlPointConnectionPiece : CompositeDrawable
+ /// The type of which this visualises.
+ public partial class PathControlPointConnectionPiece : CompositeDrawable where T : OsuHitObject, IHasPath
{
public readonly PathControlPoint ControlPoint;
private readonly Path path;
- private readonly Slider slider;
+ private readonly T hitObject;
public int ControlPointIndex { get; set; }
- private IBindable sliderPosition;
+ private IBindable hitObjectPosition;
private IBindable pathVersion;
- public PathControlPointConnectionPiece(Slider slider, int controlPointIndex)
+ public PathControlPointConnectionPiece(T hitObject, int controlPointIndex)
{
- this.slider = slider;
+ this.hitObject = hitObject;
ControlPointIndex = controlPointIndex;
Origin = Anchor.Centre;
AutoSizeAxes = Axes.Both;
- ControlPoint = slider.Path.ControlPoints[controlPointIndex];
+ ControlPoint = hitObject.Path.ControlPoints[controlPointIndex];
InternalChild = path = new SmoothPath
{
@@ -48,10 +50,10 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
{
base.LoadComplete();
- sliderPosition = slider.PositionBindable.GetBoundCopy();
- sliderPosition.BindValueChanged(_ => updateConnectingPath());
+ hitObjectPosition = hitObject.PositionBindable.GetBoundCopy();
+ hitObjectPosition.BindValueChanged(_ => updateConnectingPath());
- pathVersion = slider.Path.Version.GetBoundCopy();
+ pathVersion = hitObject.Path.Version.GetBoundCopy();
pathVersion.BindValueChanged(_ => updateConnectingPath());
updateConnectingPath();
@@ -62,16 +64,16 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
///
private void updateConnectingPath()
{
- Position = slider.StackedPosition + ControlPoint.Position;
+ Position = hitObject.StackedPosition + ControlPoint.Position;
path.ClearVertices();
int nextIndex = ControlPointIndex + 1;
- if (nextIndex == 0 || nextIndex >= slider.Path.ControlPoints.Count)
+ if (nextIndex == 0 || nextIndex >= hitObject.Path.ControlPoints.Count)
return;
path.AddVertex(Vector2.Zero);
- path.AddVertex(slider.Path.ControlPoints[nextIndex].Position - ControlPoint.Position);
+ path.AddVertex(hitObject.Path.ControlPoints[nextIndex].Position - ControlPoint.Position);
path.OriginPosition = path.PositionInBoundingBox(Vector2.Zero);
}
diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs
index d83f35d13f..12e5ca0236 100644
--- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs
+++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs
@@ -29,11 +29,13 @@ using osuTK.Input;
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
{
///
- /// A visualisation of a single in a .
+ /// A visualisation of a single in an osu hit object with a path.
///
- public partial class PathControlPointPiece : BlueprintPiece, IHasTooltip
+ /// The type of which this visualises.
+ public partial class PathControlPointPiece : BlueprintPiece, IHasTooltip
+ where T : OsuHitObject, IHasPath
{
- public Action RequestSelection;
+ public Action, MouseButtonEvent> RequestSelection;
public Action DragStarted;
public Action DragInProgress;
@@ -44,34 +46,34 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
public readonly BindableBool IsSelected = new BindableBool();
public readonly PathControlPoint ControlPoint;
- private readonly Slider slider;
+ private readonly T hitObject;
private readonly Container marker;
private readonly Drawable markerRing;
[Resolved]
private OsuColour colours { get; set; }
- private IBindable sliderPosition;
- private IBindable sliderScale;
+ private IBindable hitObjectPosition;
+ private IBindable hitObjectScale;
[UsedImplicitly]
- private readonly IBindable sliderVersion;
+ private readonly IBindable hitObjectVersion;
- public PathControlPointPiece(Slider slider, PathControlPoint controlPoint)
+ public PathControlPointPiece(T hitObject, PathControlPoint controlPoint)
{
- this.slider = slider;
+ this.hitObject = hitObject;
ControlPoint = controlPoint;
- // we don't want to run the path type update on construction as it may inadvertently change the slider.
- cachePoints(slider);
+ // we don't want to run the path type update on construction as it may inadvertently change the hit object.
+ cachePoints(hitObject);
- sliderVersion = slider.Path.Version.GetBoundCopy();
+ hitObjectVersion = hitObject.Path.Version.GetBoundCopy();
// schedule ensure that updates are only applied after all operations from a single frame are applied.
- // this avoids inadvertently changing the slider path type for batch operations.
- sliderVersion.BindValueChanged(_ => Scheduler.AddOnce(() =>
+ // this avoids inadvertently changing the hit object path type for batch operations.
+ hitObjectVersion.BindValueChanged(_ => Scheduler.AddOnce(() =>
{
- cachePoints(slider);
+ cachePoints(hitObject);
updatePathType();
}));
@@ -120,11 +122,11 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
{
base.LoadComplete();
- sliderPosition = slider.PositionBindable.GetBoundCopy();
- sliderPosition.BindValueChanged(_ => updateMarkerDisplay());
+ hitObjectPosition = hitObject.PositionBindable.GetBoundCopy();
+ hitObjectPosition.BindValueChanged(_ => updateMarkerDisplay());
- sliderScale = slider.ScaleBindable.GetBoundCopy();
- sliderScale.BindValueChanged(_ => updateMarkerDisplay());
+ hitObjectScale = hitObject.ScaleBindable.GetBoundCopy();
+ hitObjectScale.BindValueChanged(_ => updateMarkerDisplay());
IsSelected.BindValueChanged(_ => updateMarkerDisplay());
@@ -212,7 +214,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
protected override void OnDragEnd(DragEndEvent e) => DragEnded?.Invoke();
- private void cachePoints(Slider slider) => PointsInSegment = slider.Path.PointsInSegment(ControlPoint);
+ private void cachePoints(T hitObject) => PointsInSegment = hitObject.Path.PointsInSegment(ControlPoint);
///
/// Handles correction of invalid path types.
@@ -239,7 +241,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
///
private void updateMarkerDisplay()
{
- Position = slider.StackedPosition + ControlPoint.Position;
+ Position = hitObject.StackedPosition + ControlPoint.Position;
markerRing.Alpha = IsSelected.Value ? 1 : 0;
@@ -249,7 +251,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
colour = colour.Lighten(1);
marker.Colour = colour;
- marker.Scale = new Vector2(slider.Scale);
+ marker.Scale = new Vector2(hitObject.Scale);
}
private Color4 getColourFromNodeType()
diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs
index 3a175888d9..17d0fc457a 100644
--- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs
+++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs
@@ -29,15 +29,16 @@ using osuTK.Input;
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
{
- public partial class PathControlPointVisualiser : CompositeDrawable, IKeyBindingHandler, IHasContextMenu
+ public partial class PathControlPointVisualiser : CompositeDrawable, IKeyBindingHandler, IHasContextMenu
+ where T : OsuHitObject, IHasPath
{
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; // allow context menu to appear outside of the playfield.
- internal readonly Container Pieces;
- internal readonly Container Connections;
+ internal readonly Container> Pieces;
+ internal readonly Container> Connections;
private readonly IBindableList controlPoints = new BindableList();
- private readonly Slider slider;
+ private readonly T hitObject;
private readonly bool allowSelection;
private InputManager inputManager;
@@ -48,17 +49,17 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
[Resolved(CanBeNull = true)]
private IDistanceSnapProvider snapProvider { get; set; }
- public PathControlPointVisualiser(Slider slider, bool allowSelection)
+ public PathControlPointVisualiser(T hitObject, bool allowSelection)
{
- this.slider = slider;
+ this.hitObject = hitObject;
this.allowSelection = allowSelection;
RelativeSizeAxes = Axes.Both;
InternalChildren = new Drawable[]
{
- Connections = new Container { RelativeSizeAxes = Axes.Both },
- Pieces = new Container { RelativeSizeAxes = Axes.Both }
+ Connections = new Container> { RelativeSizeAxes = Axes.Both },
+ Pieces = new Container> { RelativeSizeAxes = Axes.Both }
};
}
@@ -69,12 +70,12 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
inputManager = GetContainingInputManager();
controlPoints.CollectionChanged += onControlPointsChanged;
- controlPoints.BindTo(slider.Path.ControlPoints);
+ controlPoints.BindTo(hitObject.Path.ControlPoints);
}
///
- /// Selects the corresponding to the given ,
- /// and deselects all other s.
+ /// Selects the corresponding to the given ,
+ /// and deselects all other s.
///
public void SetSelectionTo(PathControlPoint pathControlPoint)
{
@@ -124,8 +125,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
return true;
}
- private bool isSplittable(PathControlPointPiece p) =>
- // A slider can only be split on control points which connect two different slider segments.
+ private bool isSplittable(PathControlPointPiece p) =>
+ // A hit object can only be split on control points which connect two different path segments.
p.ControlPoint.Type.HasValue && p != Pieces.FirstOrDefault() && p != Pieces.LastOrDefault();
private void onControlPointsChanged(object sender, NotifyCollectionChangedEventArgs e)
@@ -133,6 +134,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
+ Debug.Assert(e.NewItems != null);
+
// If inserting in the path (not appending),
// update indices of existing connections after insert location
if (e.NewStartingIndex < Pieces.Count)
@@ -148,7 +151,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
{
var point = (PathControlPoint)e.NewItems[i];
- Pieces.Add(new PathControlPointPiece(slider, point).With(d =>
+ Pieces.Add(new PathControlPointPiece(hitObject, point).With(d =>
{
if (allowSelection)
d.RequestSelection = selectionRequested;
@@ -158,12 +161,14 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
d.DragEnded = dragEnded;
}));
- Connections.Add(new PathControlPointConnectionPiece(slider, e.NewStartingIndex + i));
+ Connections.Add(new PathControlPointConnectionPiece(hitObject, e.NewStartingIndex + i));
}
break;
case NotifyCollectionChangedAction.Remove:
+ Debug.Assert(e.OldItems != null);
+
foreach (var point in e.OldItems.Cast())
{
foreach (var piece in Pieces.Where(p => p.ControlPoint == point).ToArray())
@@ -215,7 +220,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
{
}
- private void selectionRequested(PathControlPointPiece piece, MouseButtonEvent e)
+ private void selectionRequested(PathControlPointPiece piece, MouseButtonEvent e)
{
if (e.Button == MouseButton.Left && inputManager.CurrentState.Keyboard.ControlPressed)
piece.IsSelected.Toggle();
@@ -230,7 +235,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
///
/// The control point piece that we want to change the path type of.
/// The path type we want to assign to the given control point piece.
- private void updatePathType(PathControlPointPiece piece, PathType? type)
+ private void updatePathType(PathControlPointPiece piece, PathType? type)
{
int indexInSegment = piece.PointsInSegment.IndexOf(piece.ControlPoint);
@@ -248,7 +253,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
break;
}
- slider.Path.ExpectedDistance.Value = null;
+ hitObject.Path.ExpectedDistance.Value = null;
piece.ControlPoint.Type = type;
}
@@ -264,9 +269,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
private void dragStarted(PathControlPoint controlPoint)
{
- dragStartPositions = slider.Path.ControlPoints.Select(point => point.Position).ToArray();
- dragPathTypes = slider.Path.ControlPoints.Select(point => point.Type).ToArray();
- draggedControlPointIndex = slider.Path.ControlPoints.IndexOf(controlPoint);
+ dragStartPositions = hitObject.Path.ControlPoints.Select(point => point.Position).ToArray();
+ dragPathTypes = hitObject.Path.ControlPoints.Select(point => point.Type).ToArray();
+ draggedControlPointIndex = hitObject.Path.ControlPoints.IndexOf(controlPoint);
selectedControlPoints = new HashSet(Pieces.Where(piece => piece.IsSelected.Value).Select(piece => piece.ControlPoint));
Debug.Assert(draggedControlPointIndex >= 0);
@@ -276,25 +281,25 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
private void dragInProgress(DragEvent e)
{
- Vector2[] oldControlPoints = slider.Path.ControlPoints.Select(cp => cp.Position).ToArray();
- var oldPosition = slider.Position;
- double oldStartTime = slider.StartTime;
+ Vector2[] oldControlPoints = hitObject.Path.ControlPoints.Select(cp => cp.Position).ToArray();
+ var oldPosition = hitObject.Position;
+ double oldStartTime = hitObject.StartTime;
- if (selectedControlPoints.Contains(slider.Path.ControlPoints[0]))
+ if (selectedControlPoints.Contains(hitObject.Path.ControlPoints[0]))
{
- // Special handling for selections containing head control point - the position of the slider changes which means the snapped position and time have to be taken into account
+ // Special handling for selections containing head control point - the position of the hit object changes which means the snapped position and time have to be taken into account
Vector2 newHeadPosition = Parent.ToScreenSpace(e.MousePosition + (dragStartPositions[0] - dragStartPositions[draggedControlPointIndex]));
var result = snapProvider?.FindSnappedPositionAndTime(newHeadPosition);
- Vector2 movementDelta = Parent.ToLocalSpace(result?.ScreenSpacePosition ?? newHeadPosition) - slider.Position;
+ Vector2 movementDelta = Parent.ToLocalSpace(result?.ScreenSpacePosition ?? newHeadPosition) - hitObject.Position;
- slider.Position += movementDelta;
- slider.StartTime = result?.Time ?? slider.StartTime;
+ hitObject.Position += movementDelta;
+ hitObject.StartTime = result?.Time ?? hitObject.StartTime;
- for (int i = 1; i < slider.Path.ControlPoints.Count; i++)
+ for (int i = 1; i < hitObject.Path.ControlPoints.Count; i++)
{
- var controlPoint = slider.Path.ControlPoints[i];
- // Since control points are relative to the position of the slider, all points that are _not_ selected
+ var controlPoint = hitObject.Path.ControlPoints[i];
+ // Since control points are relative to the position of the hit object, all points that are _not_ selected
// need to be offset _back_ by the delta corresponding to the movement of the head point.
// All other selected control points (if any) will move together with the head point
// (and so they will not move at all, relative to each other).
@@ -306,7 +311,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
{
var result = snapProvider?.FindSnappedPositionAndTime(Parent.ToScreenSpace(e.MousePosition));
- Vector2 movementDelta = Parent.ToLocalSpace(result?.ScreenSpacePosition ?? Parent.ToScreenSpace(e.MousePosition)) - dragStartPositions[draggedControlPointIndex] - slider.Position;
+ Vector2 movementDelta = Parent.ToLocalSpace(result?.ScreenSpacePosition ?? Parent.ToScreenSpace(e.MousePosition)) - dragStartPositions[draggedControlPointIndex] - hitObject.Position;
for (int i = 0; i < controlPoints.Count; ++i)
{
@@ -317,23 +322,23 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
}
// Snap the path to the current beat divisor before checking length validity.
- slider.SnapTo(snapProvider);
+ hitObject.SnapTo(snapProvider);
- if (!slider.Path.HasValidLength)
+ if (!hitObject.Path.HasValidLength)
{
- for (int i = 0; i < slider.Path.ControlPoints.Count; i++)
- slider.Path.ControlPoints[i].Position = oldControlPoints[i];
+ for (int i = 0; i < hitObject.Path.ControlPoints.Count; i++)
+ hitObject.Path.ControlPoints[i].Position = oldControlPoints[i];
- slider.Position = oldPosition;
- slider.StartTime = oldStartTime;
+ hitObject.Position = oldPosition;
+ hitObject.StartTime = oldStartTime;
// Snap the path length again to undo the invalid length.
- slider.SnapTo(snapProvider);
+ hitObject.SnapTo(snapProvider);
return;
}
// Maintain the path types in case they got defaulted to bezier at some point during the drag.
- for (int i = 0; i < slider.Path.ControlPoints.Count; i++)
- slider.Path.ControlPoints[i].Type = dragPathTypes[i];
+ for (int i = 0; i < hitObject.Path.ControlPoints.Count; i++)
+ hitObject.Path.ControlPoints[i].Type = dragPathTypes[i];
}
private void dragEnded() => changeHandler?.EndChange();
diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderBodyPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderBodyPiece.cs
index ecd840dda6..68a44eb2f8 100644
--- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderBodyPiece.cs
+++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderBodyPiece.cs
@@ -22,6 +22,11 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
///
public Vector2 PathStartLocation => body.PathOffset;
+ ///
+ /// Offset in absolute (local) coordinates from the end of the curve.
+ ///
+ public Vector2 PathEndLocation => body.PathEndOffset;
+
public SliderBodyPiece()
{
InternalChild = body = new ManualSliderBody
diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs
index f91d35e2e1..77393efeb3 100644
--- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs
+++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs
@@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
private SliderBodyPiece bodyPiece;
private HitCirclePiece headCirclePiece;
private HitCirclePiece tailCirclePiece;
- private PathControlPointVisualiser controlPointVisualiser;
+ private PathControlPointVisualiser controlPointVisualiser;
private InputManager inputManager;
@@ -59,7 +59,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
bodyPiece = new SliderBodyPiece(),
headCirclePiece = new HitCirclePiece(),
tailCirclePiece = new HitCirclePiece(),
- controlPointVisualiser = new PathControlPointVisualiser(HitObject, false)
+ controlPointVisualiser = new PathControlPointVisualiser(HitObject, false)
};
setState(SliderPlacementState.Initial);
diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs
index a51c223785..e444287b73 100644
--- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs
+++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs
@@ -38,7 +38,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
protected SliderCircleOverlay TailOverlay { get; private set; }
[CanBeNull]
- protected PathControlPointVisualiser ControlPointVisualiser { get; private set; }
+ protected PathControlPointVisualiser ControlPointVisualiser { get; private set; }
[Resolved(CanBeNull = true)]
private IDistanceSnapProvider snapProvider { get; set; }
@@ -147,7 +147,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
{
if (ControlPointVisualiser == null)
{
- AddInternal(ControlPointVisualiser = new PathControlPointVisualiser(HitObject, true)
+ AddInternal(ControlPointVisualiser = new PathControlPointVisualiser(HitObject, true)
{
RemoveControlPointsRequested = removeControlPoints,
SplitControlPointsRequested = splitControlPoints
@@ -409,6 +409,11 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
public override Vector2 ScreenSpaceSelectionPoint => DrawableObject.SliderBody?.ToScreenSpace(DrawableObject.SliderBody.PathOffset)
?? BodyPiece.ToScreenSpace(BodyPiece.PathStartLocation);
+ protected override Vector2[] ScreenSpaceAdditionalNodes => new[]
+ {
+ DrawableObject.SliderBody?.ToScreenSpace(DrawableObject.SliderBody.PathEndOffset) ?? BodyPiece.ToScreenSpace(BodyPiece.PathEndLocation)
+ };
+
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) =>
BodyPiece.ReceivePositionalInputAt(screenSpacePos) || ControlPointVisualiser?.Pieces.Any(p => p.ReceivePositionalInputAt(screenSpacePos)) == true;
diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Spinners/SpinnerPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Spinners/SpinnerPlacementBlueprint.cs
index e412c47c09..73ee5df9dc 100644
--- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Spinners/SpinnerPlacementBlueprint.cs
+++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Spinners/SpinnerPlacementBlueprint.cs
@@ -4,6 +4,8 @@
#nullable disable
using System;
+using JetBrains.Annotations;
+using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Input.Events;
using osu.Game.Rulesets.Edit;
@@ -22,6 +24,10 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners
private bool isPlacingEnd;
+ [Resolved(CanBeNull = true)]
+ [CanBeNull]
+ private IBeatSnapProvider beatSnapProvider { get; set; }
+
public SpinnerPlacementBlueprint()
: base(new Spinner { Position = OsuPlayfield.BASE_SIZE / 2 })
{
@@ -33,7 +39,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners
base.Update();
if (isPlacingEnd)
- HitObject.EndTime = Math.Max(HitObject.StartTime, EditorClock.CurrentTime);
+ updateEndTimeFromCurrent();
piece.UpdateFrom(HitObject);
}
@@ -45,7 +51,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners
if (e.Button != MouseButton.Right)
return false;
- HitObject.EndTime = EditorClock.CurrentTime;
+ updateEndTimeFromCurrent();
EndPlacement(true);
}
else
@@ -61,5 +67,12 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners
return true;
}
+
+ private void updateEndTimeFromCurrent()
+ {
+ HitObject.EndTime = beatSnapProvider == null
+ ? Math.Max(HitObject.StartTime, EditorClock.CurrentTime)
+ : Math.Max(HitObject.StartTime + beatSnapProvider.GetBeatLengthAtTime(HitObject.StartTime), beatSnapProvider.SnapTime(EditorClock.CurrentTime));
+ }
}
}
diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs
index 09ddc420a7..7a70257f3a 100644
--- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs
+++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs
@@ -187,28 +187,19 @@ namespace osu.Game.Rulesets.Osu.Edit
if (b.IsSelected)
continue;
- var hitObject = (OsuHitObject)b.Item;
+ var snapPositions = b.ScreenSpaceSnapPoints;
- Vector2? snap = checkSnap(hitObject.Position);
- if (snap == null && hitObject.Position != hitObject.EndPosition)
- snap = checkSnap(hitObject.EndPosition);
+ if (!snapPositions.Any())
+ continue;
- if (snap != null)
+ var closestSnapPosition = snapPositions.MinBy(p => Vector2.Distance(p, screenSpacePosition));
+
+ if (Vector2.Distance(closestSnapPosition, screenSpacePosition) < snapRadius)
{
// only return distance portion, since time is not really valid
- snapResult = new SnapResult(snap.Value, null, playfield);
+ snapResult = new SnapResult(closestSnapPosition, null, playfield);
return true;
}
-
- Vector2? checkSnap(Vector2 checkPos)
- {
- Vector2 checkScreenPos = playfield.GamefieldToScreenSpace(checkPos);
-
- if (Vector2.Distance(checkScreenPos, screenSpacePosition) < snapRadius)
- return checkScreenPos;
-
- return null;
- }
}
snapResult = null;
diff --git a/osu.Game.Rulesets.Osu/Mods/InputBlockingMod.cs b/osu.Game.Rulesets.Osu/Mods/InputBlockingMod.cs
index 7db4e2625b..b56fdbdf74 100644
--- a/osu.Game.Rulesets.Osu/Mods/InputBlockingMod.cs
+++ b/osu.Game.Rulesets.Osu/Mods/InputBlockingMod.cs
@@ -11,6 +11,7 @@ using osu.Game.Beatmaps.Timing;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Osu.Objects;
+using osu.Game.Rulesets.Osu.UI;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.UI;
using osu.Game.Screens.Play;
@@ -26,7 +27,7 @@ namespace osu.Game.Rulesets.Osu.Mods
private const double flash_duration = 1000;
- private DrawableRuleset ruleset = null!;
+ private DrawableOsuRuleset ruleset = null!;
protected OsuAction? LastAcceptedAction { get; private set; }
@@ -42,8 +43,8 @@ namespace osu.Game.Rulesets.Osu.Mods
public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset)
{
- ruleset = drawableRuleset;
- drawableRuleset.KeyBindingInputManager.Add(new InputInterceptor(this));
+ ruleset = (DrawableOsuRuleset)drawableRuleset;
+ ruleset.KeyBindingInputManager.Add(new InputInterceptor(this));
var periods = new List();
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAccuracyChallenge.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAccuracyChallenge.cs
new file mode 100644
index 0000000000..5b79753632
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModAccuracyChallenge.cs
@@ -0,0 +1,14 @@
+// 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.Linq;
+using osu.Game.Rulesets.Mods;
+
+namespace osu.Game.Rulesets.Osu.Mods
+{
+ public class OsuModAccuracyChallenge : ModAccuracyChallenge
+ {
+ public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(OsuModAutopilot)).ToArray();
+ }
+}
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs
index 6772cfe0be..4782a0e49c 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs
@@ -11,6 +11,7 @@ using osu.Game.Graphics;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Replays;
+using osu.Game.Rulesets.Osu.UI;
using osu.Game.Rulesets.UI;
namespace osu.Game.Rulesets.Osu.Mods
@@ -55,11 +56,13 @@ namespace osu.Game.Rulesets.Osu.Mods
public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset)
{
// Grab the input manager to disable the user's cursor, and for future use
- inputManager = (OsuInputManager)drawableRuleset.KeyBindingInputManager;
+ inputManager = ((DrawableOsuRuleset)drawableRuleset).KeyBindingInputManager;
inputManager.AllowUserCursorMovement = false;
// Generate the replay frames the cursor should follow
replayFrames = new OsuAutoGenerator(drawableRuleset.Beatmap, drawableRuleset.Mods).Generate().Frames.Cast().ToList();
+
+ drawableRuleset.UseResumeOverlay = false;
}
}
}
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs b/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs
index e021992f86..250d97c537 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs
@@ -4,6 +4,7 @@
using System;
using System.Linq;
using osu.Framework.Bindables;
+using osu.Framework.Graphics;
using osu.Game.Configuration;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects;
@@ -11,6 +12,7 @@ using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables;
using osu.Game.Rulesets.Osu.UI;
+using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.UI;
namespace osu.Game.Rulesets.Osu.Mods
@@ -31,6 +33,11 @@ namespace osu.Game.Rulesets.Osu.Mods
[SettingSource("Always play a slider's tail sample", "Always plays a slider's tail sample regardless of whether it was hit or not.")]
public Bindable AlwaysPlayTailSample { get; } = new BindableBool(true);
+ [SettingSource("Fade out hit circles earlier", "Make hit circles fade out into a miss, rather than after it.")]
+ public Bindable FadeHitCircleEarly { get; } = new Bindable(true);
+
+ private bool usingHiddenFading;
+
public void ApplyToHitObject(HitObject hitObject)
{
switch (hitObject)
@@ -51,6 +58,8 @@ namespace osu.Game.Rulesets.Osu.Mods
if (ClassicNoteLock.Value)
osuRuleset.Playfield.HitPolicy = new ObjectOrderedHitPolicy();
+
+ usingHiddenFading = drawableRuleset.Mods.OfType().SingleOrDefault()?.OnlyFadeApproachCircles.Value == false;
}
public void ApplyToDrawableHitObject(DrawableHitObject obj)
@@ -59,12 +68,32 @@ namespace osu.Game.Rulesets.Osu.Mods
{
case DrawableSliderHead head:
head.TrackFollowCircle = !NoSliderHeadMovement.Value;
+ if (FadeHitCircleEarly.Value && !usingHiddenFading)
+ applyEarlyFading(head);
break;
case DrawableSliderTail tail:
tail.SamplePlaysOnlyOnHit = !AlwaysPlayTailSample.Value;
break;
+
+ case DrawableHitCircle circle:
+ if (FadeHitCircleEarly.Value && !usingHiddenFading)
+ applyEarlyFading(circle);
+ break;
}
}
+
+ private void applyEarlyFading(DrawableHitCircle circle)
+ {
+ circle.ApplyCustomUpdateState += (o, _) =>
+ {
+ using (o.BeginAbsoluteSequence(o.StateUpdateTime))
+ {
+ double okWindow = o.HitObject.HitWindows.WindowFor(HitResult.Ok);
+ double lateMissFadeTime = o.HitObject.HitWindows.WindowFor(HitResult.Meh) - okWindow;
+ o.Delay(okWindow).FadeOut(lateMissFadeTime);
+ }
+ };
+ }
}
}
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModHardRock.cs b/osu.Game.Rulesets.Osu/Mods/OsuModHardRock.cs
index 5430929143..19d4a1bf83 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModHardRock.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModHardRock.cs
@@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Osu.Mods
{
var osuObject = (OsuHitObject)hitObject;
- OsuHitObjectGenerationUtils.ReflectVertically(osuObject);
+ OsuHitObjectGenerationUtils.ReflectVerticallyAlongPlayfield(osuObject);
}
}
}
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModMirror.cs b/osu.Game.Rulesets.Osu/Mods/OsuModMirror.cs
index 0a54d58718..6d01808fb5 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModMirror.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModMirror.cs
@@ -27,16 +27,16 @@ namespace osu.Game.Rulesets.Osu.Mods
switch (Reflection.Value)
{
case MirrorType.Horizontal:
- OsuHitObjectGenerationUtils.ReflectHorizontally(osuObject);
+ OsuHitObjectGenerationUtils.ReflectHorizontallyAlongPlayfield(osuObject);
break;
case MirrorType.Vertical:
- OsuHitObjectGenerationUtils.ReflectVertically(osuObject);
+ OsuHitObjectGenerationUtils.ReflectVerticallyAlongPlayfield(osuObject);
break;
case MirrorType.Both:
- OsuHitObjectGenerationUtils.ReflectHorizontally(osuObject);
- OsuHitObjectGenerationUtils.ReflectVertically(osuObject);
+ OsuHitObjectGenerationUtils.ReflectHorizontallyAlongPlayfield(osuObject);
+ OsuHitObjectGenerationUtils.ReflectVerticallyAlongPlayfield(osuObject);
break;
}
}
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs
index 1621bb50b1..307d731fd4 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs
@@ -65,6 +65,11 @@ namespace osu.Game.Rulesets.Osu.Mods
flowDirection = !flowDirection;
}
+ if (positionInfos[i].HitObject is Slider slider && random.NextDouble() < 0.5)
+ {
+ OsuHitObjectGenerationUtils.FlipSliderInPlaceHorizontally(slider);
+ }
+
if (i == 0)
{
positionInfos[i].DistanceFromPrevious = (float)(random.NextDouble() * OsuPlayfield.BASE_SIZE.Y / 2);
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs
index 753de6231a..32ffb545e0 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs
@@ -10,6 +10,7 @@ using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables;
+using osu.Game.Rulesets.Osu.UI;
using osu.Game.Rulesets.Replays;
using osu.Game.Rulesets.UI;
using osu.Game.Screens.Play;
@@ -42,7 +43,7 @@ namespace osu.Game.Rulesets.Osu.Mods
public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset)
{
// grab the input manager for future use.
- osuInputManager = (OsuInputManager)drawableRuleset.KeyBindingInputManager;
+ osuInputManager = ((DrawableOsuRuleset)drawableRuleset).KeyBindingInputManager;
}
public void ApplyToPlayer(Player player)
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTargetPractice.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTargetPractice.cs
index 55c20eebe9..77cf340b95 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModTargetPractice.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModTargetPractice.cs
@@ -17,7 +17,6 @@ using osu.Game.Configuration;
using osu.Game.Graphics;
using osu.Game.Overlays.Settings;
using osu.Game.Rulesets.Mods;
-using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Osu.Beatmaps;
@@ -196,8 +195,8 @@ namespace osu.Game.Rulesets.Osu.Mods
private IEnumerable generateBeats(IBeatmap beatmap, IReadOnlyCollection originalHitObjects)
{
- double startTime = originalHitObjects.First().StartTime;
- double endTime = originalHitObjects.Last().GetEndTime();
+ double startTime = beatmap.HitObjects.First().StartTime;
+ double endTime = beatmap.GetLastObjectTime();
var beats = beatmap.ControlPointInfo.TimingPoints
// Ignore timing points after endTime
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs
index b4004ff572..76ae7340ff 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs
@@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
{
public partial class DrawableOsuJudgement : DrawableJudgement
{
- protected SkinnableLighting Lighting { get; private set; }
+ internal SkinnableLighting Lighting { get; private set; }
[Resolved]
private OsuConfigManager config { get; set; }
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs
index 4601af45d8..a7b02596d5 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs
@@ -126,7 +126,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
PathVersion.UnbindFrom(HitObject.Path.Version);
- slidingSample.Samples = null;
+ slidingSample?.ClearSamples();
}
protected override void LoadSamples()
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs
index eed5c3e2e3..a5193f1b6e 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs
@@ -119,7 +119,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
{
base.OnFree();
- spinningSample.Samples = null;
+ spinningSample.ClearSamples();
}
protected override void LoadSamples()
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinnerTick.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinnerTick.cs
index b9ce07363c..34253e3d4f 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinnerTick.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinnerTick.cs
@@ -25,8 +25,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
Origin = Anchor.Centre;
}
- public override double MaximumJudgementOffset => DrawableSpinner.HitObject.Duration;
-
///
/// Apply a judgement result.
///
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/SkinnableLighting.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/SkinnableLighting.cs
index 68b61f9b2b..b39b9c4c54 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/SkinnableLighting.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/SkinnableLighting.cs
@@ -10,7 +10,7 @@ using osuTK.Graphics;
namespace osu.Game.Rulesets.Osu.Objects.Drawables
{
- public partial class SkinnableLighting : SkinnableSprite
+ internal partial class SkinnableLighting : SkinnableSprite
{
private DrawableHitObject targetObject;
private JudgementResult targetResult;
diff --git a/osu.Game.Rulesets.Osu/Objects/Spinner.cs b/osu.Game.Rulesets.Osu/Objects/Spinner.cs
index 0e1fe56cea..ed6f8a9a6a 100644
--- a/osu.Game.Rulesets.Osu/Objects/Spinner.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Spinner.cs
@@ -71,8 +71,8 @@ namespace osu.Game.Rulesets.Osu.Objects
double startTime = StartTime + (float)(i + 1) / totalSpins * Duration;
AddNested(i < SpinsRequired
- ? new SpinnerTick { StartTime = startTime }
- : new SpinnerBonusTick { StartTime = startTime });
+ ? new SpinnerTick { StartTime = startTime, SpinnerDuration = Duration }
+ : new SpinnerBonusTick { StartTime = startTime, SpinnerDuration = Duration });
}
}
diff --git a/osu.Game.Rulesets.Osu/Objects/SpinnerTick.cs b/osu.Game.Rulesets.Osu/Objects/SpinnerTick.cs
index 650d02c675..c890f3771b 100644
--- a/osu.Game.Rulesets.Osu/Objects/SpinnerTick.cs
+++ b/osu.Game.Rulesets.Osu/Objects/SpinnerTick.cs
@@ -11,10 +11,17 @@ namespace osu.Game.Rulesets.Osu.Objects
{
public class SpinnerTick : OsuHitObject
{
+ ///
+ /// Duration of the containing this spinner tick.
+ ///
+ public double SpinnerDuration { get; set; }
+
public override Judgement CreateJudgement() => new OsuSpinnerTickJudgement();
protected override HitWindows CreateHitWindows() => HitWindows.Empty;
+ public override double MaximumJudgementOffset => SpinnerDuration;
+
public class OsuSpinnerTickJudgement : OsuJudgement
{
public override HitResult MaxResult => HitResult.SmallBonus;
diff --git a/osu.Game.Rulesets.Osu/OsuInputManager.cs b/osu.Game.Rulesets.Osu/OsuInputManager.cs
index 7dede9e283..ccd388192e 100644
--- a/osu.Game.Rulesets.Osu/OsuInputManager.cs
+++ b/osu.Game.Rulesets.Osu/OsuInputManager.cs
@@ -1,17 +1,18 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
-using osu.Framework.Input;
+using osu.Framework.Allocation;
+using osu.Framework.Graphics;
using osu.Framework.Input.Bindings;
using osu.Framework.Input.Events;
-using osu.Framework.Input.StateChanges.Events;
using osu.Game.Input.Bindings;
+using osu.Game.Rulesets.Osu.Objects.Drawables;
+using osu.Game.Rulesets.Osu.UI;
using osu.Game.Rulesets.UI;
+using osuTK;
namespace osu.Game.Rulesets.Osu
{
@@ -28,6 +29,7 @@ namespace osu.Game.Rulesets.Osu
///
public bool AllowGameplayInputs
{
+ get => ((OsuKeyBindingContainer)KeyBindingContainer).AllowGameplayInputs;
set => ((OsuKeyBindingContainer)KeyBindingContainer).AllowGameplayInputs = value;
}
@@ -40,11 +42,24 @@ namespace osu.Game.Rulesets.Osu
protected override KeyBindingContainer CreateKeyBindingContainer(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique)
=> new OsuKeyBindingContainer(ruleset, variant, unique);
+ public bool CheckScreenSpaceActionPressJudgeable(Vector2 screenSpacePosition) =>
+ // This is a very naive but simple approach.
+ //
+ // Based on user feedback of more nuanced scenarios (where touch doesn't behave as expected),
+ // this can be expanded to a more complex implementation, but I'd still want to keep it as simple as we can.
+ NonPositionalInputQueue.OfType().Any(c => c.ReceivePositionalInputAt(screenSpacePosition));
+
public OsuInputManager(RulesetInfo ruleset)
: base(ruleset, 0, SimultaneousBindingMode.Unique)
{
}
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ Add(new OsuTouchInputMapper(this) { RelativeSizeAxes = Axes.Both });
+ }
+
protected override bool Handle(UIEvent e)
{
if ((e is MouseMoveEvent || e is TouchMoveEvent) && !AllowUserCursorMovement) return false;
@@ -52,19 +67,6 @@ namespace osu.Game.Rulesets.Osu
return base.Handle(e);
}
- protected override bool HandleMouseTouchStateChange(TouchStateChangeEvent e)
- {
- if (!AllowUserCursorMovement)
- {
- // Still allow for forwarding of the "touch" part, but replace the positional data with that of the mouse.
- // Primarily relied upon by the "autopilot" osu! mod.
- var touch = new Touch(e.Touch.Source, CurrentState.Mouse.Position);
- e = new TouchStateChangeEvent(e.State, e.Input, touch, e.IsActive, null);
- }
-
- return base.HandleMouseTouchStateChange(e);
- }
-
private partial class OsuKeyBindingContainer : RulesetKeyBindingContainer
{
private bool allowGameplayInputs = true;
diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs
index 79a566e33c..48056a49de 100644
--- a/osu.Game.Rulesets.Osu/OsuRuleset.cs
+++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs
@@ -164,7 +164,8 @@ namespace osu.Game.Rulesets.Osu
new MultiMod(new OsuModDoubleTime(), new OsuModNightcore()),
new OsuModHidden(),
new MultiMod(new OsuModFlashlight(), new OsuModBlinds()),
- new OsuModStrictTracking()
+ new OsuModStrictTracking(),
+ new OsuModAccuracyChallenge(),
};
case ModType.Conversion:
diff --git a/osu.Game.Rulesets.Osu/Replays/OsuAutoGeneratorBase.cs b/osu.Game.Rulesets.Osu/Replays/OsuAutoGeneratorBase.cs
index 1cb3208c30..74e16f7e0b 100644
--- a/osu.Game.Rulesets.Osu/Replays/OsuAutoGeneratorBase.cs
+++ b/osu.Game.Rulesets.Osu/Replays/OsuAutoGeneratorBase.cs
@@ -82,10 +82,10 @@ namespace osu.Game.Rulesets.Osu.Replays
private class ReplayFrameComparer : IComparer
{
- public int Compare(ReplayFrame f1, ReplayFrame f2)
+ public int Compare(ReplayFrame? f1, ReplayFrame? f2)
{
- if (f1 == null) throw new ArgumentNullException(nameof(f1));
- if (f2 == null) throw new ArgumentNullException(nameof(f2));
+ ArgumentNullException.ThrowIfNull(f1);
+ ArgumentNullException.ThrowIfNull(f2);
return f1.Time.CompareTo(f2.Time);
}
diff --git a/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonFollowCircle.cs b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonFollowCircle.cs
index 95c75164aa..fca3e70236 100644
--- a/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonFollowCircle.cs
+++ b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonFollowCircle.cs
@@ -1,35 +1,64 @@
// 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.Allocation;
+using osu.Framework.Bindables;
+using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Colour;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Utils;
+using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Osu.Objects.Drawables;
+using osuTK.Graphics;
namespace osu.Game.Rulesets.Osu.Skinning.Argon
{
public partial class ArgonFollowCircle : FollowCircle
{
+ private readonly CircularContainer circleContainer;
+ private readonly Box circleFill;
+
+ private readonly IBindable accentColour = new Bindable();
+
+ [Resolved(canBeNull: true)]
+ private DrawableHitObject? parentObject { get; set; }
+
public ArgonFollowCircle()
{
- InternalChild = new CircularContainer
+ InternalChild = circleContainer = new CircularContainer
{
RelativeSizeAxes = Axes.Both,
Masking = true,
BorderThickness = 4,
- BorderColour = ColourInfo.GradientVertical(Colour4.FromHex("FC618F"), Colour4.FromHex("BB1A41")),
Blending = BlendingParameters.Additive,
- Child = new Box
+ Child = circleFill = new Box
{
- Colour = ColourInfo.GradientVertical(Colour4.FromHex("FC618F"), Colour4.FromHex("BB1A41")),
RelativeSizeAxes = Axes.Both,
Alpha = 0.3f,
}
};
}
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ if (parentObject != null)
+ accentColour.BindTo(parentObject.AccentColour);
+ }
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+
+ accentColour.BindValueChanged(colour =>
+ {
+ circleContainer.BorderColour = ColourInfo.GradientVertical(colour.NewValue, colour.NewValue.Darken(0.5f));
+ circleFill.Colour = ColourInfo.GradientVertical(colour.NewValue, colour.NewValue.Darken(0.5f));
+ }, true);
+ }
+
protected override void OnSliderPress()
{
const float duration = 300f;
diff --git a/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonJudgementPiece.cs b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonJudgementPiece.cs
index f5f410210b..6f55d93eff 100644
--- a/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonJudgementPiece.cs
+++ b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonJudgementPiece.cs
@@ -3,7 +3,6 @@
using System;
using osu.Framework.Allocation;
-using osu.Framework.Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
@@ -17,42 +16,24 @@ using osuTK;
namespace osu.Game.Rulesets.Osu.Skinning.Argon
{
- public partial class ArgonJudgementPiece : CompositeDrawable, IAnimatableJudgement
+ public partial class ArgonJudgementPiece : JudgementPiece, IAnimatableJudgement
{
- protected readonly HitResult Result;
-
- protected SpriteText JudgementText { get; private set; } = null!;
-
private RingExplosion? ringExplosion;
[Resolved]
private OsuColour colours { get; set; } = null!;
public ArgonJudgementPiece(HitResult result)
+ : base(result)
{
- Result = result;
+ AutoSizeAxes = Axes.Both;
+
Origin = Anchor.Centre;
}
[BackgroundDependencyLoader]
private void load()
{
- AutoSizeAxes = Axes.Both;
-
- InternalChildren = new Drawable[]
- {
- JudgementText = new OsuSpriteText
- {
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- Text = Result.GetDescription().ToUpperInvariant(),
- Colour = colours.ForHitResult(Result),
- Blending = BlendingParameters.Additive,
- Spacing = new Vector2(5, 0),
- Font = OsuFont.Default.With(size: 20, weight: FontWeight.Bold),
- },
- };
-
if (Result.IsHit())
{
AddInternal(ringExplosion = new RingExplosion(Result)
@@ -62,6 +43,16 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon
}
}
+ protected override SpriteText CreateJudgementText() =>
+ new OsuSpriteText
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Blending = BlendingParameters.Additive,
+ Spacing = new Vector2(5, 0),
+ Font = OsuFont.Default.With(size: 20, weight: FontWeight.Bold),
+ };
+
///
/// Plays the default animation for this judgement piece.
///
diff --git a/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonMainCirclePiece.cs b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonMainCirclePiece.cs
index db458ec48a..1c5cf49625 100644
--- a/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonMainCirclePiece.cs
+++ b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonMainCirclePiece.cs
@@ -10,6 +10,7 @@ using osu.Framework.Graphics.Colour;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Effects;
using osu.Framework.Graphics.Shapes;
+using osu.Game.Configuration;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Rulesets.Objects.Drawables;
@@ -43,6 +44,9 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon
private readonly IBindable accentColour = new Bindable();
private readonly IBindable indexInCurrentCombo = new Bindable();
private readonly FlashPiece flash;
+ private readonly Container kiaiContainer;
+
+ private Bindable configHitLighting = null!;
[Resolved]
private DrawableHitObject drawableObject { get; set; } = null!;
@@ -64,24 +68,32 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon
outerGradient = new Circle // renders the outer bright gradient
{
Size = new Vector2(OUTER_GRADIENT_SIZE),
- Alpha = 1,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
},
innerGradient = new Circle // renders the inner bright gradient
{
Size = new Vector2(INNER_GRADIENT_SIZE),
- Alpha = 1,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
},
innerFill = new Circle // renders the inner dark fill
{
Size = new Vector2(INNER_FILL_SIZE),
- Alpha = 1,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
},
+ kiaiContainer = new CircularContainer
+ {
+ Masking = true,
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Size = Size,
+ Child = new KiaiFlash
+ {
+ RelativeSizeAxes = Axes.Both,
+ }
+ },
number = new OsuSpriteText
{
Font = OsuFont.Default.With(size: 52, weight: FontWeight.Bold),
@@ -96,12 +108,14 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon
}
[BackgroundDependencyLoader]
- private void load()
+ private void load(OsuConfigManager config)
{
var drawableOsuObject = (DrawableOsuHitObject)drawableObject;
accentColour.BindTo(drawableObject.AccentColour);
indexInCurrentCombo.BindTo(drawableOsuObject.IndexInCurrentComboBindable);
+
+ configHitLighting = config.GetBindable(OsuSetting.HitLighting);
}
protected override void LoadComplete()
@@ -117,20 +131,25 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon
outerGradient.ClearTransforms(targetMember: nameof(Colour));
outerGradient.Colour = ColourInfo.GradientVertical(colour.NewValue, colour.NewValue.Darken(0.1f));
+ kiaiContainer.Colour = colour.NewValue;
outerFill.Colour = innerFill.Colour = colour.NewValue.Darken(4);
innerGradient.Colour = ColourInfo.GradientVertical(colour.NewValue.Darken(0.5f), colour.NewValue.Darken(0.6f));
flash.Colour = colour.NewValue;
// Accent colour may be changed many times during a paused gameplay state.
// Schedule the change to avoid transforms piling up.
- Scheduler.AddOnce(updateStateTransforms);
+ Scheduler.AddOnce(() =>
+ {
+ ApplyTransformsAt(double.MinValue, true);
+ ClearTransformsAfter(double.MinValue, true);
+
+ updateStateTransforms(drawableObject, drawableObject.State.Value);
+ });
}, true);
drawableObject.ApplyCustomUpdateState += updateStateTransforms;
}
- private void updateStateTransforms() => updateStateTransforms(drawableObject, drawableObject.State.Value);
-
private void updateStateTransforms(DrawableHitObject drawableHitObject, ArmedState state)
{
using (BeginAbsoluteSequence(drawableObject.HitStateUpdateTime))
@@ -140,7 +159,6 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon
case ArmedState.Hit:
// Fade out time is at a maximum of 800. Must match `DrawableHitCircle`'s arbitrary lifetime spec.
const double fade_out_time = 800;
-
const double flash_in_duration = 150;
const double resize_duration = 400;
@@ -171,20 +189,40 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon
// gradient layers.
border.ResizeTo(Size * shrink_size + new Vector2(border.BorderThickness), resize_duration, Easing.OutElasticHalf);
+ // Kiai flash should track the overall size but also be cleaned up quite fast, so we don't get additional
+ // flashes after the hit animation is already in a mostly-completed state.
+ kiaiContainer.ResizeTo(Size * shrink_size, resize_duration, Easing.OutElasticHalf);
+ kiaiContainer.FadeOut(flash_in_duration, Easing.OutQuint);
+
// The outer gradient is resize with a slight delay from the border.
// This is to give it a bomb-like effect, with the border "triggering" its animation when getting close.
using (BeginDelayedSequence(flash_in_duration / 12))
{
outerGradient.ResizeTo(OUTER_GRADIENT_SIZE * shrink_size, resize_duration, Easing.OutElasticHalf);
+
outerGradient
.FadeColour(Color4.White, 80)
.Then()
.FadeOut(flash_in_duration);
}
- flash.FadeTo(1, flash_in_duration, Easing.OutQuint);
+ if (configHitLighting.Value)
+ {
+ flash.HitLighting = true;
+ flash.FadeTo(1, flash_in_duration, Easing.OutQuint);
+
+ this.FadeOut(fade_out_time, Easing.OutQuad);
+ }
+ else
+ {
+ flash.HitLighting = false;
+ flash.FadeTo(1, flash_in_duration, Easing.OutQuint)
+ .Then()
+ .FadeOut(flash_in_duration, Easing.OutQuint);
+
+ this.FadeOut(fade_out_time * 0.8f, Easing.OutQuad);
+ }
- this.FadeOut(fade_out_time, Easing.OutQuad);
break;
}
}
@@ -215,6 +253,8 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon
Child.AlwaysPresent = true;
}
+ public bool HitLighting { get; set; }
+
protected override void Update()
{
base.Update();
@@ -223,7 +263,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon
{
Type = EdgeEffectType.Glow,
Colour = Colour,
- Radius = OsuHitObject.OBJECT_RADIUS * 1.2f,
+ Radius = OsuHitObject.OBJECT_RADIUS * (HitLighting ? 1.2f : 0.6f),
};
}
}
diff --git a/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonSliderBall.cs b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonSliderBall.cs
index 48b43f359d..d6ce793c7e 100644
--- a/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonSliderBall.cs
+++ b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonSliderBall.cs
@@ -2,6 +2,8 @@
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
+using osu.Framework.Bindables;
+using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Colour;
using osu.Framework.Graphics.Containers;
@@ -21,6 +23,8 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon
private readonly Vector2 defaultIconScale = new Vector2(0.6f, 0.8f);
+ private readonly IBindable accentColour = new Bindable();
+
[Resolved(canBeNull: true)]
private DrawableHitObject? parentObject { get; set; }
@@ -37,7 +41,6 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon
{
fill = new Box
{
- Colour = ColourInfo.GradientVertical(Colour4.FromHex("FC618F"), Colour4.FromHex("BB1A41")),
RelativeSizeAxes = Axes.Both,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
@@ -53,10 +56,22 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon
};
}
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ if (parentObject != null)
+ accentColour.BindTo(parentObject.AccentColour);
+ }
+
protected override void LoadComplete()
{
base.LoadComplete();
+ accentColour.BindValueChanged(colour =>
+ {
+ fill.Colour = ColourInfo.GradientVertical(colour.NewValue, colour.NewValue.Darken(0.5f));
+ }, true);
+
if (parentObject != null)
{
parentObject.ApplyCustomUpdateState += updateStateTransforms;
diff --git a/osu.Game.Rulesets.Osu/Skinning/Argon/OsuArgonSkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/Argon/OsuArgonSkinTransformer.cs
index 86194d2c43..f98a47097d 100644
--- a/osu.Game.Rulesets.Osu/Skinning/Argon/OsuArgonSkinTransformer.cs
+++ b/osu.Game.Rulesets.Osu/Skinning/Argon/OsuArgonSkinTransformer.cs
@@ -19,6 +19,10 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon
switch (lookup)
{
case GameplaySkinComponentLookup resultComponent:
+ // This should eventually be moved to a skin setting, when supported.
+ if (Skin is ArgonProSkin && resultComponent.Component >= HitResult.Great)
+ return Drawable.Empty();
+
return new ArgonJudgementPiece(resultComponent.Component);
case OsuSkinComponentLookup osuComponent:
diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/SpinnerSpmCalculator.cs b/osu.Game.Rulesets.Osu/Skinning/Default/SpinnerSpmCalculator.cs
index 0bd5fd4cac..44962c8548 100644
--- a/osu.Game.Rulesets.Osu/Skinning/Default/SpinnerSpmCalculator.cs
+++ b/osu.Game.Rulesets.Osu/Skinning/Default/SpinnerSpmCalculator.cs
@@ -35,14 +35,14 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default
public void SetRotation(float currentRotation)
{
- // Never calculate SPM by same time of record to avoid 0 / 0 = NaN or X / 0 = Infinity result.
- if (Precision.AlmostEquals(0, Time.Elapsed))
- return;
-
// If we've gone back in time, it's fine to work with a fresh set of records for now
if (records.Count > 0 && Time.Current < records.Last().Time)
records.Clear();
+ // Never calculate SPM by same time of record to avoid 0 / 0 = NaN or X / 0 = Infinity result.
+ if (records.Count > 0 && Precision.AlmostEquals(Time.Current, records.Last().Time))
+ return;
+
if (records.Count > 0)
{
var record = records.Peek();
diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyMainCirclePiece.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyMainCirclePiece.cs
index 6547b058c2..cadac4d319 100644
--- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyMainCirclePiece.cs
+++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyMainCirclePiece.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 osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions.ObjectExtensions;
@@ -29,8 +30,8 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
private readonly bool hasNumber;
- protected Drawable CircleSprite = null!;
- protected Drawable OverlaySprite = null!;
+ protected LegacyKiaiFlashingDrawable CircleSprite = null!;
+ protected LegacyKiaiFlashingDrawable OverlaySprite = null!;
protected Container OverlayLayer { get; private set; } = null!;
@@ -65,7 +66,6 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
// at this point, any further texture fetches should be correctly using the priority source if the base texture was retrieved using it.
// the conditional above handles the case where a sliderendcircle.png is retrieved from the skin, but sliderendcircleoverlay.png doesn't exist.
// expected behaviour in this scenario is not showing the overlay, rather than using hitcircleoverlay.png.
-
InternalChildren = new[]
{
CircleSprite = new LegacyKiaiFlashingDrawable(() => new Sprite { Texture = skin.GetTexture(circleName) })
@@ -114,7 +114,21 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
{
base.LoadComplete();
- accentColour.BindValueChanged(colour => CircleSprite.Colour = LegacyColourCompatibility.DisallowZeroAlpha(colour.NewValue), true);
+ accentColour.BindValueChanged(colour =>
+ {
+ Color4 objectColour = colour.NewValue;
+ int add = Math.Max(25, 300 - (int)(objectColour.R * 255) - (int)(objectColour.G * 255) - (int)(objectColour.B * 255));
+
+ var kiaiTintColour = new Color4(
+ (byte)Math.Min((byte)(objectColour.R * 255) + add, 255),
+ (byte)Math.Min((byte)(objectColour.G * 255) + add, 255),
+ (byte)Math.Min((byte)(objectColour.B * 255) + add, 255),
+ 255);
+
+ CircleSprite.Colour = LegacyColourCompatibility.DisallowZeroAlpha(colour.NewValue);
+ OverlaySprite.KiaiGlowColour = CircleSprite.KiaiGlowColour = LegacyColourCompatibility.DisallowZeroAlpha(kiaiTintColour);
+ }, true);
+
if (hasNumber)
indexInCurrentCombo.BindValueChanged(index => hitCircleText.Text = (index.NewValue + 1).ToString(), true);
diff --git a/osu.Game.Rulesets.Osu/Skinning/SliderBody.cs b/osu.Game.Rulesets.Osu/Skinning/SliderBody.cs
index 283687adfd..e7885e65de 100644
--- a/osu.Game.Rulesets.Osu/Skinning/SliderBody.cs
+++ b/osu.Game.Rulesets.Osu/Skinning/SliderBody.cs
@@ -31,6 +31,11 @@ namespace osu.Game.Rulesets.Osu.Skinning
///
public virtual Vector2 PathOffset => path.PositionInBoundingBox(path.Vertices[0]);
+ ///
+ /// Offset in absolute coordinates from the end of the curve.
+ ///
+ public virtual Vector2 PathEndOffset => path.PositionInBoundingBox(path.Vertices[^1]);
+
///
/// Used to colour the path.
///
diff --git a/osu.Game.Rulesets.Osu/Skinning/SnakingSliderBody.cs b/osu.Game.Rulesets.Osu/Skinning/SnakingSliderBody.cs
index f8ee465cd6..0b7acc1f47 100644
--- a/osu.Game.Rulesets.Osu/Skinning/SnakingSliderBody.cs
+++ b/osu.Game.Rulesets.Osu/Skinning/SnakingSliderBody.cs
@@ -43,6 +43,8 @@ namespace osu.Game.Rulesets.Osu.Skinning
public override Vector2 PathOffset => snakedPathOffset;
+ public override Vector2 PathEndOffset => snakedPathEndOffset;
+
///
/// The top-left position of the path when fully snaked.
///
@@ -53,6 +55,11 @@ namespace osu.Game.Rulesets.Osu.Skinning
///
private Vector2 snakedPathOffset;
+ ///
+ /// The offset of the end of path from when fully snaked.
+ ///
+ private Vector2 snakedPathEndOffset;
+
private DrawableSlider drawableSlider = null!;
[BackgroundDependencyLoader]
@@ -109,6 +116,7 @@ namespace osu.Game.Rulesets.Osu.Skinning
snakedPosition = Path.PositionInBoundingBox(Vector2.Zero);
snakedPathOffset = Path.PositionInBoundingBox(Path.Vertices[0]);
+ snakedPathEndOffset = Path.PositionInBoundingBox(Path.Vertices[^1]);
double lastSnakedStart = SnakedStart ?? 0;
double lastSnakedEnd = SnakedEnd ?? 0;
diff --git a/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs b/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs
index d1fa0d09cc..c3efd48053 100644
--- a/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs
+++ b/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs
@@ -26,6 +26,8 @@ namespace osu.Game.Rulesets.Osu.UI
{
protected new OsuRulesetConfigManager Config => (OsuRulesetConfigManager)base.Config;
+ public new OsuInputManager KeyBindingInputManager => (OsuInputManager)base.KeyBindingInputManager;
+
public new OsuPlayfield Playfield => (OsuPlayfield)base.Playfield;
public DrawableOsuRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList mods = null)
diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs
index 122330d09b..ed02284a4b 100644
--- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs
+++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs
@@ -67,7 +67,7 @@ namespace osu.Game.Rulesets.Osu.UI
HitPolicy = new StartTimeOrderedHitPolicy();
var hitWindows = new OsuHitWindows();
- foreach (var result in Enum.GetValues(typeof(HitResult)).OfType().Where(r => r > HitResult.None && hitWindows.IsHitResultAllowed(r)))
+ foreach (var result in Enum.GetValues().Where(r => r > HitResult.None && hitWindows.IsHitResultAllowed(r)))
poolDictionary.Add(result, new DrawableJudgementPool(result, onJudgementLoaded));
AddRangeInternal(poolDictionary.Values);
diff --git a/osu.Game.Rulesets.Osu/UI/OsuSettingsSubsection.cs b/osu.Game.Rulesets.Osu/UI/OsuSettingsSubsection.cs
index f711a0fc31..64c4e7eef6 100644
--- a/osu.Game.Rulesets.Osu/UI/OsuSettingsSubsection.cs
+++ b/osu.Game.Rulesets.Osu/UI/OsuSettingsSubsection.cs
@@ -1,11 +1,10 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Localisation;
+using osu.Game.Localisation;
using osu.Game.Overlays.Settings;
using osu.Game.Rulesets.Osu.Configuration;
using osu.Game.Rulesets.UI;
@@ -30,23 +29,23 @@ namespace osu.Game.Rulesets.Osu.UI
{
new SettingsCheckbox
{
- LabelText = "Snaking in sliders",
+ LabelText = RulesetSettingsStrings.SnakingInSliders,
Current = config.GetBindable(OsuRulesetSetting.SnakingInSliders)
},
new SettingsCheckbox
{
ClassicDefault = false,
- LabelText = "Snaking out sliders",
+ LabelText = RulesetSettingsStrings.SnakingOutSliders,
Current = config.GetBindable(OsuRulesetSetting.SnakingOutSliders)
},
new SettingsCheckbox
{
- LabelText = "Cursor trail",
+ LabelText = RulesetSettingsStrings.CursorTrail,
Current = config.GetBindable(OsuRulesetSetting.ShowCursorTrail)
},
new SettingsEnumDropdown
{
- LabelText = "Playfield border style",
+ LabelText = RulesetSettingsStrings.PlayfieldBorderStyle,
Current = config.GetBindable(OsuRulesetSetting.PlayfieldBorderStyle),
},
};
diff --git a/osu.Game.Rulesets.Osu/UI/OsuTouchInputMapper.cs b/osu.Game.Rulesets.Osu/UI/OsuTouchInputMapper.cs
new file mode 100644
index 0000000000..8df1c35b5c
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/UI/OsuTouchInputMapper.cs
@@ -0,0 +1,161 @@
+// 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 System.Linq;
+using osu.Framework.Allocation;
+using osu.Framework.Bindables;
+using osu.Framework.Graphics;
+using osu.Framework.Input;
+using osu.Framework.Input.Events;
+using osu.Framework.Input.StateChanges;
+using osu.Game.Configuration;
+using osuTK;
+
+namespace osu.Game.Rulesets.Osu.UI
+{
+ public partial class OsuTouchInputMapper : Drawable
+ {
+ ///
+ /// All the active s and the that it triggered (if any).
+ /// Ordered from oldest to newest touch chronologically.
+ ///
+ private readonly List trackedTouches = new List();
+
+ private TrackedTouch? positionTrackingTouch;
+
+ private readonly OsuInputManager osuInputManager;
+
+ private Bindable mouseDisabled = null!;
+
+ public OsuTouchInputMapper(OsuInputManager inputManager)
+ {
+ osuInputManager = inputManager;
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(OsuConfigManager config)
+ {
+ // The mouse button disable setting affects touch. It's a bit weird.
+ // This is mostly just doing the same as what is done in RulesetInputManager to match behaviour.
+ mouseDisabled = config.GetBindable(OsuSetting.MouseDisableButtons);
+ }
+
+ // Required to handle touches outside of the playfield when screen scaling is enabled.
+ public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true;
+
+ protected override void OnTouchMove(TouchMoveEvent e)
+ {
+ base.OnTouchMove(e);
+ handleTouchMovement(e);
+ }
+
+ protected override bool OnTouchDown(TouchDownEvent e)
+ {
+ OsuAction action = trackedTouches.Any(t => t.Action == OsuAction.LeftButton)
+ ? OsuAction.RightButton
+ : OsuAction.LeftButton;
+
+ // Ignore any taps which trigger an action which is already handled. But track them for potential positional input in the future.
+ bool shouldResultInAction = osuInputManager.AllowGameplayInputs && !mouseDisabled.Value && trackedTouches.All(t => t.Action != action);
+
+ // If we can actually accept as an action, check whether this tap was on a circle's receptor.
+ // This case gets special handling to allow for empty-space stream tapping.
+ bool isDirectCircleTouch = osuInputManager.CheckScreenSpaceActionPressJudgeable(e.ScreenSpaceTouchDownPosition);
+
+ var newTouch = new TrackedTouch(e.Touch.Source, shouldResultInAction ? action : null, isDirectCircleTouch);
+
+ updatePositionTracking(newTouch);
+
+ trackedTouches.Add(newTouch);
+
+ // Important to update position before triggering the pressed action.
+ handleTouchMovement(e);
+
+ if (shouldResultInAction)
+ osuInputManager.KeyBindingContainer.TriggerPressed(action);
+
+ return true;
+ }
+
+ ///
+ /// Given a new touch, update the positional tracking state and any related operations.
+ ///
+ private void updatePositionTracking(TrackedTouch newTouch)
+ {
+ // If the new touch directly interacted with a circle's receptor, it always becomes the current touch for positional tracking.
+ if (newTouch.DirectTouch)
+ {
+ positionTrackingTouch = newTouch;
+ return;
+ }
+
+ // Otherwise, we only want to use the new touch for position tracking if no other touch is tracking position yet..
+ if (positionTrackingTouch == null)
+ {
+ positionTrackingTouch = newTouch;
+ return;
+ }
+
+ // ..or if the current position tracking touch was not a direct touch (this one is debatable and may be change in the future, but it's the simplest way to handle)
+ if (!positionTrackingTouch.DirectTouch)
+ {
+ positionTrackingTouch = newTouch;
+ return;
+ }
+
+ // In the case the new touch was not used for position tracking, we should also check the previous position tracking touch.
+ // If it was a direct touch and still has its action pressed, that action should be released.
+ //
+ // This is done to allow tracking with the initial touch while still having both Left/Right actions available for alternating with two more touches.
+ if (positionTrackingTouch.DirectTouch && positionTrackingTouch.Action is OsuAction directTouchAction)
+ {
+ osuInputManager.KeyBindingContainer.TriggerReleased(directTouchAction);
+ positionTrackingTouch.Action = null;
+ }
+ }
+
+ private void handleTouchMovement(TouchEvent touchEvent)
+ {
+ // Movement should only be tracked for the most recent touch.
+ if (touchEvent.Touch.Source != positionTrackingTouch?.Source)
+ return;
+
+ if (!osuInputManager.AllowUserCursorMovement)
+ return;
+
+ new MousePositionAbsoluteInput { Position = touchEvent.ScreenSpaceTouch.Position }.Apply(osuInputManager.CurrentState, osuInputManager);
+ }
+
+ protected override void OnTouchUp(TouchUpEvent e)
+ {
+ var tracked = trackedTouches.Single(t => t.Source == e.Touch.Source);
+
+ if (tracked.Action is OsuAction action)
+ osuInputManager.KeyBindingContainer.TriggerReleased(action);
+
+ if (positionTrackingTouch == tracked)
+ positionTrackingTouch = null;
+
+ trackedTouches.Remove(tracked);
+
+ base.OnTouchUp(e);
+ }
+
+ private class TrackedTouch
+ {
+ public readonly TouchSource Source;
+
+ public OsuAction? Action;
+
+ public readonly bool DirectTouch;
+
+ public TrackedTouch(TouchSource source, OsuAction? action, bool directTouch)
+ {
+ Source = source;
+ Action = action;
+ DirectTouch = directTouch;
+ }
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs
index 3a8b3f67d0..aa4cd0af14 100644
--- a/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs
+++ b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs
@@ -112,44 +112,46 @@ namespace osu.Game.Rulesets.Osu.Utils
/// Reflects the position of the in the playfield horizontally.
///
/// The object to reflect.
- public static void ReflectHorizontally(OsuHitObject osuObject)
+ public static void ReflectHorizontallyAlongPlayfield(OsuHitObject osuObject)
{
osuObject.Position = new Vector2(OsuPlayfield.BASE_SIZE.X - osuObject.X, osuObject.Position.Y);
- if (!(osuObject is Slider slider))
+ if (osuObject is not Slider slider)
return;
- // No need to update the head and tail circles, since slider handles that when the new slider path is set
- slider.NestedHitObjects.OfType().ForEach(h => h.Position = new Vector2(OsuPlayfield.BASE_SIZE.X - h.Position.X, h.Position.Y));
- slider.NestedHitObjects.OfType().ForEach(h => h.Position = new Vector2(OsuPlayfield.BASE_SIZE.X - h.Position.X, h.Position.Y));
+ void reflectNestedObject(OsuHitObject nested) => nested.Position = new Vector2(OsuPlayfield.BASE_SIZE.X - nested.Position.X, nested.Position.Y);
+ static void reflectControlPoint(PathControlPoint point) => point.Position = new Vector2(-point.Position.X, point.Position.Y);
- var controlPoints = slider.Path.ControlPoints.Select(p => new PathControlPoint(p.Position, p.Type)).ToArray();
- foreach (var point in controlPoints)
- point.Position = new Vector2(-point.Position.X, point.Position.Y);
-
- slider.Path = new SliderPath(controlPoints, slider.Path.ExpectedDistance.Value);
+ modifySlider(slider, reflectNestedObject, reflectControlPoint);
}
///
/// Reflects the position of the in the playfield vertically.
///
/// The object to reflect.
- public static void ReflectVertically(OsuHitObject osuObject)
+ public static void ReflectVerticallyAlongPlayfield(OsuHitObject osuObject)
{
osuObject.Position = new Vector2(osuObject.Position.X, OsuPlayfield.BASE_SIZE.Y - osuObject.Y);
- if (!(osuObject is Slider slider))
+ if (osuObject is not Slider slider)
return;
- // No need to update the head and tail circles, since slider handles that when the new slider path is set
- slider.NestedHitObjects.OfType().ForEach(h => h.Position = new Vector2(h.Position.X, OsuPlayfield.BASE_SIZE.Y - h.Position.Y));
- slider.NestedHitObjects.OfType().ForEach(h => h.Position = new Vector2(h.Position.X, OsuPlayfield.BASE_SIZE.Y - h.Position.Y));
+ void reflectNestedObject(OsuHitObject nested) => nested.Position = new Vector2(nested.Position.X, OsuPlayfield.BASE_SIZE.Y - nested.Position.Y);
+ static void reflectControlPoint(PathControlPoint point) => point.Position = new Vector2(point.Position.X, -point.Position.Y);
- var controlPoints = slider.Path.ControlPoints.Select(p => new PathControlPoint(p.Position, p.Type)).ToArray();
- foreach (var point in controlPoints)
- point.Position = new Vector2(point.Position.X, -point.Position.Y);
+ modifySlider(slider, reflectNestedObject, reflectControlPoint);
+ }
- slider.Path = new SliderPath(controlPoints, slider.Path.ExpectedDistance.Value);
+ ///
+ /// Flips the position of the around its start position horizontally.
+ ///
+ /// The slider to be flipped.
+ public static void FlipSliderInPlaceHorizontally(Slider slider)
+ {
+ void flipNestedObject(OsuHitObject nested) => nested.Position = new Vector2(slider.X - (nested.X - slider.X), nested.Y);
+ static void flipControlPoint(PathControlPoint point) => point.Position = new Vector2(-point.Position.X, point.Position.Y);
+
+ modifySlider(slider, flipNestedObject, flipControlPoint);
}
///
@@ -160,14 +162,20 @@ namespace osu.Game.Rulesets.Osu.Utils
public static void RotateSlider(Slider slider, float rotation)
{
void rotateNestedObject(OsuHitObject nested) => nested.Position = rotateVector(nested.Position - slider.Position, rotation) + slider.Position;
+ void rotateControlPoint(PathControlPoint point) => point.Position = rotateVector(point.Position, rotation);
+ modifySlider(slider, rotateNestedObject, rotateControlPoint);
+ }
+
+ private static void modifySlider(Slider slider, Action modifyNestedObject, Action modifyControlPoint)
+ {
// No need to update the head and tail circles, since slider handles that when the new slider path is set
- slider.NestedHitObjects.OfType().ForEach(rotateNestedObject);
- slider.NestedHitObjects.OfType().ForEach(rotateNestedObject);
+ slider.NestedHitObjects.OfType().ForEach(modifyNestedObject);
+ slider.NestedHitObjects.OfType().ForEach(modifyNestedObject);
var controlPoints = slider.Path.ControlPoints.Select(p => new PathControlPoint(p.Position, p.Type)).ToArray();
foreach (var point in controlPoints)
- point.Position = rotateVector(point.Position, rotation);
+ modifyControlPoint(point);
slider.Path = new SliderPath(controlPoints, slider.Path.ExpectedDistance.Value);
}
diff --git a/osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj b/osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj
index 98f1e69bd1..bf776fe5dd 100644
--- a/osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj
+++ b/osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj
@@ -1,6 +1,6 @@
- netstandard2.1
+ net6.0
Library
true
click the circles. to the beat.
@@ -15,4 +15,4 @@
-
\ No newline at end of file
+
diff --git a/osu.Game.Rulesets.Taiko.Tests.Android/Properties/AndroidManifest.xml b/osu.Game.Rulesets.Taiko.Tests.Android/AndroidManifest.xml
similarity index 95%
rename from osu.Game.Rulesets.Taiko.Tests.Android/Properties/AndroidManifest.xml
rename to osu.Game.Rulesets.Taiko.Tests.Android/AndroidManifest.xml
index d9de0fde4e..452b9683ec 100644
--- a/osu.Game.Rulesets.Taiko.Tests.Android/Properties/AndroidManifest.xml
+++ b/osu.Game.Rulesets.Taiko.Tests.Android/AndroidManifest.xml
@@ -1,5 +1,5 @@
-
+
\ No newline at end of file
diff --git a/osu.Game.Rulesets.Taiko.Tests.Android/osu.Game.Rulesets.Taiko.Tests.Android.csproj b/osu.Game.Rulesets.Taiko.Tests.Android/osu.Game.Rulesets.Taiko.Tests.Android.csproj
index 4d4dabebe6..a639326ebd 100644
--- a/osu.Game.Rulesets.Taiko.Tests.Android/osu.Game.Rulesets.Taiko.Tests.Android.csproj
+++ b/osu.Game.Rulesets.Taiko.Tests.Android/osu.Game.Rulesets.Taiko.Tests.Android.csproj
@@ -1,49 +1,24 @@
-
-
+
- Debug
- AnyCPU
- 8.0.30703
- 2.0
- {3701A0A1-8476-42C6-B5C4-D24129B4A484}
- {EFBA0AD7-5A72-4C68-AF49-83D382785DCF};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
- {122416d6-6b49-4ee2-a1e8-b825f31c79fe}
+ net6.0-android
+ Exe
osu.Game.Rulesets.Taiko.Tests
osu.Game.Rulesets.Taiko.Tests.Android
- Properties\AndroidManifest.xml
- armeabi-v7a;x86;arm64-v8a
-
-
- None
- cjk;mideast;other;rare;west
- true
-
-
-
-
-
-
-
+
%(RecursiveDir)%(Filename)%(Extension)
+
+
+ %(RecursiveDir)%(Filename)%(Extension)
+ Android\%(RecursiveDir)%(Filename)%(Extension)
+
-
- {f167e17a-7de6-4af5-b920-a5112296c695}
- osu.Game.Rulesets.Taiko
-
-
- {2a66dd92-adb1-4994-89e2-c94e04acda0d}
- osu.Game
-
+
+
-
-
- 5.0.0
-
-
-
\ No newline at end of file
diff --git a/osu.Game.Rulesets.Taiko.Tests.iOS/Application.cs b/osu.Game.Rulesets.Taiko.Tests.iOS/Application.cs
index 1ebbd61a94..0e3a953728 100644
--- a/osu.Game.Rulesets.Taiko.Tests.iOS/Application.cs
+++ b/osu.Game.Rulesets.Taiko.Tests.iOS/Application.cs
@@ -3,7 +3,6 @@
#nullable disable
-using osu.Framework.iOS;
using UIKit;
namespace osu.Game.Rulesets.Taiko.Tests.iOS
@@ -12,7 +11,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.iOS
{
public static void Main(string[] args)
{
- UIApplication.Main(args, typeof(GameUIApplication), typeof(AppDelegate));
+ UIApplication.Main(args, null, typeof(AppDelegate));
}
}
}
diff --git a/osu.Game.Rulesets.Taiko.Tests.iOS/Info.plist b/osu.Game.Rulesets.Taiko.Tests.iOS/Info.plist
index 9628475b3e..76cb3c0db0 100644
--- a/osu.Game.Rulesets.Taiko.Tests.iOS/Info.plist
+++ b/osu.Game.Rulesets.Taiko.Tests.iOS/Info.plist
@@ -13,7 +13,7 @@
LSRequiresIPhoneOS
MinimumOSVersion
- 10.0
+ 13.4
UIDeviceFamily
1
diff --git a/osu.Game.Rulesets.Taiko.Tests.iOS/osu.Game.Rulesets.Taiko.Tests.iOS.csproj b/osu.Game.Rulesets.Taiko.Tests.iOS/osu.Game.Rulesets.Taiko.Tests.iOS.csproj
index 8ee640cd99..e648a11299 100644
--- a/osu.Game.Rulesets.Taiko.Tests.iOS/osu.Game.Rulesets.Taiko.Tests.iOS.csproj
+++ b/osu.Game.Rulesets.Taiko.Tests.iOS/osu.Game.Rulesets.Taiko.Tests.iOS.csproj
@@ -1,35 +1,19 @@
-
-
+
- Debug
- iPhoneSimulator
- {7E408809-66AC-49D1-AF4D-98834F9B979A}
Exe
+ net6.0-ios
+ 13.4
osu.Game.Rulesets.Taiko.Tests
osu.Game.Rulesets.Taiko.Tests.iOS
-
-
-
- Linker.xml
-
-
-
%(RecursiveDir)%(Filename)%(Extension)
-
- {2A66DD92-ADB1-4994-89E2-C94E04ACDA0D}
- osu.Game
-
-
- {F167E17A-7DE6-4AF5-B920-A5112296C695}
- osu.Game.Rulesets.Taiko
-
+
+
-
-
\ No newline at end of file
+
diff --git a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneTaikoKiaiGlow.cs b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneTaikoKiaiGlow.cs
new file mode 100644
index 0000000000..a5e2eb0dbb
--- /dev/null
+++ b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneTaikoKiaiGlow.cs
@@ -0,0 +1,37 @@
+// 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.Beatmaps.ControlPoints;
+using osu.Game.Beatmaps;
+using osu.Game.Rulesets.Taiko.Skinning.Legacy;
+
+namespace osu.Game.Rulesets.Taiko.Tests.Skinning
+{
+ public partial class TestSceneTaikoKiaiGlow : TaikoSkinnableTestScene
+ {
+ [Test]
+ public void TestKiaiGlow()
+ {
+ AddStep("Create kiai glow", () => SetContents(_ => new LegacyKiaiGlow()));
+ AddToggleStep("Toggle kiai mode", setUpBeatmap);
+ }
+
+ private void setUpBeatmap(bool withKiai)
+ {
+ var controlPointInfo = new ControlPointInfo();
+
+ controlPointInfo.Add(0, new TimingControlPoint { BeatLength = 500 });
+
+ if (withKiai)
+ controlPointInfo.Add(0, new EffectControlPoint { KiaiMode = true });
+
+ Beatmap.Value = CreateWorkingBeatmap(new Beatmap
+ {
+ ControlPointInfo = controlPointInfo
+ });
+
+ Beatmap.Value.Track.Start();
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Taiko.Tests/TaikoBeatmapConversionTest.cs b/osu.Game.Rulesets.Taiko.Tests/TaikoBeatmapConversionTest.cs
index 796f5721bb..781a686700 100644
--- a/osu.Game.Rulesets.Taiko.Tests/TaikoBeatmapConversionTest.cs
+++ b/osu.Game.Rulesets.Taiko.Tests/TaikoBeatmapConversionTest.cs
@@ -25,6 +25,7 @@ namespace osu.Game.Rulesets.Taiko.Tests
[TestCase("slider-conversion-v6")]
[TestCase("slider-conversion-v14")]
[TestCase("slider-generating-drumroll-2")]
+ [TestCase("file-hitsamples")]
public void Test(string name) => base.Test(name);
protected override IEnumerable CreateConvertValue(HitObject hitObject)
diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneDrumTouchInputArea.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneDrumTouchInputArea.cs
index 6514b760bb..5fb85df82b 100644
--- a/osu.Game.Rulesets.Taiko.Tests/TestSceneDrumTouchInputArea.cs
+++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneDrumTouchInputArea.cs
@@ -2,8 +2,11 @@
// See the LICENCE file in the repository root for full licence text.
using NUnit.Framework;
+using osu.Framework.Allocation;
+using osu.Framework.Bindables;
+using osu.Framework.Extensions.ObjectExtensions;
using osu.Framework.Graphics;
-using osu.Framework.Testing;
+using osu.Game.Rulesets.Taiko.Configuration;
using osu.Game.Rulesets.Taiko.UI;
using osu.Game.Tests.Visual;
@@ -14,36 +17,48 @@ namespace osu.Game.Rulesets.Taiko.Tests
{
private DrumTouchInputArea drumTouchInputArea = null!;
- [SetUpSteps]
- public void SetUpSteps()
+ private readonly Bindable controlScheme = new Bindable();
+
+ [BackgroundDependencyLoader]
+ private void load()
{
- AddStep("create drum", () =>
+ var config = (TaikoRulesetConfigManager)RulesetConfigs.GetConfigFor(Ruleset.Value.CreateInstance()).AsNonNull();
+ config.BindWith(TaikoRulesetSetting.TouchControlScheme, controlScheme);
+ }
+
+ private void createDrum()
+ {
+ Child = new TaikoInputManager(new TaikoRuleset().RulesetInfo)
{
- Child = new TaikoInputManager(new TaikoRuleset().RulesetInfo)
+ RelativeSizeAxes = Axes.Both,
+ Children = new Drawable[]
{
- RelativeSizeAxes = Axes.Both,
- Children = new Drawable[]
+ new InputDrum
{
- new InputDrum
- {
- Anchor = Anchor.TopCentre,
- Origin = Anchor.TopCentre,
- Height = 0.2f,
- },
- drumTouchInputArea = new DrumTouchInputArea
- {
- Anchor = Anchor.BottomCentre,
- Origin = Anchor.BottomCentre,
- },
+ Anchor = Anchor.TopCentre,
+ Origin = Anchor.TopCentre,
+ Height = 0.2f,
},
- };
- });
+ drumTouchInputArea = new DrumTouchInputArea
+ {
+ Anchor = Anchor.BottomCentre,
+ Origin = Anchor.BottomCentre,
+ }
+ }
+ };
}
[Test]
public void TestDrum()
{
+ AddStep("create drum", createDrum);
AddStep("show drum", () => drumTouchInputArea.Show());
+
+ AddStep("change scheme (kddk)", () => controlScheme.Value = TaikoTouchControlScheme.KDDK);
+ AddStep("change scheme (kkdd)", () => controlScheme.Value = TaikoTouchControlScheme.KKDD);
+ AddStep("change scheme (ddkk)", () => controlScheme.Value = TaikoTouchControlScheme.DDKK);
}
+
+ protected override Ruleset CreateRuleset() => new TaikoRuleset();
}
}
diff --git a/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj b/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj
index 6af1beff69..0c39ad988b 100644
--- a/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj
+++ b/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj
@@ -1,9 +1,9 @@
-
+
-
+
WinExe
diff --git a/osu.Game.Rulesets.Taiko/Configuration/TaikoRulesetConfigManager.cs b/osu.Game.Rulesets.Taiko/Configuration/TaikoRulesetConfigManager.cs
new file mode 100644
index 0000000000..c3bc7f6439
--- /dev/null
+++ b/osu.Game.Rulesets.Taiko/Configuration/TaikoRulesetConfigManager.cs
@@ -0,0 +1,28 @@
+// 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.Configuration;
+using osu.Game.Rulesets.Configuration;
+
+namespace osu.Game.Rulesets.Taiko.Configuration
+{
+ public class TaikoRulesetConfigManager : RulesetConfigManager
+ {
+ public TaikoRulesetConfigManager(SettingsStore? settings, RulesetInfo ruleset, int? variant = null)
+ : base(settings, ruleset, variant)
+ {
+ }
+
+ protected override void InitialiseDefaults()
+ {
+ base.InitialiseDefaults();
+
+ SetDefault(TaikoRulesetSetting.TouchControlScheme, TaikoTouchControlScheme.KDDK);
+ }
+ }
+
+ public enum TaikoRulesetSetting
+ {
+ TouchControlScheme
+ }
+}
diff --git a/osu.Game.Rulesets.Taiko/Configuration/TaikoTouchControlScheme.cs b/osu.Game.Rulesets.Taiko/Configuration/TaikoTouchControlScheme.cs
new file mode 100644
index 0000000000..74e4a53746
--- /dev/null
+++ b/osu.Game.Rulesets.Taiko/Configuration/TaikoTouchControlScheme.cs
@@ -0,0 +1,12 @@
+// 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.Taiko.Configuration
+{
+ public enum TaikoTouchControlScheme
+ {
+ KDDK,
+ DDKK,
+ KKDD
+ }
+}
diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModClassic.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModClassic.cs
index f7fdd447d6..d0361b1c8d 100644
--- a/osu.Game.Rulesets.Taiko/Mods/TaikoModClassic.cs
+++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModClassic.cs
@@ -1,38 +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.Diagnostics;
using osu.Game.Rulesets.Mods;
+using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Taiko.Objects;
+using osu.Game.Rulesets.Taiko.Objects.Drawables;
using osu.Game.Rulesets.Taiko.UI;
using osu.Game.Rulesets.UI;
namespace osu.Game.Rulesets.Taiko.Mods
{
- public class TaikoModClassic : ModClassic, IApplicableToDrawableRuleset, IUpdatableByPlayfield
+ public class TaikoModClassic : ModClassic, IApplicableToDrawableRuleset, IApplicableToDrawableHitObject
{
- private DrawableTaikoRuleset? drawableTaikoRuleset;
-
public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset)
{
- drawableTaikoRuleset = (DrawableTaikoRuleset)drawableRuleset;
- drawableTaikoRuleset.LockPlayfieldAspect.Value = false;
+ var drawableTaikoRuleset = (DrawableTaikoRuleset)drawableRuleset;
+ drawableTaikoRuleset.LockPlayfieldMaxAspect.Value = false;
var playfield = (TaikoPlayfield)drawableRuleset.Playfield;
playfield.ClassicHitTargetPosition.Value = true;
}
- public void Update(Playfield playfield)
+ public void ApplyToDrawableHitObject(DrawableHitObject drawable)
{
- Debug.Assert(drawableTaikoRuleset != null);
-
- // Classic taiko scrolls at a constant 100px per 1000ms. More notes become visible as the playfield is lengthened.
- const float scroll_rate = 10;
-
- // Since the time range will depend on a positional value, it is referenced to the x480 pixel space.
- float ratio = drawableTaikoRuleset.DrawHeight / 480;
-
- drawableTaikoRuleset.TimeRange.Value = (playfield.HitObjectContainer.DrawWidth / ratio) * scroll_rate;
+ if (drawable is DrawableTaikoHitObject hit)
+ hit.SnapJudgementLocation = true;
}
}
}
diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs
index 45e25ee7dc..abecd19545 100644
--- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs
@@ -37,8 +37,6 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
protected override SkinnableDrawable CreateMainPiece() => new SkinnableDrawable(new TaikoSkinComponentLookup(TaikoSkinComponents.DrumRollTick), _ => new TickPiece());
- public override double MaximumJudgementOffset => HitObject.HitWindow;
-
protected override void OnApply()
{
base.OnApply();
diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs
index ff4edf35fa..62c8457c58 100644
--- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs
@@ -207,6 +207,9 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
const float gravity_time = 300;
const float gravity_travel_height = 200;
+ if (SnapJudgementLocation)
+ MainPiece.MoveToX(-X);
+
this.ScaleTo(0.8f, gravity_time * 2, Easing.OutQuad);
this.MoveToY(-gravity_travel_height, gravity_time, Easing.Out)
diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs
index 6172b75d2c..f695c505a4 100644
--- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs
@@ -25,6 +25,15 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
private readonly Container nonProxiedContent;
+ ///
+ /// Whether the location of the hit should be snapped to the hit target before animating.
+ ///
+ ///
+ /// This is how osu-stable worked, but notably is not how TnT works.
+ /// Not snapping results in less visual feedback on hit accuracy.
+ ///
+ public bool SnapJudgementLocation { get; set; }
+
protected DrawableTaikoHitObject([CanBeNull] TaikoHitObject hitObject)
: base(hitObject)
{
diff --git a/osu.Game.Rulesets.Taiko/Objects/DrumRollTick.cs b/osu.Game.Rulesets.Taiko/Objects/DrumRollTick.cs
index 433fdab908..6bcb8674e6 100644
--- a/osu.Game.Rulesets.Taiko/Objects/DrumRollTick.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/DrumRollTick.cs
@@ -31,6 +31,8 @@ namespace osu.Game.Rulesets.Taiko.Objects
protected override HitWindows CreateHitWindows() => HitWindows.Empty;
+ public override double MaximumJudgementOffset => HitWindow;
+
protected override StrongNestedHitObject CreateStrongNestedHit(double startTime) => new StrongNestedHit { StartTime = startTime };
public class StrongNestedHit : StrongNestedHitObject
diff --git a/osu.Game.Rulesets.Taiko/Resources/Testing/Beatmaps/file-hitsamples-expected-conversion.json b/osu.Game.Rulesets.Taiko/Resources/Testing/Beatmaps/file-hitsamples-expected-conversion.json
new file mode 100644
index 0000000000..70348a3871
--- /dev/null
+++ b/osu.Game.Rulesets.Taiko/Resources/Testing/Beatmaps/file-hitsamples-expected-conversion.json
@@ -0,0 +1 @@
+{"Mappings":[{"StartTime":500.0,"Objects":[{"StartTime":500.0,"EndTime":500.0,"IsRim":false,"IsCentre":true,"IsDrumRoll":false,"IsSwell":false,"IsStrong":false}]},{"StartTime":1000.0,"Objects":[{"StartTime":1000.0,"EndTime":1000.0,"IsRim":true,"IsCentre":false,"IsDrumRoll":false,"IsSwell":false,"IsStrong":false}]},{"StartTime":1500.0,"Objects":[{"StartTime":1500.0,"EndTime":1500.0,"IsRim":true,"IsCentre":false,"IsDrumRoll":false,"IsSwell":false,"IsStrong":false}]},{"StartTime":2000.0,"Objects":[{"StartTime":2000.0,"EndTime":2000.0,"IsRim":true,"IsCentre":false,"IsDrumRoll":false,"IsSwell":false,"IsStrong":false}]},{"StartTime":2500.0,"Objects":[{"StartTime":2500.0,"EndTime":2500.0,"IsRim":false,"IsCentre":true,"IsDrumRoll":false,"IsSwell":false,"IsStrong":true}]},{"StartTime":3000.0,"Objects":[{"StartTime":3000.0,"EndTime":3000.0,"IsRim":true,"IsCentre":false,"IsDrumRoll":false,"IsSwell":false,"IsStrong":true}]},{"StartTime":3500.0,"Objects":[{"StartTime":3500.0,"EndTime":3500.0,"IsRim":true,"IsCentre":false,"IsDrumRoll":false,"IsSwell":false,"IsStrong":true}]},{"StartTime":4000.0,"Objects":[{"StartTime":4000.0,"EndTime":4000.0,"IsRim":true,"IsCentre":false,"IsDrumRoll":false,"IsSwell":false,"IsStrong":true}]}]}
\ No newline at end of file
diff --git a/osu.Game.Rulesets.Taiko/Resources/Testing/Beatmaps/file-hitsamples.osu b/osu.Game.Rulesets.Taiko/Resources/Testing/Beatmaps/file-hitsamples.osu
new file mode 100644
index 0000000000..5d4bcb52a1
--- /dev/null
+++ b/osu.Game.Rulesets.Taiko/Resources/Testing/Beatmaps/file-hitsamples.osu
@@ -0,0 +1,22 @@
+osu file format v14
+
+[Difficulty]
+HPDrainRate:5
+CircleSize:7
+OverallDifficulty:6.5
+ApproachRate:10
+SliderMultiplier:1.9
+SliderTickRate:1
+
+[TimingPoints]
+500,500,4,2,1,50,1,0
+
+[HitObjects]
+256,192,500,1,0,0:0:0:0:sample.ogg
+256,192,1000,1,8,0:0:0:0:sample.ogg
+256,192,1500,1,2,0:0:0:0:sample.ogg
+256,192,2000,1,10,0:0:0:0:sample.ogg
+256,192,2500,1,4,0:0:0:0:sample.ogg
+256,192,3000,1,12,0:0:0:0:sample.ogg
+256,192,3500,1,6,0:0:0:0:sample.ogg
+256,192,4000,1,14,0:0:0:0:sample.ogg
diff --git a/osu.Game.Rulesets.Taiko/Skinning/Argon/ArgonJudgementPiece.cs b/osu.Game.Rulesets.Taiko/Skinning/Argon/ArgonJudgementPiece.cs
index 6756001089..bbd62ff85b 100644
--- a/osu.Game.Rulesets.Taiko/Skinning/Argon/ArgonJudgementPiece.cs
+++ b/osu.Game.Rulesets.Taiko/Skinning/Argon/ArgonJudgementPiece.cs
@@ -3,7 +3,6 @@
using System;
using osu.Framework.Allocation;
-using osu.Framework.Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
@@ -18,20 +17,16 @@ using osuTK.Graphics;
namespace osu.Game.Rulesets.Taiko.Skinning.Argon
{
- public partial class ArgonJudgementPiece : CompositeDrawable, IAnimatableJudgement
+ public partial class ArgonJudgementPiece : JudgementPiece, IAnimatableJudgement
{
- protected readonly HitResult Result;
-
- protected SpriteText JudgementText { get; private set; } = null!;
-
private RingExplosion? ringExplosion;
[Resolved]
private OsuColour colours { get; set; } = null!;
public ArgonJudgementPiece(HitResult result)
+ : base(result)
{
- Result = result;
RelativePositionAxes = Axes.Both;
RelativeSizeAxes = Axes.Both;
}
@@ -39,21 +34,6 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Argon
[BackgroundDependencyLoader]
private void load()
{
- InternalChildren = new Drawable[]
- {
- JudgementText = new OsuSpriteText
- {
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- Text = Result.GetDescription().ToUpperInvariant(),
- Colour = colours.ForHitResult(Result),
- Blending = BlendingParameters.Additive,
- Spacing = new Vector2(10, 0),
- RelativePositionAxes = Axes.Both,
- Font = OsuFont.Default.With(size: 20, weight: FontWeight.Regular),
- },
- };
-
if (Result.IsHit())
{
AddInternal(ringExplosion = new RingExplosion(Result)
@@ -64,6 +44,17 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Argon
}
}
+ protected override SpriteText CreateJudgementText() =>
+ new OsuSpriteText
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Blending = BlendingParameters.Additive,
+ Spacing = new Vector2(10, 0),
+ RelativePositionAxes = Axes.Both,
+ Font = OsuFont.Default.With(size: 20, weight: FontWeight.Regular),
+ };
+
///
/// Plays the default animation for this judgement piece.
///
diff --git a/osu.Game.Rulesets.Taiko/Skinning/Argon/TaikoArgonSkinTransformer.cs b/osu.Game.Rulesets.Taiko/Skinning/Argon/TaikoArgonSkinTransformer.cs
index a5d091a1c8..780018af4e 100644
--- a/osu.Game.Rulesets.Taiko/Skinning/Argon/TaikoArgonSkinTransformer.cs
+++ b/osu.Game.Rulesets.Taiko/Skinning/Argon/TaikoArgonSkinTransformer.cs
@@ -19,6 +19,10 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Argon
switch (component)
{
case GameplaySkinComponentLookup resultComponent:
+ // This should eventually be moved to a skin setting, when supported.
+ if (Skin is ArgonProSkin && resultComponent.Component >= HitResult.Great)
+ return Drawable.Empty();
+
return new ArgonJudgementPiece(resultComponent.Component);
case TaikoSkinComponentLookup taikoComponent:
diff --git a/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyKiaiGlow.cs b/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyKiaiGlow.cs
new file mode 100644
index 0000000000..623243e9e1
--- /dev/null
+++ b/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyKiaiGlow.cs
@@ -0,0 +1,65 @@
+// 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.Allocation;
+using osu.Framework.Audio.Track;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Sprites;
+using osu.Game.Beatmaps.ControlPoints;
+using osu.Game.Graphics.Containers;
+using osu.Game.Rulesets.Judgements;
+using osu.Game.Rulesets.Scoring;
+using osu.Game.Skinning;
+using osuTK;
+
+namespace osu.Game.Rulesets.Taiko.Skinning.Legacy
+{
+ internal partial class LegacyKiaiGlow : BeatSyncedContainer
+ {
+ private bool isKiaiActive;
+
+ private Sprite sprite = null!;
+
+ [BackgroundDependencyLoader(true)]
+ private void load(ISkinSource skin, HealthProcessor? healthProcessor)
+ {
+ Child = sprite = new Sprite
+ {
+ Texture = skin.GetTexture("taiko-glow"),
+ Origin = Anchor.Centre,
+ Anchor = Anchor.Centre,
+ Alpha = 0,
+ Scale = new Vector2(0.7f),
+ Colour = new Colour4(255, 228, 0, 255),
+ };
+
+ if (healthProcessor != null)
+ healthProcessor.NewJudgement += onNewJudgement;
+ }
+
+ protected override void Update()
+ {
+ base.Update();
+
+ if (isKiaiActive)
+ sprite.Alpha = (float)Math.Min(1, sprite.Alpha + Math.Abs(Clock.ElapsedFrameTime) / 100f);
+ else
+ sprite.Alpha = (float)Math.Max(0, sprite.Alpha - Math.Abs(Clock.ElapsedFrameTime) / 600f);
+ }
+
+ protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, ChannelAmplitudes amplitudes)
+ {
+ isKiaiActive = effectPoint.KiaiMode;
+ }
+
+ private void onNewJudgement(JudgementResult result)
+ {
+ if (!result.IsHit || !isKiaiActive)
+ return;
+
+ sprite.ScaleTo(0.85f).Then()
+ .ScaleTo(0.7f, 80, Easing.OutQuad);
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Taiko/Skinning/Legacy/TaikoLegacyPlayfieldBackgroundRight.cs b/osu.Game.Rulesets.Taiko/Skinning/Legacy/TaikoLegacyPlayfieldBackgroundRight.cs
index 86175d3bca..85870d0fd6 100644
--- a/osu.Game.Rulesets.Taiko/Skinning/Legacy/TaikoLegacyPlayfieldBackgroundRight.cs
+++ b/osu.Game.Rulesets.Taiko/Skinning/Legacy/TaikoLegacyPlayfieldBackgroundRight.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 osu.Framework.Allocation;
using osu.Framework.Audio.Track;
using osu.Framework.Graphics;
@@ -16,7 +17,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy
{
private Sprite kiai = null!;
- private bool kiaiDisplayed;
+ private bool isKiaiActive;
[BackgroundDependencyLoader]
private void load(ISkinSource skin)
@@ -41,17 +42,19 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy
};
}
+ protected override void Update()
+ {
+ base.Update();
+
+ if (isKiaiActive)
+ kiai.Alpha = (float)Math.Min(1, kiai.Alpha + Math.Abs(Clock.ElapsedFrameTime) / 200f);
+ else
+ kiai.Alpha = (float)Math.Max(0, kiai.Alpha - Math.Abs(Clock.ElapsedFrameTime) / 200f);
+ }
+
protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, ChannelAmplitudes amplitudes)
{
- base.OnNewBeat(beatIndex, timingPoint, effectPoint, amplitudes);
-
- if (effectPoint.KiaiMode != kiaiDisplayed)
- {
- kiaiDisplayed = effectPoint.KiaiMode;
-
- kiai.ClearTransforms();
- kiai.FadeTo(kiaiDisplayed ? 1 : 0, 200);
- }
+ isKiaiActive = effectPoint.KiaiMode;
}
}
}
diff --git a/osu.Game.Rulesets.Taiko/Skinning/Legacy/TaikoLegacySkinTransformer.cs b/osu.Game.Rulesets.Taiko/Skinning/Legacy/TaikoLegacySkinTransformer.cs
index 7bf99306f0..d61f9ac35d 100644
--- a/osu.Game.Rulesets.Taiko/Skinning/Legacy/TaikoLegacySkinTransformer.cs
+++ b/osu.Game.Rulesets.Taiko/Skinning/Legacy/TaikoLegacySkinTransformer.cs
@@ -129,6 +129,12 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy
case TaikoSkinComponents.Mascot:
return new DrawableTaikoMascot();
+ case TaikoSkinComponents.KiaiGlow:
+ if (GetTexture("taiko-glow") != null)
+ return new LegacyKiaiGlow();
+
+ return null;
+
default:
throw new UnsupportedSkinComponentException(lookup);
}
diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs
index fe12cf9765..4f435e73b3 100644
--- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs
+++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs
@@ -28,9 +28,13 @@ using osu.Game.Rulesets.Taiko.Skinning.Argon;
using osu.Game.Rulesets.Taiko.Skinning.Legacy;
using osu.Game.Rulesets.Taiko.UI;
using osu.Game.Rulesets.UI;
+using osu.Game.Overlays.Settings;
using osu.Game.Scoring;
using osu.Game.Screens.Ranking.Statistics;
using osu.Game.Skinning;
+using osu.Game.Rulesets.Configuration;
+using osu.Game.Configuration;
+using osu.Game.Rulesets.Taiko.Configuration;
namespace osu.Game.Rulesets.Taiko
{
@@ -144,6 +148,7 @@ namespace osu.Game.Rulesets.Taiko
new MultiMod(new TaikoModDoubleTime(), new TaikoModNightcore()),
new TaikoModHidden(),
new TaikoModFlashlight(),
+ new ModAccuracyChallenge(),
};
case ModType.Conversion:
@@ -193,6 +198,10 @@ namespace osu.Game.Rulesets.Taiko
public override IConvertibleReplayFrame CreateConvertibleReplayFrame() => new TaikoReplayFrame();
+ public override IRulesetConfigManager CreateConfig(SettingsStore? settings) => new TaikoRulesetConfigManager(settings, RulesetInfo);
+
+ public override RulesetSettingsSubsection CreateSettings() => new TaikoSettingsSubsection(this);
+
protected override IEnumerable GetValidHitResults()
{
return new[]
@@ -200,9 +209,8 @@ namespace osu.Game.Rulesets.Taiko
HitResult.Great,
HitResult.Ok,
- HitResult.SmallTickHit,
-
HitResult.SmallBonus,
+ HitResult.LargeBonus,
};
}
@@ -211,6 +219,9 @@ namespace osu.Game.Rulesets.Taiko
switch (result)
{
case HitResult.SmallBonus:
+ return "drum tick";
+
+ case HitResult.LargeBonus:
return "bonus";
}
diff --git a/osu.Game.Rulesets.Taiko/TaikoSettingsSubsection.cs b/osu.Game.Rulesets.Taiko/TaikoSettingsSubsection.cs
new file mode 100644
index 0000000000..9fe861c08c
--- /dev/null
+++ b/osu.Game.Rulesets.Taiko/TaikoSettingsSubsection.cs
@@ -0,0 +1,36 @@
+// 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.Allocation;
+using osu.Framework.Graphics;
+using osu.Framework.Localisation;
+using osu.Game.Overlays.Settings;
+using osu.Game.Rulesets.Taiko.Configuration;
+
+namespace osu.Game.Rulesets.Taiko
+{
+ public partial class TaikoSettingsSubsection : RulesetSettingsSubsection
+ {
+ protected override LocalisableString Header => "osu!taiko";
+
+ public TaikoSettingsSubsection(TaikoRuleset ruleset)
+ : base(ruleset)
+ {
+ }
+
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ var config = (TaikoRulesetConfigManager)Config;
+
+ Children = new Drawable[]
+ {
+ new SettingsEnumDropdown
+ {
+ LabelText = "Touch control scheme",
+ Current = config.GetBindable(TaikoRulesetSetting.TouchControlScheme)
+ }
+ };
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Taiko/TaikoSkinComponents.cs b/osu.Game.Rulesets.Taiko/TaikoSkinComponents.cs
index bf48898dd2..b8e3313e1b 100644
--- a/osu.Game.Rulesets.Taiko/TaikoSkinComponents.cs
+++ b/osu.Game.Rulesets.Taiko/TaikoSkinComponents.cs
@@ -21,5 +21,6 @@ namespace osu.Game.Rulesets.Taiko
TaikoExplosionKiai,
Scroller,
Mascot,
+ KiaiGlow
}
}
diff --git a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs
index 32a83d87b8..40203440c5 100644
--- a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs
+++ b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs
@@ -31,7 +31,7 @@ namespace osu.Game.Rulesets.Taiko.UI
{
public new BindableDouble TimeRange => base.TimeRange;
- public readonly BindableBool LockPlayfieldAspect = new BindableBool(true);
+ public readonly BindableBool LockPlayfieldMaxAspect = new BindableBool(true);
protected override ScrollVisualisationMethod VisualisationMethod => ScrollVisualisationMethod.Overlapping;
@@ -43,7 +43,6 @@ namespace osu.Game.Rulesets.Taiko.UI
: base(ruleset, beatmap, mods)
{
Direction.Value = ScrollingDirection.Left;
- TimeRange.Value = 7000;
}
[BackgroundDependencyLoader]
@@ -60,6 +59,19 @@ namespace osu.Game.Rulesets.Taiko.UI
KeyBindingInputManager.Add(new DrumTouchInputArea());
}
+ protected override void Update()
+ {
+ base.Update();
+
+ // Taiko scrolls at a constant 100px per 1000ms. More notes become visible as the playfield is lengthened.
+ const float scroll_rate = 10;
+
+ // Since the time range will depend on a positional value, it is referenced to the x480 pixel space.
+ float ratio = DrawHeight / 480;
+
+ TimeRange.Value = (Playfield.HitObjectContainer.DrawWidth / ratio) * scroll_rate;
+ }
+
protected override void UpdateAfterChildren()
{
base.UpdateAfterChildren();
@@ -78,7 +90,7 @@ namespace osu.Game.Rulesets.Taiko.UI
public override PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer() => new TaikoPlayfieldAdjustmentContainer
{
- LockPlayfieldAspect = { BindTarget = LockPlayfieldAspect }
+ LockPlayfieldMaxAspect = { BindTarget = LockPlayfieldMaxAspect }
};
protected override PassThroughInputManager CreateInputManager() => new TaikoInputManager(Ruleset.RulesetInfo);
diff --git a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs
index ab8c0a484e..29ccd96675 100644
--- a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs
+++ b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.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.Diagnostics;
using osu.Framework.Allocation;
+using osu.Framework.Bindables;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
@@ -11,6 +13,7 @@ using osu.Framework.Graphics.Shapes;
using osu.Framework.Input.Bindings;
using osu.Framework.Input.Events;
using osu.Game.Graphics;
+using osu.Game.Rulesets.Taiko.Configuration;
using osuTK;
using osuTK.Graphics;
@@ -31,15 +34,18 @@ namespace osu.Game.Rulesets.Taiko.UI
private Container mainContent = null!;
- private QuarterCircle leftCentre = null!;
- private QuarterCircle rightCentre = null!;
- private QuarterCircle leftRim = null!;
- private QuarterCircle rightRim = null!;
+ private DrumSegment leftCentre = null!;
+ private DrumSegment rightCentre = null!;
+ private DrumSegment leftRim = null!;
+ private DrumSegment rightRim = null!;
+
+ private readonly Bindable configTouchControlScheme = new Bindable();
[BackgroundDependencyLoader]
- private void load(TaikoInputManager taikoInputManager, OsuColour colours)
+ private void load(TaikoInputManager taikoInputManager, TaikoRulesetConfigManager config)
{
Debug.Assert(taikoInputManager.KeyBindingContainer != null);
+
keyBindingContainer = taikoInputManager.KeyBindingContainer;
// Container should handle input everywhere.
@@ -65,27 +71,27 @@ namespace osu.Game.Rulesets.Taiko.UI
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
{
- leftRim = new QuarterCircle(TaikoAction.LeftRim, colours.Blue)
+ leftRim = new DrumSegment
{
Anchor = Anchor.BottomCentre,
Origin = Anchor.BottomRight,
X = -2,
},
- rightRim = new QuarterCircle(TaikoAction.RightRim, colours.Blue)
+ rightRim = new DrumSegment
{
Anchor = Anchor.BottomCentre,
Origin = Anchor.BottomRight,
X = 2,
Rotation = 90,
},
- leftCentre = new QuarterCircle(TaikoAction.LeftCentre, colours.Pink)
+ leftCentre = new DrumSegment
{
Anchor = Anchor.BottomCentre,
Origin = Anchor.BottomRight,
X = -2,
Scale = new Vector2(centre_region),
},
- rightCentre = new QuarterCircle(TaikoAction.RightCentre, colours.Pink)
+ rightCentre = new DrumSegment
{
Anchor = Anchor.BottomCentre,
Origin = Anchor.BottomRight,
@@ -98,6 +104,17 @@ namespace osu.Game.Rulesets.Taiko.UI
}
},
};
+
+ config.BindWith(TaikoRulesetSetting.TouchControlScheme, configTouchControlScheme);
+ configTouchControlScheme.BindValueChanged(scheme =>
+ {
+ var actions = getOrderedActionsForScheme(scheme.NewValue);
+
+ leftRim.Action = actions[0];
+ leftCentre.Action = actions[1];
+ rightCentre.Action = actions[2];
+ rightRim.Action = actions[3];
+ }, true);
}
protected override bool OnKeyDown(KeyDownEvent e)
@@ -107,24 +124,6 @@ namespace osu.Game.Rulesets.Taiko.UI
return false;
}
- protected override bool OnMouseDown(MouseDownEvent e)
- {
- if (!validMouse(e))
- return false;
-
- handleDown(e.Button, e.ScreenSpaceMousePosition);
- return true;
- }
-
- protected override void OnMouseUp(MouseUpEvent e)
- {
- if (!validMouse(e))
- return;
-
- handleUp(e.Button);
- base.OnMouseUp(e);
- }
-
protected override bool OnTouchDown(TouchDownEvent e)
{
handleDown(e.Touch.Source, e.ScreenSpaceTouchDownPosition);
@@ -137,11 +136,47 @@ namespace osu.Game.Rulesets.Taiko.UI
base.OnTouchUp(e);
}
+ private static TaikoAction[] getOrderedActionsForScheme(TaikoTouchControlScheme scheme)
+ {
+ switch (scheme)
+ {
+ case TaikoTouchControlScheme.KDDK:
+ return new[]
+ {
+ TaikoAction.LeftRim,
+ TaikoAction.LeftCentre,
+ TaikoAction.RightCentre,
+ TaikoAction.RightRim
+ };
+
+ case TaikoTouchControlScheme.DDKK:
+ return new[]
+ {
+ TaikoAction.LeftCentre,
+ TaikoAction.RightCentre,
+ TaikoAction.LeftRim,
+ TaikoAction.RightRim
+ };
+
+ case TaikoTouchControlScheme.KKDD:
+ return new[]
+ {
+ TaikoAction.LeftRim,
+ TaikoAction.RightRim,
+ TaikoAction.LeftCentre,
+ TaikoAction.RightCentre
+ };
+
+ default:
+ throw new ArgumentOutOfRangeException(nameof(scheme), scheme, null);
+ }
+ }
+
private void handleDown(object source, Vector2 position)
{
Show();
- TaikoAction taikoAction = getTaikoActionFromInput(position);
+ TaikoAction taikoAction = getTaikoActionFromPosition(position);
// Not too sure how this can happen, but let's avoid throwing.
if (trackedActions.ContainsKey(source))
@@ -157,18 +192,15 @@ namespace osu.Game.Rulesets.Taiko.UI
trackedActions.Remove(source);
}
- private bool validMouse(MouseButtonEvent e) =>
- leftRim.Contains(e.ScreenSpaceMouseDownPosition) || rightRim.Contains(e.ScreenSpaceMouseDownPosition);
-
- private TaikoAction getTaikoActionFromInput(Vector2 inputPosition)
+ private TaikoAction getTaikoActionFromPosition(Vector2 inputPosition)
{
bool centreHit = leftCentre.Contains(inputPosition) || rightCentre.Contains(inputPosition);
bool leftSide = ToLocalSpace(inputPosition).X < DrawWidth / 2;
if (leftSide)
- return centreHit ? TaikoAction.LeftCentre : TaikoAction.LeftRim;
+ return centreHit ? leftCentre.Action : leftRim.Action;
- return centreHit ? TaikoAction.RightCentre : TaikoAction.RightRim;
+ return centreHit ? rightCentre.Action : rightRim.Action;
}
protected override void PopIn()
@@ -181,23 +213,42 @@ namespace osu.Game.Rulesets.Taiko.UI
mainContent.FadeOut(300);
}
- private partial class QuarterCircle : CompositeDrawable, IKeyBindingHandler
+ private partial class DrumSegment : CompositeDrawable, IKeyBindingHandler
{
- private readonly Circle overlay;
+ private TaikoAction action;
- private readonly TaikoAction handledAction;
+ public TaikoAction Action
+ {
+ get => action;
+ set
+ {
+ if (action == value)
+ return;
- private readonly Circle circle;
+ action = value;
+ updateColoursFromAction();
+ }
+ }
+
+ private Circle overlay = null!;
+
+ private Circle circle = null!;
+
+ [Resolved]
+ private OsuColour colours { get; set; } = null!;
public override bool Contains(Vector2 screenSpacePos) => circle.Contains(screenSpacePos);
- public QuarterCircle(TaikoAction handledAction, Color4 colour)
+ public DrumSegment()
{
- this.handledAction = handledAction;
RelativeSizeAxes = Axes.Both;
FillMode = FillMode.Fit;
+ }
+ [BackgroundDependencyLoader]
+ private void load()
+ {
InternalChildren = new Drawable[]
{
new Container
@@ -209,7 +260,6 @@ namespace osu.Game.Rulesets.Taiko.UI
circle = new Circle
{
RelativeSizeAxes = Axes.Both,
- Colour = colour.Multiply(1.4f).Darken(2.8f),
Alpha = 0.8f,
Scale = new Vector2(2),
},
@@ -218,7 +268,6 @@ namespace osu.Game.Rulesets.Taiko.UI
Alpha = 0,
RelativeSizeAxes = Axes.Both,
Blending = BlendingParameters.Additive,
- Colour = colour,
Scale = new Vector2(2),
}
}
@@ -226,18 +275,52 @@ namespace osu.Game.Rulesets.Taiko.UI
};
}
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+
+ updateColoursFromAction();
+ }
+
public bool OnPressed(KeyBindingPressEvent e)
{
- if (e.Action == handledAction)
+ if (e.Action == Action)
overlay.FadeTo(1f, 80, Easing.OutQuint);
return false;
}
public void OnReleased(KeyBindingReleaseEvent e)
{
- if (e.Action == handledAction)
+ if (e.Action == Action)
overlay.FadeOut(1000, Easing.OutQuint);
}
+
+ private void updateColoursFromAction()
+ {
+ if (!IsLoaded)
+ return;
+
+ var colour = getColourFromTaikoAction(Action);
+
+ circle.Colour = colour.Multiply(1.4f).Darken(2.8f);
+ overlay.Colour = colour;
+ }
+
+ private Color4 getColourFromTaikoAction(TaikoAction handledAction)
+ {
+ switch (handledAction)
+ {
+ case TaikoAction.LeftRim:
+ case TaikoAction.RightRim:
+ return colours.Blue;
+
+ case TaikoAction.LeftCentre:
+ case TaikoAction.RightCentre:
+ return colours.Pink;
+ }
+
+ throw new ArgumentOutOfRangeException();
+ }
}
}
}
diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs
index 6ce0be5868..9f9debe7d7 100644
--- a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs
+++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs
@@ -112,6 +112,10 @@ namespace osu.Game.Rulesets.Taiko.UI
FillMode = FillMode.Fit,
Children = new[]
{
+ new SkinnableDrawable(new TaikoSkinComponentLookup(TaikoSkinComponents.KiaiGlow), _ => Empty())
+ {
+ RelativeSizeAxes = Axes.Both,
+ },
hitExplosionContainer = new Container
{
RelativeSizeAxes = Axes.Both,
@@ -186,7 +190,7 @@ namespace osu.Game.Rulesets.Taiko.UI
var hitWindows = new TaikoHitWindows();
- foreach (var result in Enum.GetValues(typeof(HitResult)).OfType().Where(r => hitWindows.IsHitResultAllowed(r)))
+ foreach (var result in Enum.GetValues().Where(r => hitWindows.IsHitResultAllowed(r)))
{
judgementPools.Add(result, new DrawablePool(15));
explosionPools.Add(result, new HitExplosionPool(result));
diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfieldAdjustmentContainer.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfieldAdjustmentContainer.cs
index 79c5c36e08..42732d90e4 100644
--- a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfieldAdjustmentContainer.cs
+++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfieldAdjustmentContainer.cs
@@ -13,7 +13,7 @@ namespace osu.Game.Rulesets.Taiko.UI
private const float default_relative_height = TaikoPlayfield.DEFAULT_HEIGHT / 768;
private const float default_aspect = 16f / 9f;
- public readonly IBindable LockPlayfieldAspect = new BindableBool(true);
+ public readonly IBindable LockPlayfieldMaxAspect = new BindableBool(true);
protected override void Update()
{
@@ -21,7 +21,12 @@ namespace osu.Game.Rulesets.Taiko.UI
float height = default_relative_height;
- if (LockPlayfieldAspect.Value)
+ // Players coming from stable expect to be able to change the aspect ratio regardless of the window size.
+ // We originally wanted to limit this more, but there was considerable pushback from the community.
+ //
+ // As a middle-ground, the aspect ratio can still be adjusted in the downwards direction but has a maximum limit.
+ // This is still a bit weird, because readability changes with window size, but it is what it is.
+ if (LockPlayfieldMaxAspect.Value && Parent.ChildSize.X / Parent.ChildSize.Y > default_aspect)
height *= Math.Clamp(Parent.ChildSize.X / Parent.ChildSize.Y, 0.4f, 4) / default_aspect;
Height = height;
diff --git a/osu.Game.Rulesets.Taiko/osu.Game.Rulesets.Taiko.csproj b/osu.Game.Rulesets.Taiko/osu.Game.Rulesets.Taiko.csproj
index b2953106d8..a773f3c5b4 100644
--- a/osu.Game.Rulesets.Taiko/osu.Game.Rulesets.Taiko.csproj
+++ b/osu.Game.Rulesets.Taiko/osu.Game.Rulesets.Taiko.csproj
@@ -1,6 +1,6 @@
- netstandard2.1
+ net6.0
Library
true
bash the drum. to the beat.
diff --git a/osu.Game.Tests.Android/Properties/AndroidManifest.xml b/osu.Game.Tests.Android/AndroidManifest.xml
similarity index 95%
rename from osu.Game.Tests.Android/Properties/AndroidManifest.xml
rename to osu.Game.Tests.Android/AndroidManifest.xml
index 4a63f0c357..f25b2e5328 100644
--- a/osu.Game.Tests.Android/Properties/AndroidManifest.xml
+++ b/osu.Game.Tests.Android/AndroidManifest.xml
@@ -1,5 +1,5 @@
-
+
\ No newline at end of file
diff --git a/osu.Game.Tests.Android/MainActivity.cs b/osu.Game.Tests.Android/MainActivity.cs
index 6c4f9bac58..bdb947fbb4 100644
--- a/osu.Game.Tests.Android/MainActivity.cs
+++ b/osu.Game.Tests.Android/MainActivity.cs
@@ -3,7 +3,9 @@
#nullable disable
+using System.Reflection;
using Android.App;
+using Android.OS;
using osu.Framework.Android;
namespace osu.Game.Tests.Android
@@ -12,5 +14,16 @@ namespace osu.Game.Tests.Android
public class MainActivity : AndroidGameActivity
{
protected override Framework.Game CreateGame() => new OsuTestBrowser();
+
+ protected override void OnCreate(Bundle savedInstanceState)
+ {
+ base.OnCreate(savedInstanceState);
+
+ // See the comment in OsuGameActivity
+ Assembly.Load("osu.Game.Rulesets.Osu");
+ Assembly.Load("osu.Game.Rulesets.Taiko");
+ Assembly.Load("osu.Game.Rulesets.Catch");
+ Assembly.Load("osu.Game.Rulesets.Mania");
+ }
}
}
diff --git a/osu.Game.Tests.Android/osu.Game.Tests.Android.csproj b/osu.Game.Tests.Android/osu.Game.Tests.Android.csproj
index afafec6b1f..b745d91980 100644
--- a/osu.Game.Tests.Android/osu.Game.Tests.Android.csproj
+++ b/osu.Game.Tests.Android/osu.Game.Tests.Android.csproj
@@ -1,88 +1,34 @@
-
-
+
- Debug
- AnyCPU
- 8.0.30703
- 2.0
- {5CC222DC-5716-4499-B897-DCBDDA4A5CF9}
- {EFBA0AD7-5A72-4C68-AF49-83D382785DCF};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
- {122416d6-6b49-4ee2-a1e8-b825f31c79fe}
+ net6.0-android
+ Exe
osu.Game.Tests
osu.Game.Tests.Android
- Properties\AndroidManifest.xml
- armeabi-v7a;x86;arm64-v8a
-
-
-
-
-
-
$(NoWarn);CA2007
-
- None
- cjk;mideast;other;rare;west
- true
-
-
+
%(RecursiveDir)%(Filename)%(Extension)
-
-
+
+
%(RecursiveDir)%(Filename)%(Extension)
-
-
- %(RecursiveDir)%(Filename)%(Extension)
-
-
- %(RecursiveDir)%(Filename)%(Extension)
-
-
- %(RecursiveDir)%(Filename)%(Extension)
-
-
- %(RecursiveDir)%(Filename)%(Extension)
-
-
- %(RecursiveDir)%(Filename)%(Extension)
-
-
- %(RecursiveDir)%(Filename)%(Extension)
-
+ Android\%(RecursiveDir)%(Filename)%(Extension)
+
-
- {58f6c80c-1253-4a0e-a465-b8c85ebeadf3}
- osu.Game.Rulesets.Catch
-
-
- {48f4582b-7687-4621-9cbe-5c24197cb536}
- osu.Game.Rulesets.Mania
-
-
- {c92a607b-1fdd-4954-9f92-03ff547d9080}
- osu.Game.Rulesets.Osu
-
-
- {f167e17a-7de6-4af5-b920-a5112296c695}
- osu.Game.Rulesets.Taiko
-
-
- {2a66dd92-adb1-4994-89e2-c94e04acda0d}
- osu.Game
-
+
+
+
+
+
-
- 5.0.0
-
-
diff --git a/osu.Game.Tests.iOS/Application.cs b/osu.Game.Tests.iOS/Application.cs
index cf36fea139..4678be4fb8 100644
--- a/osu.Game.Tests.iOS/Application.cs
+++ b/osu.Game.Tests.iOS/Application.cs
@@ -3,7 +3,6 @@
#nullable disable
-using osu.Framework.iOS;
using UIKit;
namespace osu.Game.Tests.iOS
@@ -12,7 +11,7 @@ namespace osu.Game.Tests.iOS
{
public static void Main(string[] args)
{
- UIApplication.Main(args, typeof(GameUIApplication), typeof(AppDelegate));
+ UIApplication.Main(args, null, typeof(AppDelegate));
}
}
}
diff --git a/osu.Game.Tests.iOS/Info.plist b/osu.Game.Tests.iOS/Info.plist
index 31e2b3f257..ac661f6263 100644
--- a/osu.Game.Tests.iOS/Info.plist
+++ b/osu.Game.Tests.iOS/Info.plist
@@ -13,7 +13,7 @@
LSRequiresIPhoneOS
MinimumOSVersion
- 10.0
+ 13.4
UIDeviceFamily
1
diff --git a/osu.Game.Tests.iOS/osu.Game.Tests.iOS.csproj b/osu.Game.Tests.iOS/osu.Game.Tests.iOS.csproj
index 05b3cad6da..79771fcd50 100644
--- a/osu.Game.Tests.iOS/osu.Game.Tests.iOS.csproj
+++ b/osu.Game.Tests.iOS/osu.Game.Tests.iOS.csproj
@@ -1,54 +1,26 @@
-
-
+
- Debug
- iPhoneSimulator
Exe
- {65FF8E19-6934-469B-B690-23C6D6E56A17}
+ net6.0-ios
+ 13.4
osu.Game.Tests
osu.Game.Tests.iOS
-
-
-
- Linker.xml
-
-
-
%(RecursiveDir)%(Filename)%(Extension)
-
- $(NoWarn);CA2007
-
-
- {2A66DD92-ADB1-4994-89E2-C94E04ACDA0D}
- osu.Game
-
-
- {C92A607B-1FDD-4954-9F92-03FF547D9080}
- osu.Game.Rulesets.Osu
-
-
- {58F6C80C-1253-4A0E-A465-B8C85EBEADF3}
- osu.Game.Rulesets.Catch
-
-
- {48F4582B-7687-4621-9CBE-5C24197CB536}
- osu.Game.Rulesets.Mania
-
-
- {F167E17A-7DE6-4AF5-B920-A5112296C695}
- osu.Game.Rulesets.Taiko
-
+
+
+
+
+
-
diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs
index c6bdd25e8b..5787bd6066 100644
--- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs
+++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs
@@ -314,6 +314,24 @@ namespace osu.Game.Tests.Beatmaps.Formats
}
}
+ [Test]
+ public void TestGetLastObjectTime()
+ {
+ var decoder = new LegacyBeatmapDecoder();
+
+ using (var resStream = TestResources.OpenResource("mania-last-object-not-latest.osu"))
+ using (var stream = new LineBufferedReader(resStream))
+ {
+ var beatmap = decoder.Decode(stream);
+
+ Assert.That(beatmap.HitObjects.Last().StartTime, Is.EqualTo(2494));
+ Assert.That(beatmap.HitObjects.Last().GetEndTime(), Is.EqualTo(2494));
+
+ Assert.That(beatmap.HitObjects.Max(h => h.GetEndTime()), Is.EqualTo(2582));
+ Assert.That(beatmap.GetLastObjectTime(), Is.EqualTo(2582));
+ }
+ }
+
[Test]
public void TestDecodeBeatmapComboOffsetsOsu()
{
diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs
index cd6e5e7919..93cda34ef7 100644
--- a/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs
+++ b/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs
@@ -16,7 +16,9 @@ using osu.Game.Rulesets;
using osu.Game.Rulesets.Catch;
using osu.Game.Rulesets.Mania;
using osu.Game.Rulesets.Mania.Mods;
+using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu;
+using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Rulesets.Osu.Replays;
using osu.Game.Rulesets.Osu.UI;
using osu.Game.Rulesets.Replays;
@@ -179,6 +181,40 @@ namespace osu.Game.Tests.Beatmaps.Formats
});
}
+ [Test]
+ public void TestSoloScoreData()
+ {
+ var ruleset = new OsuRuleset().RulesetInfo;
+
+ var scoreInfo = TestResources.CreateTestScoreInfo(ruleset);
+ scoreInfo.Mods = new Mod[]
+ {
+ new OsuModDoubleTime { SpeedChange = { Value = 1.1 } }
+ };
+
+ var beatmap = new TestBeatmap(ruleset);
+ var score = new Score
+ {
+ ScoreInfo = scoreInfo,
+ Replay = new Replay
+ {
+ Frames = new List
+ {
+ new OsuReplayFrame(2000, OsuPlayfield.BASE_SIZE / 2, OsuAction.LeftButton)
+ }
+ }
+ };
+
+ var decodedAfterEncode = encodeThenDecode(LegacyBeatmapDecoder.LATEST_VERSION, score, beatmap);
+
+ Assert.Multiple(() =>
+ {
+ Assert.That(decodedAfterEncode.ScoreInfo.Statistics, Is.EqualTo(scoreInfo.Statistics));
+ Assert.That(decodedAfterEncode.ScoreInfo.MaximumStatistics, Is.EqualTo(scoreInfo.MaximumStatistics));
+ Assert.That(decodedAfterEncode.ScoreInfo.Mods, Is.EqualTo(scoreInfo.Mods));
+ });
+ }
+
private static Score encodeThenDecode(int beatmapVersion, Score score, TestBeatmap beatmap)
{
var encodeStream = new MemoryStream();
diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs
index d9e80fa111..281ea4e4ff 100644
--- a/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs
+++ b/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs
@@ -214,7 +214,9 @@ namespace osu.Game.Tests.Beatmaps.Formats
Assert.That(oneTime.EndTime, Is.EqualTo(4000 + loop_duration));
StoryboardSprite manyTimes = background.Elements.OfType().Single(s => s.Path == "many-times.png");
- Assert.That(manyTimes.EndTime, Is.EqualTo(9000 + 40 * loop_duration));
+ // It is intentional that we don't consider the loop count (40) as part of the end time calculation to match stable's handling.
+ // If we were to include the loop count, storyboards which loop for stupid long loop counts would continue playing the outro forever.
+ Assert.That(manyTimes.EndTime, Is.EqualTo(9000 + loop_duration));
}
}
}
diff --git a/osu.Game.Tests/Chat/MessageFormatterTests.cs b/osu.Game.Tests/Chat/MessageFormatterTests.cs
index ebfa9bd8b7..3c35dc311f 100644
--- a/osu.Game.Tests/Chat/MessageFormatterTests.cs
+++ b/osu.Game.Tests/Chat/MessageFormatterTests.cs
@@ -26,6 +26,16 @@ namespace osu.Game.Tests.Chat
MessageFormatter.WebsiteRootUrl = originalWebsiteRootUrl;
}
+ [Test]
+ public void TestUnsupportedProtocolLink()
+ {
+ Message result = MessageFormatter.FormatMessage(new Message { Content = "This is a gopher://really-old-protocol we don't support." });
+
+ Assert.AreEqual(result.Content, result.DisplayContent);
+ Assert.AreEqual(1, result.Links.Count);
+ Assert.AreEqual("gopher://really-old-protocol", result.Links[0].Url);
+ }
+
[Test]
public void TestBareLink()
{
diff --git a/osu.Game.Tests/Chat/TestSceneChannelManager.cs b/osu.Game.Tests/Chat/TestSceneChannelManager.cs
index cdd192cfe1..3a4c55c65c 100644
--- a/osu.Game.Tests/Chat/TestSceneChannelManager.cs
+++ b/osu.Game.Tests/Chat/TestSceneChannelManager.cs
@@ -75,6 +75,8 @@ namespace osu.Game.Tests.Chat
return false;
};
});
+
+ AddUntilStep("wait for notifications client", () => channelManager.NotificationsConnected);
}
[Test]
diff --git a/osu.Game.Tests/Database/BeatmapImporterTests.cs b/osu.Game.Tests/Database/BeatmapImporterTests.cs
index 56964aa8b2..446eb72b04 100644
--- a/osu.Game.Tests/Database/BeatmapImporterTests.cs
+++ b/osu.Game.Tests/Database/BeatmapImporterTests.cs
@@ -564,7 +564,7 @@ namespace osu.Game.Tests.Database
var imported = await importer.Import(
progressNotification,
- new ImportTask(zipStream, string.Empty)
+ new[] { new ImportTask(zipStream, string.Empty) }
);
realm.Run(r => r.Refresh());
@@ -1052,7 +1052,7 @@ namespace osu.Game.Tests.Database
{
string? temp = path ?? TestResources.GetTestBeatmapForImport(virtualTrack);
- var importedSet = await importer.Import(new ImportTask(temp), batchImport);
+ var importedSet = await importer.Import(new ImportTask(temp), new ImportParameters { Batch = batchImport });
Assert.NotNull(importedSet);
Debug.Assert(importedSet != null);
diff --git a/osu.Game.Tests/Editing/EditorChangeHandlerTest.cs b/osu.Game.Tests/Editing/BeatmapEditorChangeHandlerTest.cs
similarity index 77%
rename from osu.Game.Tests/Editing/EditorChangeHandlerTest.cs
rename to osu.Game.Tests/Editing/BeatmapEditorChangeHandlerTest.cs
index e1accd5b5f..80237fe6c8 100644
--- a/osu.Game.Tests/Editing/EditorChangeHandlerTest.cs
+++ b/osu.Game.Tests/Editing/BeatmapEditorChangeHandlerTest.cs
@@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using NUnit.Framework;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Beatmaps;
@@ -12,7 +10,7 @@ using osu.Game.Screens.Edit;
namespace osu.Game.Tests.Editing
{
[TestFixture]
- public class EditorChangeHandlerTest
+ public class BeatmapEditorChangeHandlerTest
{
private int stateChangedFired;
@@ -23,18 +21,23 @@ namespace osu.Game.Tests.Editing
}
[Test]
- public void TestSaveRestoreState()
+ public void TestSaveRestoreStateUsingTransaction()
{
var (handler, beatmap) = createChangeHandler();
Assert.That(handler.CanUndo.Value, Is.False);
Assert.That(handler.CanRedo.Value, Is.False);
- addArbitraryChange(beatmap);
- handler.SaveState();
+ handler.BeginChange();
+ // Initial state will be saved on BeginChange
Assert.That(stateChangedFired, Is.EqualTo(1));
+ addArbitraryChange(beatmap);
+ handler.EndChange();
+
+ Assert.That(stateChangedFired, Is.EqualTo(2));
+
Assert.That(handler.CanUndo.Value, Is.True);
Assert.That(handler.CanRedo.Value, Is.False);
@@ -43,7 +46,35 @@ namespace osu.Game.Tests.Editing
Assert.That(handler.CanUndo.Value, Is.False);
Assert.That(handler.CanRedo.Value, Is.True);
+ Assert.That(stateChangedFired, Is.EqualTo(3));
+ }
+
+ [Test]
+ public void TestSaveRestoreState()
+ {
+ var (handler, beatmap) = createChangeHandler();
+
+ Assert.That(handler.CanUndo.Value, Is.False);
+ Assert.That(handler.CanRedo.Value, Is.False);
+
+ // Save initial state
+ handler.SaveState();
+ Assert.That(stateChangedFired, Is.EqualTo(1));
+
+ addArbitraryChange(beatmap);
+ handler.SaveState();
+
Assert.That(stateChangedFired, Is.EqualTo(2));
+
+ Assert.That(handler.CanUndo.Value, Is.True);
+ Assert.That(handler.CanRedo.Value, Is.False);
+
+ handler.RestoreState(-1);
+
+ Assert.That(handler.CanUndo.Value, Is.False);
+ Assert.That(handler.CanRedo.Value, Is.True);
+
+ Assert.That(stateChangedFired, Is.EqualTo(3));
}
[Test]
@@ -54,6 +85,10 @@ namespace osu.Game.Tests.Editing
Assert.That(handler.CanUndo.Value, Is.False);
Assert.That(handler.CanRedo.Value, Is.False);
+ // Save initial state
+ handler.SaveState();
+ Assert.That(stateChangedFired, Is.EqualTo(1));
+
string originalHash = handler.CurrentStateHash;
addArbitraryChange(beatmap);
@@ -61,7 +96,7 @@ namespace osu.Game.Tests.Editing
Assert.That(handler.CanUndo.Value, Is.True);
Assert.That(handler.CanRedo.Value, Is.False);
- Assert.That(stateChangedFired, Is.EqualTo(1));
+ Assert.That(stateChangedFired, Is.EqualTo(2));
string hash = handler.CurrentStateHash;
@@ -69,7 +104,7 @@ namespace osu.Game.Tests.Editing
handler.RestoreState(-1);
Assert.That(originalHash, Is.EqualTo(handler.CurrentStateHash));
- Assert.That(stateChangedFired, Is.EqualTo(2));
+ Assert.That(stateChangedFired, Is.EqualTo(3));
addArbitraryChange(beatmap);
handler.SaveState();
@@ -84,12 +119,16 @@ namespace osu.Game.Tests.Editing
Assert.That(handler.CanUndo.Value, Is.False);
Assert.That(handler.CanRedo.Value, Is.False);
+ // Save initial state
+ handler.SaveState();
+ Assert.That(stateChangedFired, Is.EqualTo(1));
+
addArbitraryChange(beatmap);
handler.SaveState();
Assert.That(handler.CanUndo.Value, Is.True);
Assert.That(handler.CanRedo.Value, Is.False);
- Assert.That(stateChangedFired, Is.EqualTo(1));
+ Assert.That(stateChangedFired, Is.EqualTo(2));
string hash = handler.CurrentStateHash;
@@ -97,7 +136,7 @@ namespace osu.Game.Tests.Editing
handler.SaveState();
Assert.That(hash, Is.EqualTo(handler.CurrentStateHash));
- Assert.That(stateChangedFired, Is.EqualTo(1));
+ Assert.That(stateChangedFired, Is.EqualTo(2));
handler.RestoreState(-1);
@@ -106,7 +145,7 @@ namespace osu.Game.Tests.Editing
// we should only be able to restore once even though we saved twice.
Assert.That(handler.CanUndo.Value, Is.False);
Assert.That(handler.CanRedo.Value, Is.True);
- Assert.That(stateChangedFired, Is.EqualTo(2));
+ Assert.That(stateChangedFired, Is.EqualTo(3));
}
[Test]
@@ -114,11 +153,15 @@ namespace osu.Game.Tests.Editing
{
var (handler, beatmap) = createChangeHandler();
+ // Save initial state
+ handler.SaveState();
+ Assert.That(stateChangedFired, Is.EqualTo(1));
+
Assert.That(handler.CanUndo.Value, Is.False);
for (int i = 0; i < EditorChangeHandler.MAX_SAVED_STATES; i++)
{
- Assert.That(stateChangedFired, Is.EqualTo(i));
+ Assert.That(stateChangedFired, Is.EqualTo(i + 1));
addArbitraryChange(beatmap);
handler.SaveState();
@@ -169,7 +212,7 @@ namespace osu.Game.Tests.Editing
},
});
- var changeHandler = new EditorChangeHandler(beatmap);
+ var changeHandler = new BeatmapEditorChangeHandler(beatmap);
changeHandler.OnStateChange += () => stateChangedFired++;
return (changeHandler, beatmap);
diff --git a/osu.Game.Tests/Editing/Checks/CheckPreviewTimeTest.cs b/osu.Game.Tests/Editing/Checks/CheckPreviewTimeTest.cs
new file mode 100644
index 0000000000..37b01da6ee
--- /dev/null
+++ b/osu.Game.Tests/Editing/Checks/CheckPreviewTimeTest.cs
@@ -0,0 +1,88 @@
+// 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 System.Linq;
+using NUnit.Framework;
+using osu.Game.Beatmaps;
+using osu.Game.Rulesets.Edit;
+using osu.Game.Rulesets.Edit.Checks;
+using osu.Game.Rulesets.Objects;
+using osu.Game.Tests.Beatmaps;
+
+namespace osu.Game.Tests.Editing.Checks
+{
+ public class CheckPreviewTimeTest
+ {
+ private CheckPreviewTime check = null!;
+
+ private IBeatmap beatmap = null!;
+
+ [SetUp]
+ public void Setup()
+ {
+ check = new CheckPreviewTime();
+ }
+
+ [Test]
+ public void TestPreviewTimeNotSet()
+ {
+ setNoPreviewTimeBeatmap();
+ var content = new BeatmapVerifierContext(beatmap, new TestWorkingBeatmap(beatmap));
+
+ var issues = check.Run(content).ToList();
+
+ Assert.That(issues, Has.Count.EqualTo(1));
+ Assert.That(issues.Single().Template is CheckPreviewTime.IssueTemplateHasNoPreviewTime);
+ }
+
+ [Test]
+ public void TestPreviewTimeconflict()
+ {
+ setPreviewTimeConflictBeatmap();
+
+ var content = new BeatmapVerifierContext(beatmap, new TestWorkingBeatmap(beatmap));
+
+ var issues = check.Run(content).ToList();
+
+ Assert.That(issues, Has.Count.EqualTo(1));
+ Assert.That(issues.Single().Template is CheckPreviewTime.IssueTemplatePreviewTimeConflict);
+ Assert.That(issues.Single().Arguments.FirstOrDefault()?.ToString() == "Test1");
+ }
+
+ private void setNoPreviewTimeBeatmap()
+ {
+ beatmap = new Beatmap
+ {
+ BeatmapInfo = new BeatmapInfo
+ {
+ Metadata = new BeatmapMetadata { PreviewTime = -1 },
+ }
+ };
+ }
+
+ private void setPreviewTimeConflictBeatmap()
+ {
+ beatmap = new Beatmap
+ {
+ BeatmapInfo = new BeatmapInfo
+ {
+ Metadata = new BeatmapMetadata { PreviewTime = 10 },
+ BeatmapSet = new BeatmapSetInfo(new List
+ {
+ new BeatmapInfo
+ {
+ DifficultyName = "Test1",
+ Metadata = new BeatmapMetadata { PreviewTime = 5 },
+ },
+ new BeatmapInfo
+ {
+ DifficultyName = "Test2",
+ Metadata = new BeatmapMetadata { PreviewTime = 10 },
+ },
+ })
+ }
+ };
+ }
+ }
+}
diff --git a/osu.Game.Tests/Editing/TestSceneSnappingNearZero.cs b/osu.Game.Tests/Editing/TestSceneSnappingNearZero.cs
new file mode 100644
index 0000000000..59081215ba
--- /dev/null
+++ b/osu.Game.Tests/Editing/TestSceneSnappingNearZero.cs
@@ -0,0 +1,79 @@
+// 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.Beatmaps.ControlPoints;
+
+namespace osu.Game.Tests.Editing
+{
+ [TestFixture]
+ public class TestSceneSnappingNearZero
+ {
+ private readonly ControlPointInfo cpi = new ControlPointInfo();
+
+ [Test]
+ public void TestOnZero()
+ {
+ test(0, 500, 0, 0);
+ test(0, 500, 100, 0);
+ test(0, 500, 250, 500);
+ test(0, 500, 600, 500);
+
+ test(0, 500, -600, 0);
+ }
+
+ [Test]
+ public void TestAlmostOnZero()
+ {
+ test(50, 500, 0, 50);
+ test(50, 500, 50, 50);
+ test(50, 500, 100, 50);
+ test(50, 500, 299, 50);
+ test(50, 500, 300, 550);
+
+ test(50, 500, -500, 50);
+ }
+
+ [Test]
+ public void TestAlmostOnOne()
+ {
+ test(499, 500, -1, 499);
+ test(499, 500, 0, 499);
+ test(499, 500, 1, 499);
+ test(499, 500, 499, 499);
+ test(499, 500, 600, 499);
+ test(499, 500, 800, 999);
+ }
+
+ [Test]
+ public void TestOnOne()
+ {
+ test(500, 500, -500, 0);
+ test(500, 500, 0, 0);
+ test(500, 500, 200, 0);
+ test(500, 500, 400, 500);
+ test(500, 500, 500, 500);
+ test(500, 500, 600, 500);
+ test(500, 500, 900, 1000);
+ }
+
+ [Test]
+ public void TestNegative()
+ {
+ test(-600, 500, -600, 400);
+ test(-600, 500, -100, 400);
+ test(-600, 500, 0, 400);
+ test(-600, 500, 200, 400);
+ test(-600, 500, 400, 400);
+ test(-600, 500, 600, 400);
+ test(-600, 500, 1000, 900);
+ }
+
+ private void test(double pointTime, double beatLength, double from, double expected)
+ {
+ cpi.Clear();
+ cpi.Add(pointTime, new TimingControlPoint { BeatLength = beatLength });
+ Assert.That(cpi.GetClosestSnappedTime(from, 1), Is.EqualTo(expected), $"From: {from}");
+ }
+ }
+}
diff --git a/osu.Game.Tests/Gameplay/TestSceneDrawableHitObject.cs b/osu.Game.Tests/Gameplay/TestSceneDrawableHitObject.cs
index 62863524fe..04fc4cafbd 100644
--- a/osu.Game.Tests/Gameplay/TestSceneDrawableHitObject.cs
+++ b/osu.Game.Tests/Gameplay/TestSceneDrawableHitObject.cs
@@ -8,6 +8,7 @@ using osu.Framework.Graphics;
using osu.Framework.Testing;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
+using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Scoring;
@@ -137,6 +138,31 @@ namespace osu.Game.Tests.Gameplay
AddAssert("DHO state is correct", () => dho.State.Value == ArmedState.Miss);
}
+ [Test]
+ public void TestResultSetBeforeLoadComplete()
+ {
+ TestDrawableHitObject dho = null;
+ HitObjectLifetimeEntry lifetimeEntry = null;
+ AddStep("Create lifetime entry", () =>
+ {
+ var hitObject = new HitObject { StartTime = Time.Current };
+ lifetimeEntry = new HitObjectLifetimeEntry(hitObject)
+ {
+ Result = new JudgementResult(hitObject, hitObject.CreateJudgement())
+ {
+ Type = HitResult.Great
+ }
+ };
+ });
+ AddStep("Create DHO and apply entry", () =>
+ {
+ dho = new TestDrawableHitObject();
+ dho.Apply(lifetimeEntry);
+ Child = dho;
+ });
+ AddAssert("DHO state is correct", () => dho.State.Value, () => Is.EqualTo(ArmedState.Hit));
+ }
+
private partial class TestDrawableHitObject : DrawableHitObject
{
public const double INITIAL_LIFETIME_OFFSET = 100;
diff --git a/osu.Game.Tests/Gameplay/TestSceneMasterGameplayClockContainer.cs b/osu.Game.Tests/Gameplay/TestSceneMasterGameplayClockContainer.cs
index 3c6ec28da2..393217f371 100644
--- a/osu.Game.Tests/Gameplay/TestSceneMasterGameplayClockContainer.cs
+++ b/osu.Game.Tests/Gameplay/TestSceneMasterGameplayClockContainer.cs
@@ -73,16 +73,6 @@ namespace osu.Game.Tests.Gameplay
}
[Test]
- [FlakyTest]
- /*
- * Fail rate around 0.15%
- *
- * TearDown : osu.Framework.Testing.Drawables.Steps.AssertButton+TracedException : gameplay clock time = 2500
- * --TearDown
- * at osu.Framework.Threading.ScheduledDelegate.RunTaskInternal()
- * at osu.Framework.Threading.Scheduler.Update()
- * at osu.Framework.Graphics.Drawable.UpdateSubTree()
- */
public void TestSeekPerformsInGameplayTime(
[Values(1.0, 0.5, 2.0)] double clockRate,
[Values(0.0, 200.0, -200.0)] double userOffset,
@@ -92,6 +82,9 @@ namespace osu.Game.Tests.Gameplay
ClockBackedTestWorkingBeatmap working = null;
GameplayClockContainer gameplayClockContainer = null;
+ // ReSharper disable once NotAccessedVariable
+ BindableDouble trackAdjustment = null; // keeping a reference for track adjustment
+
if (setAudioOffsetBeforeConstruction)
AddStep($"preset audio offset to {userOffset}", () => localConfig.SetValue(OsuSetting.AudioOffset, userOffset));
@@ -103,16 +96,16 @@ namespace osu.Game.Tests.Gameplay
gameplayClockContainer.Reset(startClock: !whileStopped);
});
- AddStep($"set clock rate to {clockRate}", () => working.Track.AddAdjustment(AdjustableProperty.Frequency, new BindableDouble(clockRate)));
+ AddStep($"set clock rate to {clockRate}", () => working.Track.AddAdjustment(AdjustableProperty.Frequency, trackAdjustment = new BindableDouble(clockRate)));
if (!setAudioOffsetBeforeConstruction)
AddStep($"set audio offset to {userOffset}", () => localConfig.SetValue(OsuSetting.AudioOffset, userOffset));
AddStep("seek to 2500", () => gameplayClockContainer.Seek(2500));
- AddStep("gameplay clock time = 2500", () => Assert.AreEqual(gameplayClockContainer.CurrentTime, 2500, 10f));
+ AddAssert("gameplay clock time = 2500", () => gameplayClockContainer.CurrentTime, () => Is.EqualTo(2500).Within(10f));
AddStep("seek to 10000", () => gameplayClockContainer.Seek(10000));
- AddStep("gameplay clock time = 10000", () => Assert.AreEqual(gameplayClockContainer.CurrentTime, 10000, 10f));
+ AddAssert("gameplay clock time = 10000", () => gameplayClockContainer.CurrentTime, () => Is.EqualTo(10000).Within(10f));
}
protected override void Dispose(bool isDisposing)
diff --git a/osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs b/osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs
index 3ce7aa72d9..90c7688443 100644
--- a/osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs
+++ b/osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs
@@ -8,6 +8,7 @@ using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Testing;
+using osu.Framework.Utils;
using osu.Game.Beatmaps;
using osu.Game.Online.Spectator;
using osu.Game.Rulesets.Judgements;
@@ -139,6 +140,29 @@ namespace osu.Game.Tests.Gameplay
Assert.That(score.MaximumStatistics[HitResult.LargeBonus], Is.EqualTo(1));
}
+ [Test]
+ public void TestAccuracyModes()
+ {
+ var beatmap = new Beatmap
+ {
+ HitObjects = Enumerable.Range(0, 4).Select(_ => new TestHitObject(HitResult.Great)).ToList()
+ };
+
+ var scoreProcessor = new ScoreProcessor(new OsuRuleset());
+ scoreProcessor.ApplyBeatmap(beatmap);
+
+ Assert.That(scoreProcessor.Accuracy.Value, Is.EqualTo(1));
+ Assert.That(scoreProcessor.MinimumAccuracy.Value, Is.EqualTo(0));
+ Assert.That(scoreProcessor.MaximumAccuracy.Value, Is.EqualTo(1));
+
+ scoreProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[0], beatmap.HitObjects[0].CreateJudgement()) { Type = HitResult.Ok });
+ scoreProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[1], beatmap.HitObjects[1].CreateJudgement()) { Type = HitResult.Great });
+
+ Assert.That(scoreProcessor.Accuracy.Value, Is.EqualTo((double)(100 + 300) / (2 * 300)).Within(Precision.DOUBLE_EPSILON));
+ Assert.That(scoreProcessor.MinimumAccuracy.Value, Is.EqualTo((double)(100 + 300) / (4 * 300)).Within(Precision.DOUBLE_EPSILON));
+ Assert.That(scoreProcessor.MaximumAccuracy.Value, Is.EqualTo((double)(100 + 3 * 300) / (4 * 300)).Within(Precision.DOUBLE_EPSILON));
+ }
+
private class TestJudgement : Judgement
{
public override HitResult MaxResult { get; }
diff --git a/osu.Game.Tests/Mods/MultiModIncompatibilityTest.cs b/osu.Game.Tests/Mods/MultiModIncompatibilityTest.cs
index b8a3828a64..ca5240a39d 100644
--- a/osu.Game.Tests/Mods/MultiModIncompatibilityTest.cs
+++ b/osu.Game.Tests/Mods/MultiModIncompatibilityTest.cs
@@ -60,6 +60,6 @@ namespace osu.Game.Tests.Mods
/// This local helper is used rather than , because the aforementioned method flattens multi mods.
/// >
private static IEnumerable getMultiMods(Ruleset ruleset)
- => Enum.GetValues(typeof(ModType)).Cast().SelectMany(ruleset.GetModsFor).OfType();
+ => Enum.GetValues().SelectMany(ruleset.GetModsFor).OfType();
}
}
diff --git a/osu.Game.Tests/NonVisual/ClosestBeatDivisorTest.cs b/osu.Game.Tests/NonVisual/ClosestBeatDivisorTest.cs
index 6f3fe875e3..8ebf34b1ca 100644
--- a/osu.Game.Tests/NonVisual/ClosestBeatDivisorTest.cs
+++ b/osu.Game.Tests/NonVisual/ClosestBeatDivisorTest.cs
@@ -17,7 +17,7 @@ namespace osu.Game.Tests.NonVisual
public void TestExactDivisors()
{
var cpi = new ControlPointInfo();
- cpi.Add(-1000, new TimingControlPoint { BeatLength = 1000 });
+ cpi.Add(0, new TimingControlPoint { BeatLength = 1000 });
double[] divisors = { 3, 1, 16, 12, 8, 6, 4, 3, 2, 1 };
@@ -47,7 +47,7 @@ namespace osu.Game.Tests.NonVisual
public void TestExactDivisorsHighBPMStream()
{
var cpi = new ControlPointInfo();
- cpi.Add(-50, new TimingControlPoint { BeatLength = 50 }); // 1200 BPM 1/4 (limit testing)
+ cpi.Add(0, new TimingControlPoint { BeatLength = 50 }); // 1200 BPM 1/4 (limit testing)
// A 1/4 stream should land on 1/1, 1/2 and 1/4 divisors.
double[] divisors = { 4, 4, 4, 4, 4, 4, 4, 4 };
@@ -60,7 +60,7 @@ namespace osu.Game.Tests.NonVisual
public void TestApproximateDivisors()
{
var cpi = new ControlPointInfo();
- cpi.Add(-1000, new TimingControlPoint { BeatLength = 1000 });
+ cpi.Add(0, new TimingControlPoint { BeatLength = 1000 });
double[] divisors = { 3.03d, 0.97d, 14, 13, 7.94d, 6.08d, 3.93d, 2.96d, 2.02d, 64 };
double[] closestDivisors = { 3, 1, 16, 12, 8, 6, 4, 3, 2, 1 };
@@ -68,7 +68,7 @@ namespace osu.Game.Tests.NonVisual
assertClosestDivisors(divisors, closestDivisors, cpi);
}
- private void assertClosestDivisors(IReadOnlyList divisors, IReadOnlyList closestDivisors, ControlPointInfo cpi, double step = 1)
+ private static void assertClosestDivisors(IReadOnlyList divisors, IReadOnlyList closestDivisors, ControlPointInfo cpi, double step = 1)
{
List hitobjects = new List();
double offset = cpi.TimingPoints[0].Time;
diff --git a/osu.Game.Tests/NonVisual/FirstAvailableHitWindowsTest.cs b/osu.Game.Tests/NonVisual/FirstAvailableHitWindowsTest.cs
index 9a32b8e894..0bdd0ceae6 100644
--- a/osu.Game.Tests/NonVisual/FirstAvailableHitWindowsTest.cs
+++ b/osu.Game.Tests/NonVisual/FirstAvailableHitWindowsTest.cs
@@ -7,6 +7,7 @@ using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using NUnit.Framework;
+using osu.Framework.Audio;
using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Mods;
@@ -93,6 +94,7 @@ namespace osu.Game.Tests.NonVisual
remove => throw new InvalidOperationException($"{nameof(RevertResult)} operations not supported in test context");
}
+ public override IAdjustableAudioComponent Audio { get; }
public override Playfield Playfield { get; }
public override Container Overlays { get; }
public override Container FrameStableComponents { get; }
diff --git a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs
index e1d8e08c5e..585fd516bd 100644
--- a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs
+++ b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs
@@ -226,12 +226,12 @@ namespace osu.Game.Tests.Online
this.testBeatmapManager = testBeatmapManager;
}
- public override Live ImportModel(BeatmapSetInfo item, ArchiveReader archive = null, bool batchImport = false, CancellationToken cancellationToken = default)
+ public override Live ImportModel(BeatmapSetInfo item, ArchiveReader archive = null, ImportParameters parameters = default, CancellationToken cancellationToken = default)
{
if (!testBeatmapManager.AllowImport.Wait(TimeSpan.FromSeconds(10), cancellationToken))
throw new TimeoutException("Timeout waiting for import to be allowed.");
- return (testBeatmapManager.CurrentImport = base.ImportModel(item, archive, batchImport, cancellationToken));
+ return (testBeatmapManager.CurrentImport = base.ImportModel(item, archive, parameters, cancellationToken));
}
}
}
diff --git a/osu.Game.Tests/Resources/Archives/conflicting-filenames-beatmap.osz b/osu.Game.Tests/Resources/Archives/conflicting-filenames-beatmap.osz
new file mode 100644
index 0000000000..c6b5f083ff
Binary files /dev/null and b/osu.Game.Tests/Resources/Archives/conflicting-filenames-beatmap.osz differ
diff --git a/osu.Game.Tests/Resources/Archives/conflicting-filenames-skin.osk b/osu.Game.Tests/Resources/Archives/conflicting-filenames-skin.osk
new file mode 100644
index 0000000000..73576f3e22
Binary files /dev/null and b/osu.Game.Tests/Resources/Archives/conflicting-filenames-skin.osk differ
diff --git a/osu.Game.Tests/Resources/Archives/modified-argon-20221024.osk b/osu.Game.Tests/Resources/Archives/modified-argon-20221024.osk
new file mode 100644
index 0000000000..28b6a001eb
Binary files /dev/null and b/osu.Game.Tests/Resources/Archives/modified-argon-20221024.osk differ
diff --git a/osu.Game.Tests/Resources/Archives/modified-default-20221205.osk b/osu.Game.Tests/Resources/Archives/modified-default-20221205.osk
new file mode 100644
index 0000000000..ae421fc323
Binary files /dev/null and b/osu.Game.Tests/Resources/Archives/modified-default-20221205.osk differ
diff --git a/osu.Game.Tests/Resources/Archives/modified-default-20230117.osk b/osu.Game.Tests/Resources/Archives/modified-default-20230117.osk
new file mode 100644
index 0000000000..810bc4edf0
Binary files /dev/null and b/osu.Game.Tests/Resources/Archives/modified-default-20230117.osk differ
diff --git a/osu.Game.Tests/Resources/TestResources.cs b/osu.Game.Tests/Resources/TestResources.cs
index 9c85f61330..adf28afc8e 100644
--- a/osu.Game.Tests/Resources/TestResources.cs
+++ b/osu.Game.Tests/Resources/TestResources.cs
@@ -12,6 +12,7 @@ using System.Threading;
using NUnit.Framework;
using osu.Framework.Extensions;
using osu.Framework.IO.Stores;
+using osu.Framework.Logging;
using osu.Framework.Testing;
using osu.Framework.Utils;
using osu.Game.Beatmaps;
@@ -93,10 +94,12 @@ namespace osu.Game.Tests.Resources
{
// Create random metadata, then we can check if sorting works based on these
Artist = "Some Artist " + RNG.Next(0, 9),
- Title = $"Some Song (set id {setId}) {Guid.NewGuid()}",
+ Title = $"Some Song (set id {setId:000}) {Guid.NewGuid()}",
Author = { Username = "Some Guy " + RNG.Next(0, 9) },
};
+ Logger.Log($"🛠️ Generating beatmap set \"{metadata}\" for test consumption.");
+
var beatmapSet = new BeatmapSetInfo
{
OnlineID = setId,
diff --git a/osu.Game.Tests/Resources/mania-last-object-not-latest.osu b/osu.Game.Tests/Resources/mania-last-object-not-latest.osu
new file mode 100644
index 0000000000..51893383d8
--- /dev/null
+++ b/osu.Game.Tests/Resources/mania-last-object-not-latest.osu
@@ -0,0 +1,39 @@
+osu file format v14
+
+[General]
+SampleSet: Normal
+StackLeniency: 0.7
+Mode: 3
+
+[Difficulty]
+HPDrainRate:3
+CircleSize:5
+OverallDifficulty:8
+ApproachRate:8
+SliderMultiplier:3.59999990463257
+SliderTickRate:2
+
+[TimingPoints]
+24,352.941176470588,4,1,1,100,1,0
+6376,-50,4,1,1,100,0,0
+
+[HitObjects]
+51,192,24,1,0,0:0:0:0:
+153,192,200,1,0,0:0:0:0:
+358,192,376,1,0,0:0:0:0:
+460,192,553,1,0,0:0:0:0:
+460,192,729,128,0,1435:0:0:0:0:
+358,192,906,128,0,1612:0:0:0:0:
+256,192,1082,128,0,1788:0:0:0:0:
+153,192,1259,128,0,1965:0:0:0:0:
+51,192,1435,128,0,2141:0:0:0:0:
+51,192,2318,1,12,0:0:0:0:
+153,192,2318,1,4,0:0:0:0:
+256,192,2318,1,6,0:0:0:0:
+358,192,2318,1,14,0:0:0:0:
+460,192,2318,1,0,0:0:0:0:
+51,192,2494,128,0,2582:0:0:0:0:
+153,192,2494,128,14,2582:0:0:0:0:
+256,192,2494,128,6,2582:0:0:0:0:
+358,192,2494,128,4,2582:0:0:0:0:
+460,192,2494,1,12,0:0:0:0:0:
diff --git a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs
index e9e94aa897..826c610f56 100644
--- a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs
+++ b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs
@@ -7,6 +7,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
+using osu.Framework.Utils;
using osu.Game.Beatmaps;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Difficulty;
@@ -355,6 +356,28 @@ namespace osu.Game.Tests.Rulesets.Scoring
}
#pragma warning restore CS0618
+ [Test]
+ public void TestAccuracyWhenNearPerfect()
+ {
+ const int count_judgements = 1000;
+ const int count_misses = 1;
+
+ double actual = new TestScoreProcessor().ComputeAccuracy(new ScoreInfo
+ {
+ Statistics = new Dictionary
+ {
+ { HitResult.Great, count_judgements - count_misses },
+ { HitResult.Miss, count_misses }
+ }
+ });
+
+ const double expected = (count_judgements - count_misses) / (double)count_judgements;
+
+ Assert.That(actual, Is.Not.EqualTo(0.0));
+ Assert.That(actual, Is.Not.EqualTo(1.0));
+ Assert.That(actual, Is.EqualTo(expected).Within(Precision.FLOAT_EPSILON));
+ }
+
private class TestRuleset : Ruleset
{
public override IEnumerable GetModsFor(ModType type) => throw new NotImplementedException();
diff --git a/osu.Game.Tests/Rulesets/TestSceneDrawableRulesetDependencies.cs b/osu.Game.Tests/Rulesets/TestSceneDrawableRulesetDependencies.cs
index 3fba21050e..5cfcca303f 100644
--- a/osu.Game.Tests/Rulesets/TestSceneDrawableRulesetDependencies.cs
+++ b/osu.Game.Tests/Rulesets/TestSceneDrawableRulesetDependencies.cs
@@ -150,6 +150,8 @@ namespace osu.Game.Tests.Rulesets
public IBindable AggregateTempo => throw new NotImplementedException();
public int PlaybackConcurrency { get; set; }
+
+ public void AddExtension(string extension) => throw new NotImplementedException();
}
private class TestShaderManager : ShaderManager
diff --git a/osu.Game.Tests/Skins/IO/ImportSkinTest.cs b/osu.Game.Tests/Skins/IO/ImportSkinTest.cs
index 5c20f46787..0bd40e9962 100644
--- a/osu.Game.Tests/Skins/IO/ImportSkinTest.cs
+++ b/osu.Game.Tests/Skins/IO/ImportSkinTest.cs
@@ -360,7 +360,7 @@ namespace osu.Game.Tests.Skins.IO
private async Task> loadSkinIntoOsu(OsuGameBase osu, ImportTask import, bool batchImport = false)
{
var skinManager = osu.Dependencies.Get();
- return await skinManager.Import(import, batchImport);
+ return await skinManager.Import(import, new ImportParameters { Batch = batchImport });
}
}
}
diff --git a/osu.Game.Tests/Skins/SkinDeserialisationTest.cs b/osu.Game.Tests/Skins/SkinDeserialisationTest.cs
index ff665499ae..04b8c6dd4e 100644
--- a/osu.Game.Tests/Skins/SkinDeserialisationTest.cs
+++ b/osu.Game.Tests/Skins/SkinDeserialisationTest.cs
@@ -41,8 +41,14 @@ namespace osu.Game.Tests.Skins
"Archives/modified-default-20220818.osk",
// Covers longest combo counter
"Archives/modified-default-20221012.osk",
+ // Covers Argon variant of song progress bar
+ "Archives/modified-argon-20221024.osk",
// Covers TextElement and BeatmapInfoDrawable
- "Archives/modified-default-20221102.osk"
+ "Archives/modified-default-20221102.osk",
+ // Covers BPM counter.
+ "Archives/modified-default-20221205.osk",
+ // Covers judgement counter.
+ "Archives/modified-default-20230117.osk"
};
///
@@ -60,15 +66,15 @@ namespace osu.Game.Tests.Skins
{
var skin = new TestSkin(new SkinInfo(), null, storage);
- foreach (var target in skin.DrawableComponentInfo)
+ foreach (var target in skin.LayoutInfos)
{
- foreach (var info in target.Value)
+ foreach (var info in target.Value.AllDrawables)
instantiatedTypes.Add(info.Type);
}
}
}
- var editableTypes = SkinnableInfo.GetAllAvailableDrawables().Where(t => (Activator.CreateInstance(t) as ISkinnableDrawable)?.IsEditable == true);
+ var editableTypes = SerialisedDrawableInfo.GetAllAvailableDrawables().Where(t => (Activator.CreateInstance(t) as ISerialisableDrawable)?.IsEditable == true);
Assert.That(instantiatedTypes, Is.EquivalentTo(editableTypes));
}
@@ -81,8 +87,8 @@ namespace osu.Game.Tests.Skins
{
var skin = new TestSkin(new SkinInfo(), null, storage);
- Assert.That(skin.DrawableComponentInfo, Has.Count.EqualTo(2));
- Assert.That(skin.DrawableComponentInfo[GlobalSkinComponentLookup.LookupType.MainHUDComponents], Has.Length.EqualTo(9));
+ Assert.That(skin.LayoutInfos, Has.Count.EqualTo(2));
+ Assert.That(skin.LayoutInfos[SkinComponentsContainerLookup.TargetArea.MainHUDComponents].AllDrawables.ToArray(), Has.Length.EqualTo(9));
}
}
@@ -94,11 +100,11 @@ namespace osu.Game.Tests.Skins
{
var skin = new TestSkin(new SkinInfo(), null, storage);
- Assert.That(skin.DrawableComponentInfo, Has.Count.EqualTo(2));
- Assert.That(skin.DrawableComponentInfo[GlobalSkinComponentLookup.LookupType.MainHUDComponents], Has.Length.EqualTo(6));
- Assert.That(skin.DrawableComponentInfo[GlobalSkinComponentLookup.LookupType.SongSelect], Has.Length.EqualTo(1));
+ Assert.That(skin.LayoutInfos, Has.Count.EqualTo(2));
+ Assert.That(skin.LayoutInfos[SkinComponentsContainerLookup.TargetArea.MainHUDComponents].AllDrawables.ToArray(), Has.Length.EqualTo(6));
+ Assert.That(skin.LayoutInfos[SkinComponentsContainerLookup.TargetArea.SongSelect].AllDrawables.ToArray(), Has.Length.EqualTo(1));
- var skinnableInfo = skin.DrawableComponentInfo[GlobalSkinComponentLookup.LookupType.SongSelect].First();
+ var skinnableInfo = skin.LayoutInfos[SkinComponentsContainerLookup.TargetArea.SongSelect].AllDrawables.First();
Assert.That(skinnableInfo.Type, Is.EqualTo(typeof(SkinnableSprite)));
Assert.That(skinnableInfo.Settings.First().Key, Is.EqualTo("sprite_name"));
@@ -109,10 +115,10 @@ namespace osu.Game.Tests.Skins
using (var storage = new ZipArchiveReader(stream))
{
var skin = new TestSkin(new SkinInfo(), null, storage);
- Assert.That(skin.DrawableComponentInfo[GlobalSkinComponentLookup.LookupType.MainHUDComponents], Has.Length.EqualTo(8));
- Assert.That(skin.DrawableComponentInfo[GlobalSkinComponentLookup.LookupType.MainHUDComponents].Select(i => i.Type), Contains.Item(typeof(UnstableRateCounter)));
- Assert.That(skin.DrawableComponentInfo[GlobalSkinComponentLookup.LookupType.MainHUDComponents].Select(i => i.Type), Contains.Item(typeof(ColourHitErrorMeter)));
- Assert.That(skin.DrawableComponentInfo[GlobalSkinComponentLookup.LookupType.MainHUDComponents].Select(i => i.Type), Contains.Item(typeof(LegacySongProgress)));
+ Assert.That(skin.LayoutInfos[SkinComponentsContainerLookup.TargetArea.MainHUDComponents].AllDrawables.ToArray(), Has.Length.EqualTo(8));
+ Assert.That(skin.LayoutInfos[SkinComponentsContainerLookup.TargetArea.MainHUDComponents].AllDrawables.Select(i => i.Type), Contains.Item(typeof(UnstableRateCounter)));
+ Assert.That(skin.LayoutInfos[SkinComponentsContainerLookup.TargetArea.MainHUDComponents].AllDrawables.Select(i => i.Type), Contains.Item(typeof(ColourHitErrorMeter)));
+ Assert.That(skin.LayoutInfos[SkinComponentsContainerLookup.TargetArea.MainHUDComponents].AllDrawables.Select(i => i.Type), Contains.Item(typeof(LegacySongProgress)));
}
}
diff --git a/osu.Game.Tests/Skins/TestSceneBeatmapSkinResources.cs b/osu.Game.Tests/Skins/TestSceneBeatmapSkinResources.cs
index 5256bcb3af..d9212386c3 100644
--- a/osu.Game.Tests/Skins/TestSceneBeatmapSkinResources.cs
+++ b/osu.Game.Tests/Skins/TestSceneBeatmapSkinResources.cs
@@ -1,12 +1,11 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Audio.Track;
using osu.Framework.Extensions;
+using osu.Framework.Extensions.ObjectExtensions;
using osu.Framework.Testing;
using osu.Game.Audio;
using osu.Game.Beatmaps;
@@ -20,29 +19,36 @@ namespace osu.Game.Tests.Skins
public partial class TestSceneBeatmapSkinResources : OsuTestScene
{
[Resolved]
- private BeatmapManager beatmaps { get; set; }
+ private BeatmapManager beatmaps { get; set; } = null!;
- private IWorkingBeatmap beatmap;
-
- [BackgroundDependencyLoader]
- private void load()
+ [Test]
+ public void TestRetrieveOggAudio()
{
- var imported = beatmaps.Import(new ImportTask(TestResources.OpenResource("Archives/ogg-beatmap.osz"), "ogg-beatmap.osz")).GetResultSafely();
+ IWorkingBeatmap beatmap = null!;
- imported?.PerformRead(s =>
+ AddStep("import beatmap", () => beatmap = importBeatmapFromArchives(@"ogg-beatmap.osz"));
+ AddAssert("sample is non-null", () => beatmap.Skin.GetSample(new SampleInfo(@"sample")) != null);
+ AddAssert("track is non-null", () =>
{
- beatmap = beatmaps.GetWorkingBeatmap(s.Beatmaps[0]);
+ using (var track = beatmap.LoadTrack())
+ return track is not TrackVirtual;
});
}
[Test]
- public void TestRetrieveOggSample() => AddAssert("sample is non-null", () => beatmap.Skin.GetSample(new SampleInfo("sample")) != null);
-
- [Test]
- public void TestRetrieveOggTrack() => AddAssert("track is non-null", () =>
+ public void TestRetrievalWithConflictingFilenames()
{
- using (var track = beatmap.LoadTrack())
- return track is not TrackVirtual;
- });
+ IWorkingBeatmap beatmap = null!;
+
+ AddStep("import beatmap", () => beatmap = importBeatmapFromArchives(@"conflicting-filenames-beatmap.osz"));
+ AddAssert("texture is non-null", () => beatmap.Skin.GetTexture(@"spinner-osu") != null);
+ AddAssert("sample is non-null", () => beatmap.Skin.GetSample(new SampleInfo(@"spinner-osu")) != null);
+ }
+
+ private IWorkingBeatmap importBeatmapFromArchives(string filename)
+ {
+ var imported = beatmaps.Import(new ImportTask(TestResources.OpenResource($@"Archives/{filename}"), filename)).GetResultSafely();
+ return imported.AsNonNull().PerformRead(s => beatmaps.GetWorkingBeatmap(s.Beatmaps[0]));
+ }
}
}
diff --git a/osu.Game.Tests/Skins/TestSceneSkinResources.cs b/osu.Game.Tests/Skins/TestSceneSkinResources.cs
index 748668b6ca..aaec319b57 100644
--- a/osu.Game.Tests/Skins/TestSceneSkinResources.cs
+++ b/osu.Game.Tests/Skins/TestSceneSkinResources.cs
@@ -31,17 +31,24 @@ namespace osu.Game.Tests.Skins
[Resolved]
private SkinManager skins { get; set; } = null!;
- private ISkin skin = null!;
-
- [BackgroundDependencyLoader]
- private void load()
+ [Test]
+ public void TestRetrieveOggSample()
{
- var imported = skins.Import(new ImportTask(TestResources.OpenResource("Archives/ogg-skin.osk"), "ogg-skin.osk")).GetResultSafely();
- skin = imported.PerformRead(skinInfo => skins.GetSkin(skinInfo));
+ ISkin skin = null!;
+
+ AddStep("import skin", () => skin = importSkinFromArchives(@"ogg-skin.osk"));
+ AddAssert("sample is non-null", () => skin.GetSample(new SampleInfo(@"sample")) != null);
}
[Test]
- public void TestRetrieveOggSample() => AddAssert("sample is non-null", () => skin.GetSample(new SampleInfo("sample")) != null);
+ public void TestRetrievalWithConflictingFilenames()
+ {
+ ISkin skin = null!;
+
+ AddStep("import skin", () => skin = importSkinFromArchives(@"conflicting-filenames-skin.osk"));
+ AddAssert("texture is non-null", () => skin.GetTexture(@"spinner-osu") != null);
+ AddAssert("sample is non-null", () => skin.GetSample(new SampleInfo(@"spinner-osu")) != null);
+ }
[Test]
public void TestSampleRetrievalOrder()
@@ -78,6 +85,12 @@ namespace osu.Game.Tests.Skins
});
}
+ private Skin importSkinFromArchives(string filename)
+ {
+ var imported = skins.Import(new ImportTask(TestResources.OpenResource($@"Archives/{filename}"), filename)).GetResultSafely();
+ return imported.PerformRead(skinInfo => skins.GetSkin(skinInfo));
+ }
+
private class TestSkin : Skin
{
public const string SAMPLE_NAME = "test-sample";
diff --git a/osu.Game.Tests/Utils/NamingUtilsTest.cs b/osu.Game.Tests/Utils/NamingUtilsTest.cs
index 62e688db90..1f7e06f996 100644
--- a/osu.Game.Tests/Utils/NamingUtilsTest.cs
+++ b/osu.Game.Tests/Utils/NamingUtilsTest.cs
@@ -11,7 +11,7 @@ namespace osu.Game.Tests.Utils
public class NamingUtilsTest
{
[Test]
- public void TestEmptySet()
+ public void TestNextBestNameEmptySet()
{
string nextBestName = NamingUtils.GetNextBestName(Enumerable.Empty(), "New Difficulty");
@@ -19,7 +19,7 @@ namespace osu.Game.Tests.Utils
}
[Test]
- public void TestNotTaken()
+ public void TestNextBestNameNotTaken()
{
string[] existingNames =
{
@@ -34,7 +34,7 @@ namespace osu.Game.Tests.Utils
}
[Test]
- public void TestNotTakenButClose()
+ public void TestNextBestNameNotTakenButClose()
{
string[] existingNames =
{
@@ -49,7 +49,7 @@ namespace osu.Game.Tests.Utils
}
[Test]
- public void TestAlreadyTaken()
+ public void TestNextBestNameAlreadyTaken()
{
string[] existingNames =
{
@@ -62,7 +62,7 @@ namespace osu.Game.Tests.Utils
}
[Test]
- public void TestAlreadyTakenWithDifferentCase()
+ public void TestNextBestNameAlreadyTakenWithDifferentCase()
{
string[] existingNames =
{
@@ -75,7 +75,7 @@ namespace osu.Game.Tests.Utils
}
[Test]
- public void TestAlreadyTakenWithBrackets()
+ public void TestNextBestNameAlreadyTakenWithBrackets()
{
string[] existingNames =
{
@@ -88,7 +88,7 @@ namespace osu.Game.Tests.Utils
}
[Test]
- public void TestMultipleAlreadyTaken()
+ public void TestNextBestNameMultipleAlreadyTaken()
{
string[] existingNames =
{
@@ -104,7 +104,7 @@ namespace osu.Game.Tests.Utils
}
[Test]
- public void TestEvenMoreAlreadyTaken()
+ public void TestNextBestNameEvenMoreAlreadyTaken()
{
string[] existingNames = Enumerable.Range(1, 30).Select(i => $"New Difficulty ({i})").Append("New Difficulty").ToArray();
@@ -114,7 +114,7 @@ namespace osu.Game.Tests.Utils
}
[Test]
- public void TestMultipleAlreadyTakenWithGaps()
+ public void TestNextBestNameMultipleAlreadyTakenWithGaps()
{
string[] existingNames =
{
@@ -128,5 +128,153 @@ namespace osu.Game.Tests.Utils
Assert.AreEqual("New Difficulty (2)", nextBestName);
}
+
+ [Test]
+ public void TestNextBestFilenameEmptySet()
+ {
+ string nextBestFilename = NamingUtils.GetNextBestFilename(Enumerable.Empty(), "test_file.osr");
+
+ Assert.AreEqual("test_file.osr", nextBestFilename);
+ }
+
+ [Test]
+ public void TestNextBestFilenameNotTaken()
+ {
+ string[] existingFiles =
+ {
+ "this file exists.zip",
+ "that file exists.too",
+ "three.4",
+ };
+
+ string nextBestFilename = NamingUtils.GetNextBestFilename(existingFiles, "test_file.osr");
+
+ Assert.AreEqual("test_file.osr", nextBestFilename);
+ }
+
+ [Test]
+ public void TestNextBestFilenameNotTakenButClose()
+ {
+ string[] existingFiles =
+ {
+ "replay_file(1).osr",
+ "replay_file (not a number).zip",
+ "replay_file (1 <- now THAT is a number right here).lol",
+ };
+
+ string nextBestFilename = NamingUtils.GetNextBestFilename(existingFiles, "replay_file.osr");
+
+ Assert.AreEqual("replay_file.osr", nextBestFilename);
+ }
+
+ [Test]
+ public void TestNextBestFilenameAlreadyTaken()
+ {
+ string[] existingFiles =
+ {
+ "replay_file.osr",
+ };
+
+ string nextBestFilename = NamingUtils.GetNextBestFilename(existingFiles, "replay_file.osr");
+
+ Assert.AreEqual("replay_file (1).osr", nextBestFilename);
+ }
+
+ [Test]
+ public void TestNextBestFilenameAlreadyTakenDifferentCase()
+ {
+ string[] existingFiles =
+ {
+ "replay_file.osr",
+ "RePlAy_FiLe (1).OsR",
+ "REPLAY_FILE (2).OSR",
+ };
+
+ string nextBestFilename = NamingUtils.GetNextBestFilename(existingFiles, "replay_file.osr");
+ Assert.AreEqual("replay_file (3).osr", nextBestFilename);
+ }
+
+ [Test]
+ public void TestNextBestFilenameAlreadyTakenWithBrackets()
+ {
+ string[] existingFiles =
+ {
+ "replay_file.osr",
+ "replay_file (copy).osr",
+ };
+
+ string nextBestFilename = NamingUtils.GetNextBestFilename(existingFiles, "replay_file.osr");
+ Assert.AreEqual("replay_file (1).osr", nextBestFilename);
+
+ nextBestFilename = NamingUtils.GetNextBestFilename(existingFiles, "replay_file (copy).osr");
+ Assert.AreEqual("replay_file (copy) (1).osr", nextBestFilename);
+ }
+
+ [Test]
+ public void TestNextBestFilenameMultipleAlreadyTaken()
+ {
+ string[] existingFiles =
+ {
+ "replay_file.osr",
+ "replay_file (1).osr",
+ "replay_file (2).osr",
+ "replay_file (3).osr",
+ };
+
+ string nextBestFilename = NamingUtils.GetNextBestFilename(existingFiles, "replay_file.osr");
+
+ Assert.AreEqual("replay_file (4).osr", nextBestFilename);
+ }
+
+ [Test]
+ public void TestNextBestFilenameMultipleAlreadyTakenWithGaps()
+ {
+ string[] existingFiles =
+ {
+ "replay_file.osr",
+ "replay_file (1).osr",
+ "replay_file (2).osr",
+ "replay_file (4).osr",
+ "replay_file (5).osr",
+ };
+
+ string nextBestFilename = NamingUtils.GetNextBestFilename(existingFiles, "replay_file.osr");
+
+ Assert.AreEqual("replay_file (3).osr", nextBestFilename);
+ }
+
+ [Test]
+ public void TestNextBestFilenameNoExtensions()
+ {
+ string[] existingFiles =
+ {
+ "those",
+ "are definitely",
+ "files",
+ };
+
+ string nextBestFilename = NamingUtils.GetNextBestFilename(existingFiles, "surely");
+ Assert.AreEqual("surely", nextBestFilename);
+
+ nextBestFilename = NamingUtils.GetNextBestFilename(existingFiles, "those");
+ Assert.AreEqual("those (1)", nextBestFilename);
+ }
+
+ [Test]
+ public void TestNextBestFilenameDifferentExtensions()
+ {
+ string[] existingFiles =
+ {
+ "replay_file.osr",
+ "replay_file (1).osr",
+ "replay_file.txt",
+ };
+
+ string nextBestFilename = NamingUtils.GetNextBestFilename(existingFiles, "replay_file.osr");
+ Assert.AreEqual("replay_file (2).osr", nextBestFilename);
+
+ nextBestFilename = NamingUtils.GetNextBestFilename(existingFiles, "replay_file.txt");
+ Assert.AreEqual("replay_file (1).txt", nextBestFilename);
+ }
}
}
diff --git a/osu.Game.Tests/Visual/Audio/TestSceneAudioFilter.cs b/osu.Game.Tests/Visual/Audio/TestSceneAudioFilter.cs
index cc72731493..d96c19a13e 100644
--- a/osu.Game.Tests/Visual/Audio/TestSceneAudioFilter.cs
+++ b/osu.Game.Tests/Visual/Audio/TestSceneAudioFilter.cs
@@ -31,8 +31,8 @@ namespace osu.Game.Tests.Visual.Audio
private WaveformTestBeatmap beatmap;
- private OsuSliderBar lowPassSlider;
- private OsuSliderBar highPassSlider;
+ private RoundedSliderBar lowPassSlider;
+ private RoundedSliderBar highPassSlider;
[BackgroundDependencyLoader]
private void load(AudioManager audio)
@@ -52,7 +52,7 @@ namespace osu.Game.Tests.Visual.Audio
Text = $"Low Pass: {lowPassFilter.Cutoff}hz",
Font = new FontUsage(size: 40)
},
- lowPassSlider = new OsuSliderBar
+ lowPassSlider = new RoundedSliderBar
{
Width = 500,
Height = 50,
@@ -69,7 +69,7 @@ namespace osu.Game.Tests.Visual.Audio
Text = $"High Pass: {highPassFilter.Cutoff}hz",
Font = new FontUsage(size: 40)
},
- highPassSlider = new OsuSliderBar
+ highPassSlider = new RoundedSliderBar
{
Width = 500,
Height = 50,
diff --git a/osu.Game.Tests/Visual/Background/TestSceneTriangleBorderShader.cs b/osu.Game.Tests/Visual/Background/TestSceneTriangleBorderShader.cs
index 185b83d1cc..271642edd1 100644
--- a/osu.Game.Tests/Visual/Background/TestSceneTriangleBorderShader.cs
+++ b/osu.Game.Tests/Visual/Background/TestSceneTriangleBorderShader.cs
@@ -14,7 +14,7 @@ namespace osu.Game.Tests.Visual.Background
{
public partial class TestSceneTriangleBorderShader : OsuTestScene
{
- private readonly TriangleBorder border;
+ private readonly TestTriangle triangle;
public TestSceneTriangleBorderShader()
{
@@ -25,11 +25,11 @@ namespace osu.Game.Tests.Visual.Background
RelativeSizeAxes = Axes.Both,
Colour = Color4.DarkGreen
},
- border = new TriangleBorder
+ triangle = new TestTriangle
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
- Size = new Vector2(100)
+ Size = new Vector2(200)
}
};
}
@@ -38,12 +38,13 @@ namespace osu.Game.Tests.Visual.Background
{
base.LoadComplete();
- AddSliderStep("Thickness", 0f, 1f, 0.02f, t => border.Thickness = t);
+ AddSliderStep("Thickness", 0f, 1f, 0.15f, t => triangle.Thickness = t);
+ AddSliderStep("Texel size", 0f, 0.1f, 0f, t => triangle.TexelSize = t);
}
- private partial class TriangleBorder : Sprite
+ private partial class TestTriangle : Sprite
{
- private float thickness = 0.02f;
+ private float thickness = 0.15f;
public float Thickness
{
@@ -55,6 +56,18 @@ namespace osu.Game.Tests.Visual.Background
}
}
+ private float texelSize;
+
+ public float TexelSize
+ {
+ get => texelSize;
+ set
+ {
+ texelSize = value;
+ Invalidate(Invalidation.DrawNode);
+ }
+ }
+
[BackgroundDependencyLoader]
private void load(ShaderManager shaders, IRenderer renderer)
{
@@ -62,29 +75,32 @@ namespace osu.Game.Tests.Visual.Background
Texture = renderer.WhitePixel;
}
- protected override DrawNode CreateDrawNode() => new TriangleBorderDrawNode(this);
+ protected override DrawNode CreateDrawNode() => new TriangleDrawNode(this);
- private class TriangleBorderDrawNode : SpriteDrawNode
+ private class TriangleDrawNode : SpriteDrawNode
{
- public new TriangleBorder Source => (TriangleBorder)base.Source;
+ public new TestTriangle Source => (TestTriangle)base.Source;
- public TriangleBorderDrawNode(TriangleBorder source)
+ public TriangleDrawNode(TestTriangle source)
: base(source)
{
}
private float thickness;
+ private float texelSize;
public override void ApplyState()
{
base.ApplyState();
thickness = Source.thickness;
+ texelSize = Source.texelSize;
}
public override void Draw(IRenderer renderer)
{
TextureShader.GetUniform("thickness").UpdateValue(ref thickness);
+ TextureShader.GetUniform("texelSize").UpdateValue(ref texelSize);
base.Draw(renderer);
}
diff --git a/osu.Game.Tests/Visual/Background/TestSceneTrianglesBackground.cs b/osu.Game.Tests/Visual/Background/TestSceneTrianglesBackground.cs
index d3cdf928e8..378dd99664 100644
--- a/osu.Game.Tests/Visual/Background/TestSceneTrianglesBackground.cs
+++ b/osu.Game.Tests/Visual/Background/TestSceneTrianglesBackground.cs
@@ -5,6 +5,7 @@ using osu.Game.Graphics.Backgrounds;
using osu.Framework.Graphics;
using osuTK.Graphics;
using osu.Framework.Graphics.Shapes;
+using osuTK;
namespace osu.Game.Tests.Visual.Background
{
@@ -25,7 +26,10 @@ namespace osu.Game.Tests.Visual.Background
{
RelativeSizeAxes = Axes.Both,
ColourLight = Color4.White,
- ColourDark = Color4.Gray
+ ColourDark = Color4.Gray,
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Size = new Vector2(0.9f)
}
};
}
@@ -35,6 +39,8 @@ namespace osu.Game.Tests.Visual.Background
base.LoadComplete();
AddSliderStep("Triangle scale", 0f, 10f, 1f, s => triangles.TriangleScale = s);
+ AddSliderStep("Seed", 0, 1000, 0, s => triangles.Reset(s));
+ AddToggleStep("Masking", m => triangles.Masking = m);
}
}
}
diff --git a/osu.Game.Tests/Visual/Background/TestSceneTrianglesV2Background.cs b/osu.Game.Tests/Visual/Background/TestSceneTrianglesV2Background.cs
index 8d6aef99ad..01a2464b8e 100644
--- a/osu.Game.Tests/Visual/Background/TestSceneTrianglesV2Background.cs
+++ b/osu.Game.Tests/Visual/Background/TestSceneTrianglesV2Background.cs
@@ -7,12 +7,16 @@ using osu.Framework.Graphics.Shapes;
using osuTK;
using osuTK.Graphics;
using osu.Game.Graphics.Backgrounds;
+using osu.Framework.Graphics.Colour;
+using osu.Game.Graphics.Sprites;
namespace osu.Game.Tests.Visual.Background
{
public partial class TestSceneTrianglesV2Background : OsuTestScene
{
private readonly TrianglesV2 triangles;
+ private readonly TrianglesV2 maskedTriangles;
+ private readonly Box box;
public TestSceneTrianglesV2Background()
{
@@ -23,27 +27,86 @@ namespace osu.Game.Tests.Visual.Background
RelativeSizeAxes = Axes.Both,
Colour = Color4.Gray
},
- new Container
+ new FillFlowContainer
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
- Size = new Vector2(500, 100),
- Masking = true,
- CornerRadius = 40,
+ AutoSizeAxes = Axes.Both,
+ Direction = FillDirection.Vertical,
+ Spacing = new Vector2(0, 10),
Children = new Drawable[]
{
- new Box
- {
- RelativeSizeAxes = Axes.Both,
- Colour = Color4.Red
- },
- triangles = new TrianglesV2
+ new OsuSpriteText
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
- RelativeSizeAxes = Axes.Both,
- ColourTop = Color4.White,
- ColourBottom = Color4.Red
+ Text = "Masked"
+ },
+ new Container
+ {
+ Size = new Vector2(500, 100),
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Masking = true,
+ CornerRadius = 40,
+ Children = new Drawable[]
+ {
+ new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ Colour = Color4.Red
+ },
+ triangles = new TrianglesV2
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ RelativeSizeAxes = Axes.Both
+ }
+ }
+ },
+ new OsuSpriteText
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Text = "Non-masked"
+ },
+ new Container
+ {
+ Size = new Vector2(500, 100),
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Children = new Drawable[]
+ {
+ new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ Colour = Color4.Red
+ },
+ maskedTriangles = new TrianglesV2
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ RelativeSizeAxes = Axes.Both
+ }
+ }
+ },
+ new OsuSpriteText
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Text = "Gradient comparison box"
+ },
+ new Container
+ {
+ Size = new Vector2(500, 100),
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Masking = true,
+ CornerRadius = 40,
+ Child = box = new Box
+ {
+ RelativeSizeAxes = Axes.Both
+ }
}
}
}
@@ -54,8 +117,18 @@ namespace osu.Game.Tests.Visual.Background
{
base.LoadComplete();
- AddSliderStep("Spawn ratio", 0f, 2f, 1f, s => triangles.SpawnRatio = s);
- AddSliderStep("Thickness", 0f, 1f, 0.02f, t => triangles.Thickness = t);
+ AddSliderStep("Spawn ratio", 0f, 10f, 1f, s =>
+ {
+ triangles.SpawnRatio = maskedTriangles.SpawnRatio = s;
+ triangles.Reset(1234);
+ maskedTriangles.Reset(1234);
+ });
+ AddSliderStep("Thickness", 0f, 1f, 0.02f, t => triangles.Thickness = maskedTriangles.Thickness = t);
+
+ AddStep("White colour", () => box.Colour = triangles.Colour = maskedTriangles.Colour = Color4.White);
+ AddStep("Vertical gradient", () => box.Colour = triangles.Colour = maskedTriangles.Colour = ColourInfo.GradientVertical(Color4.White, Color4.Red));
+ AddStep("Horizontal gradient", () => box.Colour = triangles.Colour = maskedTriangles.Colour = ColourInfo.GradientHorizontal(Color4.White, Color4.Red));
+ AddToggleStep("Masking", m => maskedTriangles.Masking = m);
}
}
}
diff --git a/osu.Game.Tests/Visual/Editing/TestSceneBlueprintOrdering.cs b/osu.Game.Tests/Visual/Editing/TestSceneBlueprintOrdering.cs
index 7728adecae..8b598a6a24 100644
--- a/osu.Game.Tests/Visual/Editing/TestSceneBlueprintOrdering.cs
+++ b/osu.Game.Tests/Visual/Editing/TestSceneBlueprintOrdering.cs
@@ -66,7 +66,7 @@ namespace osu.Game.Tests.Visual.Editing
AddStep("move mouse to common point", () =>
{
- var pos = blueprintContainer.ChildrenOfType().ElementAt(1).ScreenSpaceDrawQuad.Centre;
+ var pos = blueprintContainer.ChildrenOfType>().ElementAt(1).ScreenSpaceDrawQuad.Centre;
InputManager.MoveMouseTo(pos);
});
AddStep("right click", () => InputManager.Click(MouseButton.Right));
diff --git a/osu.Game.Tests/Visual/Editing/TestSceneComposerSelection.cs b/osu.Game.Tests/Visual/Editing/TestSceneComposerSelection.cs
index ffb8a67c68..b14025c9d8 100644
--- a/osu.Game.Tests/Visual/Editing/TestSceneComposerSelection.cs
+++ b/osu.Game.Tests/Visual/Editing/TestSceneComposerSelection.cs
@@ -286,7 +286,7 @@ namespace osu.Game.Tests.Visual.Editing
AddStep("move mouse to controlpoint", () =>
{
- var pos = blueprintContainer.ChildrenOfType().ElementAt(1).ScreenSpaceDrawQuad.Centre;
+ var pos = blueprintContainer.ChildrenOfType>().ElementAt(1).ScreenSpaceDrawQuad.Centre;
InputManager.MoveMouseTo(pos);
});
AddStep("hold shift", () => InputManager.PressKey(Key.ShiftLeft));
diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs
index a40c7814e1..5aa2dd2ebf 100644
--- a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs
+++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs
@@ -13,6 +13,7 @@ using osu.Framework.Screens;
using osu.Framework.Testing;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
+using osu.Game.Collections;
using osu.Game.Database;
using osu.Game.Overlays.Dialog;
using osu.Game.Rulesets;
@@ -20,6 +21,8 @@ using osu.Game.Rulesets.Catch;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.UI;
+using osu.Game.Rulesets.Taiko;
+using osu.Game.Rulesets.Taiko.Objects;
using osu.Game.Screens.Edit;
using osu.Game.Screens.Edit.Setup;
using osu.Game.Storyboards;
@@ -40,6 +43,9 @@ namespace osu.Game.Tests.Visual.Editing
[Resolved]
private BeatmapManager beatmapManager { get; set; } = null!;
+ [Resolved]
+ private RealmAccess realm { get; set; } = null!;
+
private Guid currentBeatmapSetID => EditorBeatmap.BeatmapInfo.BeatmapSet?.ID ?? Guid.Empty;
public override void SetUpSteps()
@@ -222,7 +228,8 @@ namespace osu.Game.Tests.Visual.Editing
return beatmap != null
&& beatmap.DifficultyName == secondDifficultyName
&& set != null
- && set.PerformRead(s => s.Beatmaps.Count == 2 && s.Beatmaps.Any(b => b.DifficultyName == secondDifficultyName) && s.Beatmaps.All(b => s.Status == BeatmapOnlineStatus.LocallyModified));
+ && set.PerformRead(s =>
+ s.Beatmaps.Count == 2 && s.Beatmaps.Any(b => b.DifficultyName == secondDifficultyName) && s.Beatmaps.All(b => s.Status == BeatmapOnlineStatus.LocallyModified));
});
}
@@ -325,6 +332,56 @@ namespace osu.Game.Tests.Visual.Editing
AddAssert("old beatmap file not deleted", () => refetchedBeatmapSet.AsNonNull().PerformRead(s => s.Files.Count == 2));
}
+ [Test]
+ public void TestCopyDifficultyDoesNotChangeCollections()
+ {
+ string originalDifficultyName = Guid.NewGuid().ToString();
+
+ AddStep("set unique difficulty name", () => EditorBeatmap.BeatmapInfo.DifficultyName = originalDifficultyName);
+ AddStep("save beatmap", () => Editor.Save());
+
+ string originalMd5 = string.Empty;
+ BeatmapCollection collection = null!;
+
+ AddStep("setup a collection with original beatmap", () =>
+ {
+ collection = new BeatmapCollection("test copy");
+ collection.BeatmapMD5Hashes.Add(originalMd5 = EditorBeatmap.BeatmapInfo.MD5Hash);
+
+ realm.Write(r =>
+ {
+ r.Add(collection);
+ });
+ });
+
+ AddAssert("collection contains original beatmap", () =>
+ !string.IsNullOrEmpty(originalMd5) && collection.BeatmapMD5Hashes.Contains(originalMd5));
+
+ AddStep("create new difficulty", () => Editor.CreateNewDifficulty(new OsuRuleset().RulesetInfo));
+
+ AddUntilStep("wait for dialog", () => DialogOverlay.CurrentDialog is CreateNewDifficultyDialog);
+ AddStep("confirm creation as a copy", () => DialogOverlay.CurrentDialog.Buttons.ElementAt(1).TriggerClick());
+
+ AddUntilStep("wait for created", () =>
+ {
+ string? difficultyName = Editor.ChildrenOfType().SingleOrDefault()?.BeatmapInfo.DifficultyName;
+ return difficultyName != null && difficultyName != originalDifficultyName;
+ });
+
+ AddStep("save without changes", () => Editor.Save());
+
+ AddAssert("collection still points to old beatmap", () => !collection.BeatmapMD5Hashes.Contains(EditorBeatmap.BeatmapInfo.MD5Hash)
+ && collection.BeatmapMD5Hashes.Contains(originalMd5));
+
+ AddStep("clean up collection", () =>
+ {
+ realm.Write(r =>
+ {
+ r.Remove(collection);
+ });
+ });
+ }
+
[Test]
public void TestCreateMultipleNewDifficultiesSucceeds()
{
@@ -395,5 +452,52 @@ namespace osu.Game.Tests.Visual.Editing
return set != null && set.PerformRead(s => s.Beatmaps.Count == 2 && s.Files.Count == 2);
});
}
+
+ [Test]
+ public void TestCreateNewDifficultyForInconvertibleRuleset()
+ {
+ Guid setId = Guid.Empty;
+
+ AddStep("retrieve set ID", () => setId = EditorBeatmap.BeatmapInfo.BeatmapSet!.ID);
+ AddStep("save beatmap", () => Editor.Save());
+ AddStep("try to create new taiko difficulty", () => Editor.CreateNewDifficulty(new TaikoRuleset().RulesetInfo));
+
+ AddUntilStep("wait for created", () =>
+ {
+ string? difficultyName = Editor.ChildrenOfType().SingleOrDefault()?.BeatmapInfo.DifficultyName;
+ return difficultyName != null && difficultyName == "New Difficulty";
+ });
+ AddAssert("new difficulty persisted", () =>
+ {
+ var set = beatmapManager.QueryBeatmapSet(s => s.ID == setId);
+ return set != null && set.PerformRead(s => s.Beatmaps.Count == 2 && s.Files.Count == 2);
+ });
+
+ AddStep("add timing point", () => EditorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = 1000 }));
+ AddStep("add hitobjects", () => EditorBeatmap.AddRange(new[]
+ {
+ new Hit
+ {
+ StartTime = 0
+ },
+ new Hit
+ {
+ StartTime = 1000
+ }
+ }));
+ AddStep("save beatmap", () => Editor.Save());
+ AddStep("try to create new catch difficulty", () => Editor.CreateNewDifficulty(new CatchRuleset().RulesetInfo));
+
+ AddUntilStep("wait for created", () =>
+ {
+ string? difficultyName = Editor.ChildrenOfType().SingleOrDefault()?.BeatmapInfo.DifficultyName;
+ return difficultyName != null && difficultyName == "New Difficulty (1)";
+ });
+ AddAssert("new difficulty persisted", () =>
+ {
+ var set = beatmapManager.QueryBeatmapSet(s => s.ID == setId);
+ return set != null && set.PerformRead(s => s.Beatmaps.Count == 3 && s.Files.Count == 3);
+ });
+ }
}
}
diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs
index 70118e0b67..b396b382ff 100644
--- a/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs
+++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs
@@ -144,7 +144,7 @@ namespace osu.Game.Tests.Visual.Editing
double lastStarRating = 0;
double lastLength = 0;
- AddStep("Add timing point", () => EditorBeatmap.ControlPointInfo.Add(500, new TimingControlPoint()));
+ AddStep("Add timing point", () => EditorBeatmap.ControlPointInfo.Add(200, new TimingControlPoint { BeatLength = 600 }));
AddStep("Change to placement mode", () => InputManager.Key(Key.Number2));
AddStep("Move to playfield", () => InputManager.MoveMouseTo(Game.ScreenSpaceDrawQuad.Centre));
AddStep("Place single hitcircle", () => InputManager.Click(MouseButton.Left));
diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorSummaryTimeline.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorSummaryTimeline.cs
index ccd2feef9c..f255dd08a8 100644
--- a/osu.Game.Tests/Visual/Editing/TestSceneEditorSummaryTimeline.cs
+++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorSummaryTimeline.cs
@@ -6,6 +6,7 @@
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
+using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Osu;
using osu.Game.Screens.Edit;
using osu.Game.Screens.Edit.Components.Timelines.Summary;
@@ -21,7 +22,13 @@ namespace osu.Game.Tests.Visual.Editing
public TestSceneEditorSummaryTimeline()
{
- editorBeatmap = new EditorBeatmap(CreateBeatmap(new OsuRuleset().RulesetInfo));
+ var beatmap = CreateBeatmap(new OsuRuleset().RulesetInfo);
+
+ beatmap.ControlPointInfo.Add(100000, new TimingControlPoint { BeatLength = 100 });
+ beatmap.ControlPointInfo.Add(50000, new DifficultyControlPoint { SliderVelocity = 2 });
+ beatmap.BeatmapInfo.Bookmarks = new[] { 75000, 125000 };
+
+ editorBeatmap = new EditorBeatmap(beatmap);
}
protected override void LoadComplete()
diff --git a/osu.Game.Tests/Visual/Editing/TestScenePreviewTime.cs b/osu.Game.Tests/Visual/Editing/TestScenePreviewTime.cs
new file mode 100644
index 0000000000..3319788c8a
--- /dev/null
+++ b/osu.Game.Tests/Visual/Editing/TestScenePreviewTime.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 System.Linq;
+using NUnit.Framework;
+using osu.Framework.Testing;
+using osu.Game.Rulesets;
+using osu.Game.Rulesets.Osu;
+using osu.Game.Screens.Edit.Components.Timelines.Summary.Parts;
+
+namespace osu.Game.Tests.Visual.Editing
+{
+ public partial class TestScenePreviewTime : EditorTestScene
+ {
+ protected override Ruleset CreateEditorRuleset() => new OsuRuleset();
+
+ [Test]
+ public void TestSceneSetPreviewTimingPoint()
+ {
+ AddStep("seek to 1000", () => EditorClock.Seek(1000));
+ AddAssert("time is 1000", () => EditorClock.CurrentTime == 1000);
+ AddStep("set current time as preview point", () => Editor.SetPreviewPointToCurrentTime());
+ AddAssert("preview time is 1000", () => EditorBeatmap.PreviewTime.Value == 1000);
+ }
+
+ [Test]
+ public void TestScenePreviewTimeline()
+ {
+ AddStep("set preview time to -1", () => EditorBeatmap.PreviewTime.Value = -1);
+ AddAssert("preview time line should not show", () => !Editor.ChildrenOfType().Single().Children.Any());
+ AddStep("set preview time to 1000", () => EditorBeatmap.PreviewTime.Value = 1000);
+ AddAssert("preview time line should show", () => Editor.ChildrenOfType().Single().Children.Single().Alpha == 1);
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/Editing/TestSceneTimingScreen.cs b/osu.Game.Tests/Visual/Editing/TestSceneTimingScreen.cs
index 86a977fd3f..216c35de65 100644
--- a/osu.Game.Tests/Visual/Editing/TestSceneTimingScreen.cs
+++ b/osu.Game.Tests/Visual/Editing/TestSceneTimingScreen.cs
@@ -10,6 +10,8 @@ using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Testing;
+using osu.Game.Beatmaps.ControlPoints;
+using osu.Game.Graphics.Containers;
using osu.Game.Overlays;
using osu.Game.Rulesets.Edit;
using osu.Game.Screens.Edit;
@@ -26,6 +28,7 @@ namespace osu.Game.Tests.Visual.Editing
private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Blue);
private TimingScreen timingScreen;
+ private EditorBeatmap editorBeatmap;
protected override bool ScrollUsingMouseWheel => false;
@@ -35,8 +38,11 @@ namespace osu.Game.Tests.Visual.Editing
Beatmap.Value = CreateWorkingBeatmap(Ruleset.Value);
Beatmap.Disabled = true;
+ }
- var editorBeatmap = new EditorBeatmap(Beatmap.Value.GetPlayableBeatmap(Ruleset.Value));
+ private void reloadEditorBeatmap()
+ {
+ editorBeatmap = new EditorBeatmap(Beatmap.Value.GetPlayableBeatmap(Ruleset.Value));
Child = new DependencyProvidingContainer
{
@@ -58,7 +64,9 @@ namespace osu.Game.Tests.Visual.Editing
{
AddStep("Stop clock", () => EditorClock.Stop());
- AddUntilStep("wait for rows to load", () => Child.ChildrenOfType().Any());
+ AddStep("Reload Editor Beatmap", reloadEditorBeatmap);
+
+ AddUntilStep("Wait for rows to load", () => Child.ChildrenOfType().Any());
}
[Test]
@@ -95,6 +103,37 @@ namespace osu.Game.Tests.Visual.Editing
AddUntilStep("Selection changed", () => timingScreen.SelectedGroup.Value.Time == 69670);
}
+ [Test]
+ public void TestScrollControlGroupIntoView()
+ {
+ AddStep("Add many control points", () =>
+ {
+ editorBeatmap.ControlPointInfo.Clear();
+
+ editorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint());
+
+ for (int i = 0; i < 100; i++)
+ {
+ editorBeatmap.ControlPointInfo.Add((i + 1) * 1000, new EffectControlPoint
+ {
+ KiaiMode = Convert.ToBoolean(i % 2),
+ });
+ }
+ });
+
+ AddStep("Select first effect point", () =>
+ {
+ InputManager.MoveMouseTo(Child.ChildrenOfType().First());
+ InputManager.Click(MouseButton.Left);
+ });
+
+ AddStep("Seek to beginning", () => EditorClock.Seek(0));
+
+ AddStep("Seek to last point", () => EditorClock.Seek(101 * 1000));
+
+ AddUntilStep("Scrolled to end", () => timingScreen.ChildrenOfType().First().IsScrolledToEnd());
+ }
+
protected override void Dispose(bool isDisposing)
{
Beatmap.Disabled = false;
diff --git a/osu.Game.Tests/Visual/Editing/TestSceneZoomableScrollContainer.cs b/osu.Game.Tests/Visual/Editing/TestSceneZoomableScrollContainer.cs
index 6bc2922253..a141e4d431 100644
--- a/osu.Game.Tests/Visual/Editing/TestSceneZoomableScrollContainer.cs
+++ b/osu.Game.Tests/Visual/Editing/TestSceneZoomableScrollContainer.cs
@@ -23,7 +23,7 @@ namespace osu.Game.Tests.Visual.Editing
{
public partial class TestSceneZoomableScrollContainer : OsuManualInputManagerTestScene
{
- private ZoomableScrollContainer scrollContainer;
+ private TestZoomableScrollContainer scrollContainer;
private Drawable innerBox;
[SetUpSteps]
@@ -47,7 +47,7 @@ namespace osu.Game.Tests.Visual.Editing
RelativeSizeAxes = Axes.Both,
Colour = OsuColour.Gray(30)
},
- scrollContainer = new ZoomableScrollContainer(1, 60, 1)
+ scrollContainer = new TestZoomableScrollContainer(1, 60, 1)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
@@ -93,6 +93,14 @@ namespace osu.Game.Tests.Visual.Editing
AddAssert("Inner container width matches scroll container", () => innerBox.DrawWidth == scrollContainer.DrawWidth);
}
+ [Test]
+ public void TestWidthUpdatesOnSecondZoomSetup()
+ {
+ AddAssert("Inner container width = 1x", () => innerBox.DrawWidth == scrollContainer.DrawWidth);
+ AddStep("reload zoom", () => scrollContainer.SetupZoom(10, 10, 60));
+ AddAssert("Inner container width = 10x", () => innerBox.DrawWidth == scrollContainer.DrawWidth * 10);
+ }
+
[Test]
public void TestZoom0()
{
@@ -190,5 +198,15 @@ namespace osu.Game.Tests.Visual.Editing
private Quad scrollQuad => scrollContainer.ScreenSpaceDrawQuad;
private Quad boxQuad => innerBox.ScreenSpaceDrawQuad;
+
+ private partial class TestZoomableScrollContainer : ZoomableScrollContainer
+ {
+ public TestZoomableScrollContainer(int minimum, float maximum, float initial)
+ : base(minimum, maximum, initial)
+ {
+ }
+
+ public new void SetupZoom(float initial, float minimum, float maximum) => base.SetupZoom(initial, minimum, maximum);
+ }
}
}
diff --git a/osu.Game.Tests/Visual/Gameplay/SkinnableHUDComponentTestScene.cs b/osu.Game.Tests/Visual/Gameplay/SkinnableHUDComponentTestScene.cs
index 545b3c2cf4..f54f50795e 100644
--- a/osu.Game.Tests/Visual/Gameplay/SkinnableHUDComponentTestScene.cs
+++ b/osu.Game.Tests/Visual/Gameplay/SkinnableHUDComponentTestScene.cs
@@ -20,7 +20,9 @@ namespace osu.Game.Tests.Visual.Gameplay
{
var implementation = skin is LegacySkin
? CreateLegacyImplementation()
- : CreateDefaultImplementation();
+ : skin is ArgonSkin
+ ? CreateArgonImplementation()
+ : CreateDefaultImplementation();
implementation.Anchor = Anchor.Centre;
implementation.Origin = Anchor.Centre;
@@ -29,6 +31,7 @@ namespace osu.Game.Tests.Visual.Gameplay
});
protected abstract Drawable CreateDefaultImplementation();
+ protected virtual Drawable CreateArgonImplementation() => CreateDefaultImplementation();
protected abstract Drawable CreateLegacyImplementation();
}
}
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs
index 5cd8c00935..514a2d7e84 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs
@@ -1,26 +1,23 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using System;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Audio;
+using osu.Framework.Graphics.Containers;
using osu.Framework.Lists;
using osu.Framework.Testing;
using osu.Framework.Timing;
using osu.Framework.Utils;
using osu.Game.Beatmaps;
using osu.Game.Database;
-using osu.Game.Extensions;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Skinning.Legacy;
using osu.Game.Rulesets.Scoring;
using osu.Game.Screens.Play;
-using osu.Game.Screens.Play.HUD;
using osu.Game.Skinning;
using osu.Game.Storyboards;
@@ -28,10 +25,10 @@ namespace osu.Game.Tests.Visual.Gameplay
{
public partial class TestSceneBeatmapSkinFallbacks : OsuPlayerTestScene
{
- private ISkin currentBeatmapSkin;
+ private ISkin currentBeatmapSkin = null!;
[Resolved]
- private SkinManager skinManager { get; set; }
+ private SkinManager skinManager { get; set; } = null!;
protected override bool HasCustomSteps => true;
@@ -39,8 +36,8 @@ namespace osu.Game.Tests.Visual.Gameplay
public void TestEmptyLegacyBeatmapSkinFallsBack()
{
CreateSkinTest(TrianglesSkin.CreateInfo(), () => new LegacyBeatmapSkin(new BeatmapInfo(), null));
- AddUntilStep("wait for hud load", () => Player.ChildrenOfType().All(c => c.ComponentsLoaded));
- AddAssert("hud from default skin", () => AssertComponentsFromExpectedSource(GlobalSkinComponentLookup.LookupType.MainHUDComponents, skinManager.CurrentSkin.Value));
+ AddUntilStep("wait for hud load", () => Player.ChildrenOfType().All(c => c.ComponentsLoaded));
+ AddAssert("hud from default skin", () => AssertComponentsFromExpectedSource(SkinComponentsContainerLookup.TargetArea.MainHUDComponents, skinManager.CurrentSkin.Value));
}
protected void CreateSkinTest(SkinInfo gameCurrentSkin, Func getBeatmapSkin)
@@ -55,17 +52,17 @@ namespace osu.Game.Tests.Visual.Gameplay
});
}
- protected bool AssertComponentsFromExpectedSource(GlobalSkinComponentLookup.LookupType target, ISkin expectedSource)
+ protected bool AssertComponentsFromExpectedSource(SkinComponentsContainerLookup.TargetArea target, ISkin expectedSource)
{
- var actualComponentsContainer = Player.ChildrenOfType().First(s => s.Target == target)
- .ChildrenOfType().SingleOrDefault();
+ var targetContainer = Player.ChildrenOfType().First(s => s.Lookup.Target == target);
+ var actualComponentsContainer = targetContainer.ChildrenOfType().SingleOrDefault(c => c.Parent == targetContainer);
if (actualComponentsContainer == null)
return false;
- var actualInfo = actualComponentsContainer.CreateSkinnableInfo();
+ var actualInfo = actualComponentsContainer.CreateSerialisedInfo();
- var expectedComponentsContainer = (SkinnableTargetComponentsContainer)expectedSource.GetDrawableComponent(new GlobalSkinComponentLookup(target));
+ var expectedComponentsContainer = expectedSource.GetDrawableComponent(new SkinComponentsContainerLookup(target)) as Container;
if (expectedComponentsContainer == null)
return false;
@@ -86,23 +83,23 @@ namespace osu.Game.Tests.Visual.Gameplay
Add(expectedComponentsAdjustmentContainer);
expectedComponentsAdjustmentContainer.UpdateSubTree();
- var expectedInfo = expectedComponentsContainer.CreateSkinnableInfo();
+ var expectedInfo = expectedComponentsContainer.CreateSerialisedInfo();
Remove(expectedComponentsAdjustmentContainer, true);
return almostEqual(actualInfo, expectedInfo);
}
- private static bool almostEqual(SkinnableInfo info, SkinnableInfo other) =>
+ private static bool almostEqual(SerialisedDrawableInfo drawableInfo, SerialisedDrawableInfo? other) =>
other != null
- && info.Type == other.Type
- && info.Anchor == other.Anchor
- && info.Origin == other.Origin
- && Precision.AlmostEquals(info.Position, other.Position, 1)
- && Precision.AlmostEquals(info.Scale, other.Scale)
- && Precision.AlmostEquals(info.Rotation, other.Rotation)
- && info.Children.SequenceEqual(other.Children, new FuncEqualityComparer(almostEqual));
+ && drawableInfo.Type == other.Type
+ && drawableInfo.Anchor == other.Anchor
+ && drawableInfo.Origin == other.Origin
+ && Precision.AlmostEquals(drawableInfo.Position, other.Position, 1)
+ && Precision.AlmostEquals(drawableInfo.Scale, other.Scale)
+ && Precision.AlmostEquals(drawableInfo.Rotation, other.Rotation)
+ && drawableInfo.Children.SequenceEqual(other.Children, new FuncEqualityComparer(almostEqual));
- protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null)
+ protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard? storyboard = null)
=> new CustomSkinWorkingBeatmap(beatmap, storyboard, Clock, Audio, currentBeatmapSkin);
protected override Ruleset CreatePlayerRuleset() => new TestOsuRuleset();
@@ -111,7 +108,7 @@ namespace osu.Game.Tests.Visual.Gameplay
{
private readonly ISkin beatmapSkin;
- public CustomSkinWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard, IFrameBasedClock referenceClock, AudioManager audio, ISkin beatmapSkin)
+ public CustomSkinWorkingBeatmap(IBeatmap beatmap, Storyboard? storyboard, IFrameBasedClock referenceClock, AudioManager audio, ISkin beatmapSkin)
: base(beatmap, storyboard, referenceClock, audio)
{
this.beatmapSkin = beatmapSkin;
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSongProgressGraph.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneDefaultSongProgressGraph.cs
similarity index 93%
rename from osu.Game.Tests/Visual/Gameplay/TestSceneSongProgressGraph.cs
rename to osu.Game.Tests/Visual/Gameplay/TestSceneDefaultSongProgressGraph.cs
index 5a61502978..66671a506f 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneSongProgressGraph.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneDefaultSongProgressGraph.cs
@@ -14,7 +14,7 @@ using osu.Game.Screens.Play.HUD;
namespace osu.Game.Tests.Visual.Gameplay
{
[TestFixture]
- public partial class TestSceneSongProgressGraph : OsuTestScene
+ public partial class TestSceneDefaultSongProgressGraph : OsuTestScene
{
private TestSongProgressGraph graph;
@@ -59,7 +59,7 @@ namespace osu.Game.Tests.Visual.Gameplay
graph.Objects = objects;
}
- private partial class TestSongProgressGraph : SongProgressGraph
+ private partial class TestSongProgressGraph : DefaultSongProgressGraph
{
public int CreationCount { get; private set; }
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySampleTriggerSource.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySampleTriggerSource.cs
index e184d50d7c..31133f00d9 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySampleTriggerSource.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySampleTriggerSource.cs
@@ -1,50 +1,67 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
+using System.Diagnostics;
using System.Linq;
using NUnit.Framework;
+using osu.Framework.Allocation;
+using osu.Framework.Audio;
+using osu.Framework.Timing;
+using osu.Framework.Utils;
using osu.Game.Audio;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
+using osu.Game.Beatmaps.Legacy;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables;
+using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.UI;
+using osu.Game.Storyboards;
+using osuTK;
using osuTK.Input;
namespace osu.Game.Tests.Visual.Gameplay
{
public partial class TestSceneGameplaySampleTriggerSource : PlayerTestScene
{
- private TestGameplaySampleTriggerSource sampleTriggerSource;
+ private TestGameplaySampleTriggerSource sampleTriggerSource = null!;
protected override Ruleset CreatePlayerRuleset() => new OsuRuleset();
- private Beatmap beatmap;
+ private Beatmap beatmap = null!;
+
+ [Resolved]
+ private AudioManager audio { get; set; } = null!;
+
+ protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard? storyboard = null)
+ => new ClockBackedTestWorkingBeatmap(beatmap, storyboard, new FramedClock(new ManualClock { Rate = 1 }), audio);
protected override IBeatmap CreateBeatmap(RulesetInfo ruleset)
{
+ ControlPointInfo controlPointInfo = new LegacyControlPointInfo();
+
beatmap = new Beatmap
{
BeatmapInfo = new BeatmapInfo
{
Difficulty = new BeatmapDifficulty { CircleSize = 6, SliderMultiplier = 3 },
Ruleset = ruleset
- }
+ },
+ ControlPointInfo = controlPointInfo
};
const double start_offset = 8000;
const double spacing = 2000;
+ // intentionally start objects a bit late so we can test the case of no alive objects.
double t = start_offset;
- beatmap.HitObjects.AddRange(new[]
+
+ beatmap.HitObjects.AddRange(new HitObject[]
{
new HitCircle
{
- // intentionally start objects a bit late so we can test the case of no alive objects.
StartTime = t += spacing,
Samples = new[] { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) }
},
@@ -61,12 +78,24 @@ namespace osu.Game.Tests.Visual.Gameplay
},
new HitCircle
{
- StartTime = t + spacing,
+ StartTime = t += spacing,
+ },
+ new Slider
+ {
+ StartTime = t += spacing,
+ Path = new SliderPath(PathType.Linear, new[] { Vector2.Zero, Vector2.UnitY * 200 }),
Samples = new[] { new HitSampleInfo(HitSampleInfo.HIT_WHISTLE) },
SampleControlPoint = new SampleControlPoint { SampleBank = "soft" },
},
});
+ // Add a change in volume halfway through final slider.
+ controlPointInfo.Add(t, new SampleControlPoint
+ {
+ SampleBank = "normal",
+ SampleVolume = 20,
+ });
+
return beatmap;
}
@@ -80,42 +109,88 @@ namespace osu.Game.Tests.Visual.Gameplay
[Test]
public void TestCorrectHitObject()
{
- HitObjectLifetimeEntry nextObjectEntry = null;
+ waitForAliveObjectIndex(null);
+ checkValidObjectIndex(0);
- AddAssert("no alive objects", () => getNextAliveObject() == null);
+ seekBeforeIndex(0);
+ waitForAliveObjectIndex(0);
+ checkValidObjectIndex(0);
- AddAssert("check initially correct object", () => sampleTriggerSource.GetMostValidObject() == beatmap.HitObjects[0]);
+ AddAssert("first object not hit", () => getNextAliveObject()?.Entry?.Result?.HasResult != true);
- AddUntilStep("get next object", () =>
+ AddStep("hit first object", () =>
{
- var nextDrawableObject = getNextAliveObject();
+ var next = getNextAliveObject();
- if (nextDrawableObject != null)
+ if (next != null)
{
- nextObjectEntry = nextDrawableObject.Entry;
- InputManager.MoveMouseTo(nextDrawableObject.ScreenSpaceDrawQuad.Centre);
- return true;
+ Debug.Assert(next.Entry?.Result?.HasResult != true);
+
+ InputManager.MoveMouseTo(next.ScreenSpaceDrawQuad.Centre);
+ InputManager.Click(MouseButton.Left);
}
-
- return false;
});
- AddUntilStep("hit first hitobject", () =>
- {
- InputManager.Click(MouseButton.Left);
- return nextObjectEntry.Result?.HasResult == true;
- });
+ AddAssert("first object hit", () => getNextAliveObject()?.Entry?.Result?.HasResult == true);
- AddAssert("check correct object after hit", () => sampleTriggerSource.GetMostValidObject() == beatmap.HitObjects[1]);
+ checkValidObjectIndex(1);
- AddUntilStep("check correct object after miss", () => sampleTriggerSource.GetMostValidObject() == beatmap.HitObjects[2]);
- AddUntilStep("check correct object after miss", () => sampleTriggerSource.GetMostValidObject() == beatmap.HitObjects[3]);
+ // Still object 1 as it's not hit yet.
+ seekBeforeIndex(1);
+ waitForAliveObjectIndex(1);
+ checkValidObjectIndex(1);
- AddUntilStep("no alive objects", () => getNextAliveObject() == null);
- AddAssert("check correct object after none alive", () => sampleTriggerSource.GetMostValidObject() == beatmap.HitObjects[3]);
+ seekBeforeIndex(2);
+ waitForAliveObjectIndex(2);
+ checkValidObjectIndex(2);
+
+ seekBeforeIndex(3);
+ waitForAliveObjectIndex(3);
+ checkValidObjectIndex(3);
+
+ seekBeforeIndex(4);
+ waitForAliveObjectIndex(4);
+
+ // Even before the object, we should prefer the first nested object's sample.
+ // This is because the (parent) object will only play its sample at the final EndTime.
+ AddAssert("check valid object is slider's first nested", () => sampleTriggerSource.GetMostValidObject(), () => Is.EqualTo(beatmap.HitObjects[4].NestedHitObjects.First()));
+
+ AddStep("seek to just before slider ends", () => Player.GameplayClockContainer.Seek(beatmap.HitObjects[4].GetEndTime() - 100));
+ waitForCatchUp();
+ AddUntilStep("wait until valid object is slider's last nested", () => sampleTriggerSource.GetMostValidObject(), () => Is.EqualTo(beatmap.HitObjects[4].NestedHitObjects.Last()));
+
+ // After we get far enough away, the samples of the object itself should be used, not any nested object.
+ AddStep("seek to further after slider", () => Player.GameplayClockContainer.Seek(beatmap.HitObjects[4].GetEndTime() + 1000));
+ waitForCatchUp();
+ AddUntilStep("wait until valid object is slider itself", () => sampleTriggerSource.GetMostValidObject(), () => Is.EqualTo(beatmap.HitObjects[4]));
+
+ AddStep("Seek into future", () => Player.GameplayClockContainer.Seek(beatmap.HitObjects.Last().GetEndTime() + 10000));
+ waitForCatchUp();
+ waitForAliveObjectIndex(null);
+ checkValidObjectIndex(4);
}
- private DrawableHitObject getNextAliveObject() =>
+ private void seekBeforeIndex(int index)
+ {
+ AddStep($"seek to just before object {index}", () => Player.GameplayClockContainer.Seek(beatmap.HitObjects[index].StartTime - 100));
+ waitForCatchUp();
+ }
+
+ private void waitForCatchUp() =>
+ AddUntilStep("wait for frame stable clock to catch up", () => Precision.AlmostEquals(Player.GameplayClockContainer.CurrentTime, Player.DrawableRuleset.FrameStableClock.CurrentTime));
+
+ private void waitForAliveObjectIndex(int? index)
+ {
+ if (index == null)
+ AddUntilStep("wait for no alive objects", getNextAliveObject, () => Is.Null);
+ else
+ AddUntilStep($"wait for next alive to be {index}", () => getNextAliveObject()?.HitObject, () => Is.EqualTo(beatmap.HitObjects[index.Value]));
+ }
+
+ private void checkValidObjectIndex(int index) =>
+ AddAssert($"check valid object is {index}", () => sampleTriggerSource.GetMostValidObject(), () => Is.EqualTo(beatmap.HitObjects[index]));
+
+ private DrawableHitObject? getNextAliveObject() =>
Player.DrawableRuleset.Playfield.HitObjectContainer.AliveObjects.FirstOrDefault();
[Test]
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs
index 94f9b4262d..b918c5e64a 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs
@@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using System;
+using System.Diagnostics;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
@@ -187,20 +188,24 @@ namespace osu.Game.Tests.Visual.Gameplay
[Test]
public void TestInputDoesntWorkWhenHUDHidden()
{
- SongProgressBar getSongProgress() => hudOverlay.ChildrenOfType().Single();
+ ArgonSongProgress? getSongProgress() => hudOverlay.ChildrenOfType().SingleOrDefault();
bool seeked = false;
createNew();
+ AddUntilStep("wait for song progress", () => getSongProgress() != null);
+
AddStep("bind seek", () =>
{
seeked = false;
var progress = getSongProgress();
- progress.ShowHandle = true;
- progress.OnSeek += _ => seeked = true;
+ Debug.Assert(progress != null);
+
+ progress.Interactive.Value = true;
+ progress.ChildrenOfType().Single().OnSeek += _ => seeked = true;
});
AddStep("set showhud false", () => hudOverlay.ShowHud.Value = false);
@@ -230,8 +235,8 @@ namespace osu.Game.Tests.Visual.Gameplay
createNew();
AddUntilStep("wait for hud load", () => hudOverlay.IsLoaded);
- AddUntilStep("wait for components to be hidden", () => hudOverlay.ChildrenOfType().Single().Alpha == 0);
- AddUntilStep("wait for hud load", () => hudOverlay.ChildrenOfType().All(c => c.ComponentsLoaded));
+ AddUntilStep("wait for components to be hidden", () => hudOverlay.ChildrenOfType().Single().Alpha == 0);
+ AddUntilStep("wait for hud load", () => hudOverlay.ChildrenOfType().All(c => c.ComponentsLoaded));
AddStep("bind on update", () =>
{
@@ -249,10 +254,10 @@ namespace osu.Game.Tests.Visual.Gameplay
createNew();
AddUntilStep("wait for hud load", () => hudOverlay.IsLoaded);
- AddUntilStep("wait for components to be hidden", () => hudOverlay.ChildrenOfType().Single().Alpha == 0);
+ AddUntilStep("wait for components to be hidden", () => hudOverlay.ChildrenOfType().Single().Alpha == 0);
- AddStep("reload components", () => hudOverlay.ChildrenOfType().Single().Reload());
- AddUntilStep("skinnable components loaded", () => hudOverlay.ChildrenOfType().Single().ComponentsLoaded);
+ AddStep("reload components", () => hudOverlay.ChildrenOfType().Single().Reload());
+ AddUntilStep("skinnable components loaded", () => hudOverlay.ChildrenOfType().Single().ComponentsLoaded);
}
private void createNew(Action? action = null)
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorMeter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorMeter.cs
index e2ff2780e0..56900a0549 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorMeter.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorMeter.cs
@@ -9,6 +9,7 @@ using System.Diagnostics.CodeAnalysis;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
+using osu.Framework.Audio;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
@@ -281,6 +282,7 @@ namespace osu.Game.Tests.Visual.Gameplay
remove => throw new InvalidOperationException($"{nameof(RevertResult)} operations not supported in test context");
}
+ public override IAdjustableAudioComponent Audio { get; }
public override Playfield Playfield { get; }
public override Container Overlays { get; }
public override Container FrameStableComponents { get; }
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneJudgementCounter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneJudgementCounter.cs
new file mode 100644
index 0000000000..5a802e0d36
--- /dev/null
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneJudgementCounter.cs
@@ -0,0 +1,182 @@
+// 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.Diagnostics;
+using System.Linq;
+using NUnit.Framework;
+using osu.Framework.Bindables;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Testing;
+using osu.Game.Rulesets;
+using osu.Game.Rulesets.Judgements;
+using osu.Game.Rulesets.Mania;
+using osu.Game.Rulesets.Objects;
+using osu.Game.Rulesets.Osu.Judgements;
+using osu.Game.Rulesets.Scoring;
+using osu.Game.Screens.Play.HUD.JudgementCounter;
+
+namespace osu.Game.Tests.Visual.Gameplay
+{
+ public partial class TestSceneJudgementCounter : OsuTestScene
+ {
+ private ScoreProcessor scoreProcessor = null!;
+ private JudgementTally judgementTally = null!;
+ private TestJudgementCounterDisplay counterDisplay = null!;
+
+ private DependencyProvidingContainer content = null!;
+
+ protected override Container Content => content;
+
+ private readonly Bindable lastJudgementResult = new Bindable();
+
+ private int iteration;
+
+ [SetUpSteps]
+ public void SetUpSteps() => AddStep("Create components", () =>
+ {
+ var ruleset = CreateRuleset();
+
+ Debug.Assert(ruleset != null);
+
+ scoreProcessor = new ScoreProcessor(ruleset);
+ base.Content.Child = new DependencyProvidingContainer
+ {
+ RelativeSizeAxes = Axes.Both,
+ CachedDependencies = new (Type, object)[] { (typeof(ScoreProcessor), scoreProcessor), (typeof(Ruleset), ruleset) },
+ Children = new Drawable[]
+ {
+ judgementTally = new JudgementTally(),
+ content = new DependencyProvidingContainer
+ {
+ RelativeSizeAxes = Axes.Both,
+ CachedDependencies = new (Type, object)[] { (typeof(JudgementTally), judgementTally) },
+ }
+ },
+ };
+ });
+
+ protected override Ruleset CreateRuleset() => new ManiaRuleset();
+
+ private void applyOneJudgement(HitResult result)
+ {
+ lastJudgementResult.Value = new OsuJudgementResult(new HitObject
+ {
+ StartTime = iteration * 10000
+ }, new OsuJudgement())
+ {
+ Type = result,
+ };
+ scoreProcessor.ApplyResult(lastJudgementResult.Value);
+
+ iteration++;
+ }
+
+ [Test]
+ public void TestAddJudgementsToCounters()
+ {
+ AddStep("create counter", () => Child = counterDisplay = new TestJudgementCounterDisplay());
+
+ AddRepeatStep("Add judgement", () => applyOneJudgement(HitResult.Great), 2);
+ AddRepeatStep("Add judgement", () => applyOneJudgement(HitResult.Miss), 2);
+ AddRepeatStep("Add judgement", () => applyOneJudgement(HitResult.Meh), 2);
+ }
+
+ [Test]
+ public void TestAddWhilstHidden()
+ {
+ AddStep("create counter", () => Child = counterDisplay = new TestJudgementCounterDisplay());
+
+ AddRepeatStep("Add judgement", () => applyOneJudgement(HitResult.LargeTickHit), 2);
+ AddAssert("Check value added whilst hidden", () => hiddenCount() == 2);
+ AddStep("Show all judgements", () => counterDisplay.Mode.Value = JudgementCounterDisplay.DisplayMode.All);
+ }
+
+ [Test]
+ public void TestChangeFlowDirection()
+ {
+ AddStep("create counter", () => Child = counterDisplay = new TestJudgementCounterDisplay());
+
+ AddStep("Set direction vertical", () => counterDisplay.FlowDirection.Value = Direction.Vertical);
+ AddStep("Set direction horizontal", () => counterDisplay.FlowDirection.Value = Direction.Horizontal);
+ }
+
+ [Test]
+ public void TestToggleJudgementNames()
+ {
+ AddStep("create counter", () => Child = counterDisplay = new TestJudgementCounterDisplay());
+
+ AddStep("Hide judgement names", () => counterDisplay.ShowJudgementNames.Value = false);
+ AddWaitStep("wait some", 2);
+ AddAssert("Assert hidden", () => counterDisplay.CounterFlow.Children.First().ResultName.Alpha == 0);
+ AddStep("Hide judgement names", () => counterDisplay.ShowJudgementNames.Value = true);
+ AddWaitStep("wait some", 2);
+ AddAssert("Assert shown", () => counterDisplay.CounterFlow.Children.First().ResultName.Alpha == 1);
+ }
+
+ [Test]
+ public void TestHideMaxValue()
+ {
+ AddStep("create counter", () => Child = counterDisplay = new TestJudgementCounterDisplay());
+
+ AddStep("Hide max judgement", () => counterDisplay.ShowMaxJudgement.Value = false);
+ AddWaitStep("wait some", 2);
+ AddAssert("Check max hidden", () => counterDisplay.CounterFlow.ChildrenOfType().First().Alpha == 0);
+ AddStep("Show max judgement", () => counterDisplay.ShowMaxJudgement.Value = true);
+ }
+
+ [Test]
+ public void TestMaxValueStartsHidden()
+ {
+ AddStep("create counter", () => Child = counterDisplay = new TestJudgementCounterDisplay
+ {
+ ShowMaxJudgement = { Value = false }
+ });
+ AddAssert("Check max hidden", () => counterDisplay.CounterFlow.ChildrenOfType().First().Alpha == 0);
+ }
+
+ [Test]
+ public void TestMaxValueHiddenOnModeChange()
+ {
+ AddStep("create counter", () => Child = counterDisplay = new TestJudgementCounterDisplay());
+
+ AddStep("Set max judgement to hide itself", () => counterDisplay.ShowMaxJudgement.Value = false);
+ AddStep("Show all judgements", () => counterDisplay.Mode.Value = JudgementCounterDisplay.DisplayMode.All);
+ AddWaitStep("wait some", 2);
+ AddAssert("Assert max judgement hidden", () => counterDisplay.CounterFlow.ChildrenOfType().First().Alpha == 0);
+ }
+
+ [Test]
+ public void TestCycleDisplayModes()
+ {
+ AddStep("create counter", () => Child = counterDisplay = new TestJudgementCounterDisplay());
+
+ AddStep("Show basic judgements", () => counterDisplay.Mode.Value = JudgementCounterDisplay.DisplayMode.Simple);
+ AddWaitStep("wait some", 2);
+ AddAssert("Check only basic", () => counterDisplay.CounterFlow.ChildrenOfType().Last().Alpha == 0);
+ AddStep("Show normal judgements", () => counterDisplay.Mode.Value = JudgementCounterDisplay.DisplayMode.Normal);
+ AddStep("Show all judgements", () => counterDisplay.Mode.Value = JudgementCounterDisplay.DisplayMode.All);
+ AddWaitStep("wait some", 2);
+ AddAssert("Check all visible", () => counterDisplay.CounterFlow.ChildrenOfType().Last().Alpha == 1);
+ }
+
+ private int hiddenCount()
+ {
+ var num = counterDisplay.CounterFlow.Children.First(child => child.Result.Type == HitResult.LargeTickHit);
+ return num.Result.ResultCount.Value;
+ }
+
+ private partial class TestJudgementCounterDisplay : JudgementCounterDisplay
+ {
+ public new FillFlowContainer CounterFlow => base.CounterFlow;
+
+ public TestJudgementCounterDisplay()
+ {
+ Margin = new MarginPadding { Top = 100 };
+ Anchor = Anchor.TopCentre;
+ Origin = Anchor.TopCentre;
+ }
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneLetterboxOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneLetterboxOverlay.cs
new file mode 100644
index 0000000000..ce93837925
--- /dev/null
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneLetterboxOverlay.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 osu.Framework.Graphics;
+using osu.Framework.Graphics.Shapes;
+using osu.Game.Screens.Play.Break;
+
+namespace osu.Game.Tests.Visual.Gameplay
+{
+ public partial class TestSceneLetterboxOverlay : OsuTestScene
+ {
+ public TestSceneLetterboxOverlay()
+ {
+ AddRange(new Drawable[]
+ {
+ new Box
+ {
+ RelativeSizeAxes = Axes.Both
+ },
+ new LetterboxOverlay()
+ });
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs
index 7880a849a2..b072ce191e 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs
@@ -5,9 +5,11 @@
using System.Linq;
using NUnit.Framework;
+using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
+using osu.Framework.Logging;
using osu.Framework.Screens;
using osu.Framework.Testing;
using osu.Game.Configuration;
@@ -34,6 +36,12 @@ namespace osu.Game.Tests.Visual.Gameplay
base.Content.Add(content = new GlobalCursorDisplay { RelativeSizeAxes = Axes.Both });
}
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ LocalConfig.SetValue(OsuSetting.UIHoldActivationDelay, 0.0);
+ }
+
[SetUpSteps]
public override void SetUpSteps()
{
@@ -43,6 +51,22 @@ namespace osu.Game.Tests.Visual.Gameplay
confirmClockRunning(true);
}
+ [Test]
+ public void TestTogglePauseViaBackAction()
+ {
+ pauseViaBackAction();
+ pauseViaBackAction();
+ confirmPausedWithNoOverlay();
+ }
+
+ [Test]
+ public void TestTogglePauseViaPauseGameplayAction()
+ {
+ pauseViaPauseGameplayAction();
+ pauseViaPauseGameplayAction();
+ confirmPausedWithNoOverlay();
+ }
+
[Test]
public void TestPauseWithLargeOffset()
{
@@ -66,7 +90,13 @@ namespace osu.Game.Tests.Visual.Gameplay
Player.OnUpdate += _ =>
{
double currentTime = Player.GameplayClockContainer.CurrentTime;
- alwaysGoingForward &= currentTime >= lastTime - 500;
+ bool goingForward = currentTime >= lastTime - 500;
+
+ alwaysGoingForward &= goingForward;
+
+ if (!goingForward)
+ Logger.Log($"Backwards time occurred ({currentTime:N1} -> {lastTime:N1})");
+
lastTime = currentTime;
};
});
@@ -144,7 +174,7 @@ namespace osu.Game.Tests.Visual.Gameplay
{
AddStep("disable pause support", () => Player.Configuration.AllowPause = false);
- pauseFromUserExitKey();
+ pauseViaBackAction();
confirmExited();
}
@@ -156,7 +186,7 @@ namespace osu.Game.Tests.Visual.Gameplay
pauseAndConfirm();
resume();
- pauseFromUserExitKey();
+ pauseViaBackAction();
confirmResumed();
confirmNotExited();
@@ -170,7 +200,7 @@ namespace osu.Game.Tests.Visual.Gameplay
pauseAndConfirm();
resume();
- AddStep("pause via exit key", () => Player.ExitViaQuickExit());
+ exitViaQuickExitAction();
confirmResumed();
AddAssert("exited", () => !Player.IsCurrentScreen());
@@ -214,7 +244,7 @@ namespace osu.Game.Tests.Visual.Gameplay
confirmClockRunning(false);
- AddStep("exit via user pause", () => Player.ExitViaPause());
+ pauseViaBackAction();
confirmExited();
}
@@ -224,11 +254,11 @@ namespace osu.Game.Tests.Visual.Gameplay
AddUntilStep("wait for fail", () => Player.GameplayState.HasFailed);
// will finish the fail animation and show the fail/pause screen.
- AddStep("attempt exit via pause key", () => Player.ExitViaPause());
+ pauseViaBackAction();
AddAssert("fail overlay shown", () => Player.FailOverlayVisible);
// will actually exit.
- AddStep("exit via pause key", () => Player.ExitViaPause());
+ pauseViaBackAction();
confirmExited();
}
@@ -245,7 +275,7 @@ namespace osu.Game.Tests.Visual.Gameplay
public void TestQuickExitFromFailedGameplay()
{
AddUntilStep("wait for fail", () => Player.GameplayState.HasFailed);
- AddStep("quick exit", () => Player.GameplayClockContainer.ChildrenOfType().First().Action?.Invoke());
+ exitViaQuickExitAction();
confirmExited();
}
@@ -261,7 +291,7 @@ namespace osu.Game.Tests.Visual.Gameplay
[Test]
public void TestQuickExitFromGameplay()
{
- AddStep("quick exit", () => Player.GameplayClockContainer.ChildrenOfType().First().Action?.Invoke());
+ exitViaQuickExitAction();
confirmExited();
}
@@ -327,7 +357,7 @@ namespace osu.Game.Tests.Visual.Gameplay
private void pauseAndConfirm()
{
- pauseFromUserExitKey();
+ pauseViaBackAction();
confirmPaused();
}
@@ -374,7 +404,17 @@ namespace osu.Game.Tests.Visual.Gameplay
}
private void restart() => AddStep("restart", () => Player.Restart());
- private void pauseFromUserExitKey() => AddStep("user pause", () => Player.ExitViaPause());
+ private void pauseViaBackAction() => AddStep("press escape", () => InputManager.Key(Key.Escape));
+ private void pauseViaPauseGameplayAction() => AddStep("press middle mouse", () => InputManager.Click(MouseButton.Middle));
+
+ private void exitViaQuickExitAction() => AddStep("press ctrl-tilde", () =>
+ {
+ InputManager.PressKey(Key.ControlLeft);
+ InputManager.PressKey(Key.Tilde);
+ InputManager.ReleaseKey(Key.Tilde);
+ InputManager.ReleaseKey(Key.ControlLeft);
+ });
+
private void resume() => AddStep("resume", () => Player.Resume());
private void confirmPauseOverlayShown(bool isShown) =>
@@ -405,10 +445,6 @@ namespace osu.Game.Tests.Visual.Gameplay
public bool PauseOverlayVisible => PauseOverlay.State.Value == Visibility.Visible;
- public void ExitViaPause() => PerformExit(true);
-
- public void ExitViaQuickExit() => PerformExit(false);
-
public override void OnEntering(ScreenTransitionEvent e)
{
base.OnEntering(e);
diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs
index 2fbdfbc198..1a7ea20cc0 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs
@@ -181,7 +181,8 @@ namespace osu.Game.Tests.Visual.Gameplay
AddUntilStep("wait for fail", () => Player.GameplayState.HasFailed);
AddStep("exit", () => Player.Exit());
- AddAssert("ensure failing submission", () => Player.SubmittedScore?.ScoreInfo.Passed == false);
+ AddUntilStep("wait for submission", () => Player.SubmittedScore != null);
+ AddAssert("ensure failing submission", () => Player.SubmittedScore.ScoreInfo.Passed == false);
}
[Test]
@@ -209,7 +210,9 @@ namespace osu.Game.Tests.Visual.Gameplay
addFakeHit();
AddStep("exit", () => Player.Exit());
- AddAssert("ensure failing submission", () => Player.SubmittedScore?.ScoreInfo.Passed == false);
+
+ AddUntilStep("wait for submission", () => Player.SubmittedScore != null);
+ AddAssert("ensure failing submission", () => Player.SubmittedScore.ScoreInfo.Passed == false);
}
[Test]
@@ -257,7 +260,7 @@ namespace osu.Game.Tests.Visual.Gameplay
{
prepareTestAPI(true);
- createPlayerTest(false, createRuleset: () => new OsuRuleset
+ createPlayerTest(createRuleset: () => new OsuRuleset
{
RulesetInfo =
{
diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePoolingRuleset.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePoolingRuleset.cs
index 55ee6c9fc9..0469df1de3 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestScenePoolingRuleset.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestScenePoolingRuleset.cs
@@ -19,7 +19,6 @@ using osu.Framework.Utils;
using osu.Game.Beatmaps;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Difficulty;
-using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables;
@@ -37,6 +36,8 @@ namespace osu.Game.Tests.Visual.Gameplay
private TestDrawablePoolingRuleset drawableRuleset;
+ private TestPlayfield playfield => (TestPlayfield)drawableRuleset.Playfield;
+
[Test]
public void TestReusedWithHitObjectsSpacedFarApart()
{
@@ -133,29 +134,49 @@ namespace osu.Game.Tests.Visual.Gameplay
AddUntilStep("no DHOs shown", () => !this.ChildrenOfType().Any());
}
+ [Test]
+ public void TestRevertResult()
+ {
+ ManualClock clock = null;
+ Beatmap beatmap;
+
+ createTest(beatmap = new Beatmap
+ {
+ HitObjects =
+ {
+ new TestHitObject { StartTime = 0 },
+ new TestHitObject { StartTime = 500 },
+ new TestHitObject { StartTime = 1000 },
+ }
+ }, 10, () => new FramedClock(clock = new ManualClock()));
+
+ AddStep("fast forward to end", () => clock.CurrentTime = beatmap.HitObjects[^1].GetEndTime() + 100);
+ AddUntilStep("all judged", () => playfield.JudgedObjects.Count, () => Is.EqualTo(3));
+
+ AddStep("rewind to middle", () => clock.CurrentTime = beatmap.HitObjects[1].StartTime - 100);
+ AddUntilStep("some results reverted", () => playfield.JudgedObjects.Count, () => Is.EqualTo(1));
+
+ AddStep("fast forward to end", () => clock.CurrentTime = beatmap.HitObjects[^1].GetEndTime() + 100);
+ AddUntilStep("all judged", () => playfield.JudgedObjects.Count, () => Is.EqualTo(3));
+
+ AddStep("disable frame stability", () => drawableRuleset.FrameStablePlayback = false);
+ AddStep("instant seek to start", () => clock.CurrentTime = beatmap.HitObjects[0].StartTime - 100);
+ AddAssert("all results reverted", () => playfield.JudgedObjects.Count, () => Is.EqualTo(0));
+ }
+
[Test]
public void TestApplyHitResultOnKilled()
{
ManualClock clock = null;
- bool anyJudged = false;
-
- void onNewResult(JudgementResult _) => anyJudged = true;
var beatmap = new Beatmap();
beatmap.HitObjects.Add(new TestKilledHitObject { Duration = 20 });
createTest(beatmap, 10, () => new FramedClock(clock = new ManualClock()));
- AddStep("subscribe to new result", () =>
- {
- anyJudged = false;
- drawableRuleset.NewResult += onNewResult;
- });
AddStep("skip past object", () => clock.CurrentTime = beatmap.HitObjects[0].GetEndTime() + 1000);
- AddAssert("object judged", () => anyJudged);
-
- AddStep("clean up", () => drawableRuleset.NewResult -= onNewResult);
+ AddAssert("object judged", () => playfield.JudgedObjects.Count == 1);
}
private void createTest(IBeatmap beatmap, int poolSize, Func createClock = null)
@@ -212,12 +233,24 @@ namespace osu.Game.Tests.Visual.Gameplay
private partial class TestPlayfield : Playfield
{
+ public readonly HashSet JudgedObjects = new HashSet();
+
private readonly int poolSize;
public TestPlayfield(int poolSize)
{
this.poolSize = poolSize;
AddInternal(HitObjectContainer);
+ NewResult += (_, r) =>
+ {
+ Assert.That(JudgedObjects, Has.No.Member(r.HitObject));
+ JudgedObjects.Add(r.HitObject);
+ };
+ RevertResult += r =>
+ {
+ Assert.That(JudgedObjects, Has.Member(r.HitObject));
+ JudgedObjects.Remove(r.HitObject);
+ };
}
[BackgroundDependencyLoader]
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs
index c473278fdc..6ccf73d8ff 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs
@@ -107,7 +107,7 @@ namespace osu.Game.Tests.Visual.Gameplay
AddUntilStep("wait for load", () => downloadButton.IsLoaded);
- AddAssert("state is available", () => downloadButton.State.Value == DownloadState.NotDownloaded);
+ checkState(DownloadState.NotDownloaded);
AddStep("click button", () =>
{
@@ -133,7 +133,7 @@ namespace osu.Game.Tests.Visual.Gameplay
AddUntilStep("wait for load", () => downloadButton.IsLoaded);
- AddAssert("state is not downloaded", () => downloadButton.State.Value == DownloadState.NotDownloaded);
+ checkState(DownloadState.NotDownloaded);
AddAssert("button is not enabled", () => !downloadButton.ChildrenOfType().First().Enabled.Value);
}
@@ -155,7 +155,7 @@ namespace osu.Game.Tests.Visual.Gameplay
AddUntilStep("wait for load", () => downloadButton.IsLoaded);
- AddUntilStep("state is not downloaded", () => downloadButton.State.Value == DownloadState.NotDownloaded);
+ checkState(DownloadState.NotDownloaded);
AddAssert("button is not enabled", () => !downloadButton.ChildrenOfType().First().Enabled.Value);
}
@@ -174,17 +174,16 @@ namespace osu.Game.Tests.Visual.Gameplay
});
AddUntilStep("wait for load", () => downloadButton.IsLoaded);
-
- AddUntilStep("state is not downloaded", () => downloadButton.State.Value == DownloadState.NotDownloaded);
+ checkState(DownloadState.NotDownloaded);
AddStep("import score", () => imported = scoreManager.Import(getScoreInfo(true)));
- AddUntilStep("state is available", () => downloadButton.State.Value == DownloadState.LocallyAvailable);
+ checkState(DownloadState.LocallyAvailable);
AddAssert("button is enabled", () => downloadButton.ChildrenOfType().First().Enabled.Value);
AddStep("delete score", () => scoreManager.Delete(imported.Value));
- AddUntilStep("state is not downloaded", () => downloadButton.State.Value == DownloadState.NotDownloaded);
+ checkState(DownloadState.NotDownloaded);
AddAssert("button is not enabled", () => !downloadButton.ChildrenOfType().First().Enabled.Value);
}
@@ -202,10 +201,13 @@ namespace osu.Game.Tests.Visual.Gameplay
AddUntilStep("wait for load", () => downloadButton.IsLoaded);
- AddAssert("state is unknown", () => downloadButton.State.Value == DownloadState.Unknown);
+ checkState(DownloadState.Unknown);
AddAssert("button is not enabled", () => !downloadButton.ChildrenOfType().First().Enabled.Value);
}
+ private void checkState(DownloadState expectedState) =>
+ AddUntilStep($"state is {expectedState}", () => downloadButton.State.Value, () => Is.EqualTo(expectedState));
+
private ScoreInfo getScoreInfo(bool replayAvailable, bool hasOnlineId = true) => new ScoreInfo
{
OnlineID = hasOnlineId ? online_score_id : 0,
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayPlayer.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayPlayer.cs
index e0a6a60ff2..3e415af86e 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayPlayer.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayPlayer.cs
@@ -1,36 +1,98 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
+using System.Linq;
using NUnit.Framework;
+using osu.Framework.Screens;
+using osu.Framework.Testing;
+using osu.Game.Beatmaps;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Osu;
+using osu.Game.Screens.Play;
+using osu.Game.Tests.Beatmaps;
using osuTK.Input;
namespace osu.Game.Tests.Visual.Gameplay
{
public partial class TestSceneReplayPlayer : RateAdjustedBeatmapTestScene
{
- protected TestReplayPlayer Player;
-
- public override void SetUpSteps()
- {
- base.SetUpSteps();
-
- AddStep("Initialise player", () => Player = CreatePlayer(new OsuRuleset()));
- AddStep("Load player", () => LoadScreen(Player));
- AddUntilStep("player loaded", () => Player.IsLoaded);
- }
+ protected TestReplayPlayer Player = null!;
[Test]
- public void TestPause()
+ public void TestPauseViaSpace()
{
+ loadPlayerWithBeatmap();
+
double? lastTime = null;
AddUntilStep("wait for first hit", () => Player.ScoreProcessor.TotalScore.Value > 0);
- AddStep("Pause playback", () => InputManager.Key(Key.Space));
+ AddStep("Pause playback with space", () => InputManager.Key(Key.Space));
+
+ AddAssert("player not exited", () => Player.IsCurrentScreen());
+
+ AddUntilStep("Time stopped progressing", () =>
+ {
+ double current = Player.GameplayClockContainer.CurrentTime;
+ bool changed = lastTime != current;
+ lastTime = current;
+
+ return !changed;
+ });
+
+ AddWaitStep("wait some", 10);
+
+ AddAssert("Time still stopped", () => lastTime == Player.GameplayClockContainer.CurrentTime);
+ }
+
+ [Test]
+ public void TestPauseViaSpaceWithSkip()
+ {
+ loadPlayerWithBeatmap(new TestBeatmap(new OsuRuleset().RulesetInfo)
+ {
+ BeatmapInfo = { AudioLeadIn = 60000 }
+ });
+
+ AddUntilStep("wait for skip overlay", () => Player.ChildrenOfType().First().IsButtonVisible);
+
+ AddStep("Skip with space", () => InputManager.Key(Key.Space));
+
+ AddAssert("Player not paused", () => !Player.DrawableRuleset.IsPaused.Value);
+
+ double? lastTime = null;
+
+ AddUntilStep("wait for first hit", () => Player.ScoreProcessor.TotalScore.Value > 0);
+
+ AddStep("Pause playback with space", () => InputManager.Key(Key.Space));
+
+ AddAssert("player not exited", () => Player.IsCurrentScreen());
+
+ AddUntilStep("Time stopped progressing", () =>
+ {
+ double current = Player.GameplayClockContainer.CurrentTime;
+ bool changed = lastTime != current;
+ lastTime = current;
+
+ return !changed;
+ });
+
+ AddWaitStep("wait some", 10);
+
+ AddAssert("Time still stopped", () => lastTime == Player.GameplayClockContainer.CurrentTime);
+ }
+
+ [Test]
+ public void TestPauseViaMiddleMouse()
+ {
+ loadPlayerWithBeatmap();
+
+ double? lastTime = null;
+
+ AddUntilStep("wait for first hit", () => Player.ScoreProcessor.TotalScore.Value > 0);
+
+ AddStep("Pause playback with middle mouse", () => InputManager.Click(MouseButton.Middle));
+
+ AddAssert("player not exited", () => Player.IsCurrentScreen());
AddUntilStep("Time stopped progressing", () =>
{
@@ -49,6 +111,8 @@ namespace osu.Game.Tests.Visual.Gameplay
[Test]
public void TestSeekBackwards()
{
+ loadPlayerWithBeatmap();
+
double? lastTime = null;
AddUntilStep("wait for first hit", () => Player.ScoreProcessor.TotalScore.Value > 0);
@@ -65,6 +129,8 @@ namespace osu.Game.Tests.Visual.Gameplay
[Test]
public void TestSeekForwards()
{
+ loadPlayerWithBeatmap();
+
double? lastTime = null;
AddUntilStep("wait for first hit", () => Player.ScoreProcessor.TotalScore.Value > 0);
@@ -78,12 +144,26 @@ namespace osu.Game.Tests.Visual.Gameplay
AddAssert("Jumped forwards", () => Player.GameplayClockContainer.CurrentTime - lastTime > 500);
}
- protected TestReplayPlayer CreatePlayer(Ruleset ruleset)
+ private void loadPlayerWithBeatmap(IBeatmap? beatmap = null)
{
- Beatmap.Value = CreateWorkingBeatmap(ruleset.RulesetInfo);
+ AddStep("create player", () =>
+ {
+ CreatePlayer(new OsuRuleset(), beatmap);
+ });
+
+ AddStep("Load player", () => LoadScreen(Player));
+ AddUntilStep("player loaded", () => Player.IsLoaded);
+ }
+
+ protected void CreatePlayer(Ruleset ruleset, IBeatmap? beatmap = null)
+ {
+ Beatmap.Value = beatmap != null
+ ? CreateWorkingBeatmap(beatmap)
+ : CreateWorkingBeatmap(ruleset.RulesetInfo);
+
SelectedMods.Value = new[] { ruleset.GetAutoplayMod() };
- return new TestReplayPlayer(false);
+ Player = new TestReplayPlayer(false);
}
}
}
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs
index 48dbda9da6..2f20d75813 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs
@@ -9,11 +9,12 @@ using osu.Framework.Graphics.UserInterface;
using osu.Framework.Testing;
using osu.Game.Overlays;
using osu.Game.Overlays.Settings;
+using osu.Game.Overlays.SkinEditor;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Osu;
+using osu.Game.Screens.Edit;
using osu.Game.Screens.Play.HUD.HitErrorMeters;
using osu.Game.Skinning;
-using osu.Game.Skinning.Editor;
using osuTK.Input;
namespace osu.Game.Tests.Visual.Gameplay
@@ -27,12 +28,15 @@ namespace osu.Game.Tests.Visual.Gameplay
[Cached]
private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Blue);
+ [Cached]
+ public readonly EditorClipboard Clipboard = new EditorClipboard();
+
[SetUpSteps]
public override void SetUpSteps()
{
base.SetUpSteps();
- AddUntilStep("wait for hud load", () => Player.ChildrenOfType().All(c => c.ComponentsLoaded));
+ AddUntilStep("wait for hud load", () => Player.ChildrenOfType().All(c => c.ComponentsLoaded));
AddStep("reload skin editor", () =>
{
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditorComponentsList.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditorComponentsList.cs
index 05a550a24d..2ae5e6f998 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditorComponentsList.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditorComponentsList.cs
@@ -7,9 +7,9 @@ using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Game.Overlays;
+using osu.Game.Overlays.SkinEditor;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Osu;
-using osu.Game.Skinning.Editor;
namespace osu.Game.Tests.Visual.Gameplay
{
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditorMultipleSkins.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditorMultipleSkins.cs
index ad3911f50b..a7da8f9832 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditorMultipleSkins.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditorMultipleSkins.cs
@@ -8,11 +8,12 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Testing;
using osu.Framework.Timing;
+using osu.Game.Overlays.SkinEditor;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Scoring;
+using osu.Game.Screens.Edit;
using osu.Game.Screens.Play;
-using osu.Game.Skinning.Editor;
using osu.Game.Tests.Gameplay;
using osuTK.Input;
@@ -32,6 +33,9 @@ namespace osu.Game.Tests.Visual.Gameplay
[Cached(typeof(IGameplayClock))]
private readonly IGameplayClock gameplayClock = new GameplayClockContainer(new FramedClock());
+ [Cached]
+ public readonly EditorClipboard Clipboard = new EditorClipboard();
+
[SetUpSteps]
public void SetUpSteps()
{
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSongProgress.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSongProgress.cs
index 2e579cc522..5855838d3c 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneSongProgress.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSongProgress.cs
@@ -2,14 +2,14 @@
// 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.Allocation;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics;
using osu.Framework.Testing;
-using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Osu;
+using osu.Game.Rulesets.UI;
using osu.Game.Screens.Play;
using osu.Game.Screens.Play.HUD;
using osu.Game.Skinning;
@@ -28,50 +28,62 @@ namespace osu.Game.Tests.Visual.Gameplay
{
Beatmap.Value = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo);
- Add(gameplayClockContainer = new MasterGameplayClockContainer(Beatmap.Value, skip_target_time));
+ FrameStabilityContainer frameStabilityContainer;
+
+ Add(gameplayClockContainer = new MasterGameplayClockContainer(Beatmap.Value, skip_target_time)
+ {
+ Child = frameStabilityContainer = new FrameStabilityContainer
+ {
+ MaxCatchUpFrames = 1
+ }
+ });
Dependencies.CacheAs(gameplayClockContainer);
+ Dependencies.CacheAs(frameStabilityContainer);
}
[SetUpSteps]
public void SetupSteps()
{
AddStep("reset clock", () => gameplayClockContainer.Reset());
- AddStep("set hit objects", setHitObjects);
+ AddStep("set hit objects", () => this.ChildrenOfType().ForEach(progress => progress.Objects = Beatmap.Value.Beatmap.HitObjects));
+ AddStep("hook seeking", () =>
+ {
+ applyToDefaultProgress(d => d.ChildrenOfType().Single().OnSeek += t => gameplayClockContainer.Seek(t));
+ applyToArgonProgress(d => d.ChildrenOfType().Single().OnSeek += t => gameplayClockContainer.Seek(t));
+ });
+ AddStep("seek to intro", () => gameplayClockContainer.Seek(skip_target_time));
+ AddStep("start", () => gameplayClockContainer.Start());
}
[Test]
- public void TestDisplay()
+ public void TestBasic()
{
- AddStep("seek to intro", () => gameplayClockContainer.Seek(skip_target_time));
- AddStep("start", gameplayClockContainer.Start);
+ AddToggleStep("toggle seeking", b =>
+ {
+ applyToDefaultProgress(s => s.Interactive.Value = b);
+ applyToArgonProgress(s => s.Interactive.Value = b);
+ });
+
+ AddToggleStep("toggle graph", b =>
+ {
+ applyToDefaultProgress(s => s.ShowGraph.Value = b);
+ applyToArgonProgress(s => s.ShowGraph.Value = b);
+ });
+
AddStep("stop", gameplayClockContainer.Stop);
}
- [Test]
- public void TestToggleSeeking()
- {
- void applyToDefaultProgress(Action action) =>
- this.ChildrenOfType().ForEach(action);
+ private void applyToArgonProgress(Action action) =>
+ this.ChildrenOfType().ForEach(action);
- AddStep("allow seeking", () => applyToDefaultProgress(s => s.AllowSeeking.Value = true));
- AddStep("hide graph", () => applyToDefaultProgress(s => s.ShowGraph.Value = false));
- AddStep("disallow seeking", () => applyToDefaultProgress(s => s.AllowSeeking.Value = false));
- AddStep("allow seeking", () => applyToDefaultProgress(s => s.AllowSeeking.Value = true));
- AddStep("show graph", () => applyToDefaultProgress(s => s.ShowGraph.Value = true));
- }
-
- private void setHitObjects()
- {
- var objects = new List();
- for (double i = 0; i < 5000; i++)
- objects.Add(new HitObject { StartTime = i });
-
- this.ChildrenOfType().ForEach(progress => progress.Objects = objects);
- }
+ private void applyToDefaultProgress(Action action) =>
+ this.ChildrenOfType().ForEach(action);
protected override Drawable CreateDefaultImplementation() => new DefaultSongProgress();
+ protected override Drawable CreateArgonImplementation() => new ArgonSongProgress();
+
protected override Drawable CreateLegacyImplementation() => new LegacySongProgress();
}
}
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs
index 8f1eb98c79..ffd034e4d2 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs
@@ -261,7 +261,7 @@ namespace osu.Game.Tests.Visual.Gameplay
[Test]
public void TestFinalFramesPurgedBeforeEndingPlay()
{
- AddStep("begin playing", () => spectatorClient.BeginPlaying(TestGameplayState.Create(new OsuRuleset()), new Score()));
+ AddStep("begin playing", () => spectatorClient.BeginPlaying(0, TestGameplayState.Create(new OsuRuleset()), new Score()));
AddStep("send frames and finish play", () =>
{
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs
index 1ad1da0994..794860b9ec 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs
@@ -147,7 +147,7 @@ namespace osu.Game.Tests.Visual.Gameplay
}
};
- spectatorClient.BeginPlaying(TestGameplayState.Create(new OsuRuleset()), recordingScore);
+ spectatorClient.BeginPlaying(0, TestGameplayState.Create(new OsuRuleset()), recordingScore);
spectatorClient.OnNewFrames += onNewFrames;
});
}
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneUnstableRateCounter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneUnstableRateCounter.cs
index 2f572b46c9..d0e516ed39 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneUnstableRateCounter.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneUnstableRateCounter.cs
@@ -22,12 +22,18 @@ namespace osu.Game.Tests.Visual.Gameplay
[Cached(typeof(ScoreProcessor))]
private TestScoreProcessor scoreProcessor = new TestScoreProcessor();
- private readonly OsuHitWindows hitWindows = new OsuHitWindows();
+ private readonly OsuHitWindows hitWindows;
private UnstableRateCounter counter;
private double prev;
+ public TestSceneUnstableRateCounter()
+ {
+ hitWindows = new OsuHitWindows();
+ hitWindows.SetDifficulty(5);
+ }
+
[SetUpSteps]
public void SetUp()
{
diff --git a/osu.Game.Tests/Visual/Menus/TestSceneToolbar.cs b/osu.Game.Tests/Visual/Menus/TestSceneToolbar.cs
index 0bc42b06dd..aef6f9ade0 100644
--- a/osu.Game.Tests/Visual/Menus/TestSceneToolbar.cs
+++ b/osu.Game.Tests/Visual/Menus/TestSceneToolbar.cs
@@ -201,6 +201,23 @@ namespace osu.Game.Tests.Visual.Menus
AddAssert("volume not changed", () => Audio.Volume.Value == 0.5);
}
+ [Test]
+ public void TestRulesetSelectorOverflow()
+ {
+ AddStep("set toolbar width", () =>
+ {
+ toolbar.RelativeSizeAxes = Axes.None;
+ toolbar.Width = 400;
+ });
+ AddStep("move mouse over news toggle button", () =>
+ {
+ var button = toolbar.ChildrenOfType().Single();
+ InputManager.MoveMouseTo(button);
+ });
+ AddAssert("no ruleset toggle buttons hovered", () => !toolbar.ChildrenOfType().Any(button => button.IsHovered));
+ AddUntilStep("toolbar gradient visible", () => toolbar.ChildrenOfType().Single().Children.All(d => d.Alpha > 0));
+ }
+
public partial class TestToolbar : Toolbar
{
public new Bindable OverlayActivationMode => base.OverlayActivationMode as Bindable;
diff --git a/osu.Game.Tests/Visual/Multiplayer/MultiplayerGameplayLeaderboardTestScene.cs b/osu.Game.Tests/Visual/Multiplayer/MultiplayerGameplayLeaderboardTestScene.cs
index fa7d2c04f4..649c662e41 100644
--- a/osu.Game.Tests/Visual/Multiplayer/MultiplayerGameplayLeaderboardTestScene.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/MultiplayerGameplayLeaderboardTestScene.cs
@@ -117,11 +117,9 @@ namespace osu.Game.Tests.Visual.Multiplayer
BeatmapID = 0,
RulesetID = 0,
Mods = user.Mods,
- MaximumScoringValues = new ScoringValues
+ MaximumStatistics = new Dictionary
{
- BaseScore = 10000,
- MaxCombo = 1000,
- CountBasicHitObjects = 1000
+ { HitResult.Perfect, 100 }
}
};
}
diff --git a/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs b/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs
index 0e9863a9f5..0f1ba9ba75 100644
--- a/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs
@@ -16,6 +16,7 @@ using osu.Game.Online.Multiplayer;
using osu.Game.Online.Rooms;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Osu;
+using osu.Game.Screens.OnlinePlay;
using osu.Game.Screens.OnlinePlay.Lounge;
using osu.Game.Screens.OnlinePlay.Multiplayer;
using osu.Game.Screens.OnlinePlay.Multiplayer.Match;
@@ -85,6 +86,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
ClickButtonWhenEnabled();
AddUntilStep("wait for join", () => MultiplayerClient.RoomJoined);
+ AddUntilStep("wait for ongoing operation to complete", () => !(CurrentScreen as OnlinePlayScreen).ChildrenOfType().Single().InProgress.Value);
}
[Test]
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneAllPlayersQueueMode.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneAllPlayersQueueMode.cs
index 7b1abd104f..869b8bb328 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneAllPlayersQueueMode.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneAllPlayersQueueMode.cs
@@ -15,7 +15,6 @@ using osu.Game.Rulesets.Catch;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Mods;
-using osu.Game.Screens.OnlinePlay;
using osu.Game.Screens.OnlinePlay.Multiplayer;
using osu.Game.Screens.OnlinePlay.Multiplayer.Match;
using osu.Game.Screens.Play;
@@ -33,6 +32,16 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddUntilStep("first item selected", () => MultiplayerClient.ClientRoom?.Settings.PlaylistItemId == MultiplayerClient.ClientAPIRoom?.Playlist[0].ID);
}
+ [Test]
+ public void TestSingleItemExpiredAfterGameplay()
+ {
+ RunGameplay();
+
+ AddUntilStep("playlist has only one item", () => MultiplayerClient.ClientAPIRoom?.Playlist.Count == 1);
+ AddUntilStep("playlist item is expired", () => MultiplayerClient.ClientAPIRoom?.Playlist[0].Expired == true);
+ AddUntilStep("last item selected", () => MultiplayerClient.ClientRoom?.Settings.PlaylistItemId == MultiplayerClient.ClientAPIRoom?.Playlist[0].ID);
+ }
+
[Test]
public void TestItemAddedToTheEndOfQueue()
{
@@ -45,16 +54,6 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddUntilStep("first item still selected", () => MultiplayerClient.ClientRoom?.Settings.PlaylistItemId == MultiplayerClient.ClientAPIRoom?.Playlist[0].ID);
}
- [Test]
- public void TestSingleItemExpiredAfterGameplay()
- {
- RunGameplay();
-
- AddUntilStep("playlist has only one item", () => MultiplayerClient.ClientAPIRoom?.Playlist.Count == 1);
- AddUntilStep("playlist item is expired", () => MultiplayerClient.ClientAPIRoom?.Playlist[0].Expired == true);
- AddUntilStep("last item selected", () => MultiplayerClient.ClientRoom?.Settings.PlaylistItemId == MultiplayerClient.ClientAPIRoom?.Playlist[0].ID);
- }
-
[Test]
public void TestNextItemSelectedAfterGameplayFinish()
{
@@ -140,7 +139,6 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddUntilStep("wait for song select", () => (songSelect = CurrentSubScreen as Screens.Select.SongSelect) != null);
AddUntilStep("wait for loaded", () => songSelect.AsNonNull().BeatmapSetsLoaded);
- AddUntilStep("wait for ongoing operation to complete", () => !(CurrentScreen as OnlinePlayScreen).ChildrenOfType().Single().InProgress.Value);
if (ruleset != null)
AddStep($"set {ruleset.Name} ruleset", () => songSelect.AsNonNull().Ruleset.Value = ruleset);
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneHostOnlyQueueMode.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneHostOnlyQueueMode.cs
index 145c655c0a..78baa4a39b 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneHostOnlyQueueMode.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneHostOnlyQueueMode.cs
@@ -27,6 +27,28 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddUntilStep("first item selected", () => MultiplayerClient.ClientRoom?.Settings.PlaylistItemId == MultiplayerClient.ClientAPIRoom?.Playlist[0].ID);
}
+ [Test]
+ public void TestNewItemCreatedAfterGameplayFinished()
+ {
+ RunGameplay();
+
+ AddUntilStep("playlist contains two items", () => MultiplayerClient.ClientAPIRoom?.Playlist.Count == 2);
+ AddUntilStep("first playlist item expired", () => MultiplayerClient.ClientAPIRoom?.Playlist[0].Expired == true);
+ AddUntilStep("second playlist item not expired", () => MultiplayerClient.ClientAPIRoom?.Playlist[1].Expired == false);
+ AddUntilStep("second playlist item selected", () => MultiplayerClient.ClientRoom?.Settings.PlaylistItemId == MultiplayerClient.ClientAPIRoom?.Playlist[1].ID);
+ }
+
+ [Test]
+ public void TestSettingsUpdatedWhenChangingQueueMode()
+ {
+ AddStep("change queue mode", () => MultiplayerClient.ChangeSettings(new MultiplayerRoomSettings
+ {
+ QueueMode = QueueMode.AllPlayers
+ }).WaitSafely());
+
+ AddUntilStep("api room updated", () => MultiplayerClient.ClientAPIRoom?.QueueMode.Value == QueueMode.AllPlayers);
+ }
+
[Test]
public void TestItemStillSelectedAfterChangeToSameBeatmap()
{
@@ -43,17 +65,6 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddUntilStep("playlist item still selected", () => MultiplayerClient.ClientRoom?.Settings.PlaylistItemId == MultiplayerClient.ClientAPIRoom?.Playlist[0].ID);
}
- [Test]
- public void TestNewItemCreatedAfterGameplayFinished()
- {
- RunGameplay();
-
- AddUntilStep("playlist contains two items", () => MultiplayerClient.ClientAPIRoom?.Playlist.Count == 2);
- AddUntilStep("first playlist item expired", () => MultiplayerClient.ClientAPIRoom?.Playlist[0].Expired == true);
- AddUntilStep("second playlist item not expired", () => MultiplayerClient.ClientAPIRoom?.Playlist[1].Expired == false);
- AddUntilStep("second playlist item selected", () => MultiplayerClient.ClientRoom?.Settings.PlaylistItemId == MultiplayerClient.ClientAPIRoom?.Playlist[1].ID);
- }
-
[Test]
public void TestOnlyLastItemChangedAfterGameplayFinished()
{
@@ -68,17 +79,6 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddUntilStep("second playlist item changed", () => MultiplayerClient.ClientAPIRoom?.Playlist[1].Beatmap != firstBeatmap);
}
- [Test]
- public void TestSettingsUpdatedWhenChangingQueueMode()
- {
- AddStep("change queue mode", () => MultiplayerClient.ChangeSettings(new MultiplayerRoomSettings
- {
- QueueMode = QueueMode.AllPlayers
- }).WaitSafely());
-
- AddUntilStep("api room updated", () => MultiplayerClient.ClientAPIRoom?.QueueMode.Value == QueueMode.AllPlayers);
- }
-
[Test]
public void TestAddItemsAsHost()
{
@@ -104,7 +104,6 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddUntilStep("wait for song select", () => CurrentSubScreen is Screens.Select.SongSelect select && select.BeatmapSetsLoaded);
BeatmapInfo otherBeatmap = null;
- AddUntilStep("wait for ongoing operation to complete", () => !(CurrentScreen as OnlinePlayScreen).ChildrenOfType().Single().InProgress.Value);
AddStep("select other beatmap", () => ((Screens.Select.SongSelect)CurrentSubScreen).FinaliseSelection(otherBeatmap = beatmap()));
AddUntilStep("wait for return to match", () => CurrentSubScreen is MultiplayerMatchSubScreen);
@@ -120,7 +119,6 @@ namespace osu.Game.Tests.Visual.Multiplayer
});
AddUntilStep("wait for song select", () => CurrentSubScreen is Screens.Select.SongSelect select && select.BeatmapSetsLoaded);
- AddUntilStep("wait for ongoing operation to complete", () => !(CurrentScreen as OnlinePlayScreen).ChildrenOfType().Single().InProgress.Value);
AddStep("select other beatmap", () => ((Screens.Select.SongSelect)CurrentSubScreen).FinaliseSelection(beatmap()));
AddUntilStep("wait for return to match", () => CurrentSubScreen is MultiplayerMatchSubScreen);
}
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs
index c2036984c1..e09496b6e9 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs
@@ -121,7 +121,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddUntilStep("all interactive elements removed", () => this.ChildrenOfType().All(p =>
!p.ChildrenOfType().Any() &&
!p.ChildrenOfType().Any() &&
- p.ChildrenOfType().SingleOrDefault()?.ShowHandle == false));
+ p.ChildrenOfType().SingleOrDefault()?.Interactive == false));
AddStep("restore config hud visibility", () => config.SetValue(OsuSetting.HUDVisibilityMode, originalConfigValue));
}
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs
index 5033347b15..d747d23229 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs
@@ -377,6 +377,17 @@ namespace osu.Game.Tests.Visual.Multiplayer
}
[Test]
+ [FlakyTest]
+ /*
+ * On a slight investigation, this is occurring due to the ready button
+ * not receiving the click input generated by the manual input manager.
+ *
+ * TearDown : System.TimeoutException : "wait for ready button to be enabled" timed out
+ * --TearDown
+ * at osu.Framework.Testing.Drawables.Steps.UntilStepButton.<>c__DisplayClass11_0.<.ctor>b__0()
+ * at osu.Framework.Testing.Drawables.Steps.StepButton.PerformStep(Boolean userTriggered)
+ * at osu.Framework.Testing.TestScene.runNextStep(Action onCompletion, Action`1 onError, Func`2 stopCondition)
+ */
public void TestUserSetToIdleWhenBeatmapDeleted()
{
createRoom(() => new Room
@@ -398,6 +409,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
}
[Test]
+ [FlakyTest] // See above
public void TestPlayStartsWithCorrectBeatmapWhileAtSongSelect()
{
PlaylistItem? item = null;
@@ -438,6 +450,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
}
[Test]
+ [FlakyTest] // See above
public void TestPlayStartsWithCorrectRulesetWhileAtSongSelect()
{
PlaylistItem? item = null;
@@ -478,6 +491,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
}
[Test]
+ [FlakyTest] // See above
public void TestPlayStartsWithCorrectModsWhileAtSongSelect()
{
PlaylistItem? item = null;
@@ -651,6 +665,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
}
[Test]
+ [FlakyTest] // See above
public void TestGameplayFlow()
{
createRoom(() => new Room
@@ -678,6 +693,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
}
[Test]
+ [FlakyTest] // See above
public void TestGameplayExitFlow()
{
Bindable? holdDelay = null;
@@ -715,6 +731,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
}
[Test]
+ [FlakyTest] // See above
public void TestGameplayDoesntStartWithNonLoadedUser()
{
createRoom(() => new Room
@@ -796,6 +813,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
}
[Test]
+ [FlakyTest] // See above
public void TestSpectatingStateResetOnBackButtonDuringGameplay()
{
createRoom(() => new Room
@@ -831,6 +849,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
}
[Test]
+ [FlakyTest] // See above
public void TestSpectatingStateNotResetOnBackButtonOutsideOfGameplay()
{
createRoom(() => new Room
@@ -869,6 +888,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
}
[Test]
+ [FlakyTest] // See above
public void TestItemAddedByOtherUserDuringGameplay()
{
createRoom(() => new Room
@@ -899,6 +919,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
}
[Test]
+ [FlakyTest] // See above
public void TestItemAddedAndDeletedByOtherUserDuringGameplay()
{
createRoom(() => new Room
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsRoomSettingsPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsRoomSettingsPlaylist.cs
index 91e9ce5ea2..ae27db0dd1 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsRoomSettingsPlaylist.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsRoomSettingsPlaylist.cs
@@ -3,6 +3,7 @@
#nullable disable
+using System;
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
@@ -46,10 +47,14 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddAssert("item removed", () => !playlist.Items.Contains(selectedItem));
}
- [Test]
- public void TestNextItemSelectedAfterDeletion()
+ [TestCase(true)]
+ [TestCase(false)]
+ public void TestNextItemSelectedAfterDeletion(bool allowSelection)
{
- createPlaylist();
+ createPlaylist(p =>
+ {
+ p.AllowSelection = allowSelection;
+ });
moveToItem(0);
AddStep("click", () => InputManager.Click(MouseButton.Left));
@@ -57,7 +62,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
moveToDeleteButton(0);
AddStep("click delete button", () => InputManager.Click(MouseButton.Left));
- AddAssert("item 0 is selected", () => playlist.SelectedItem.Value == playlist.Items[0]);
+ AddAssert("item 0 is " + (allowSelection ? "selected" : "not selected"), () => playlist.SelectedItem.Value == (allowSelection ? playlist.Items[0] : null));
}
[Test]
@@ -117,7 +122,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
InputManager.MoveMouseTo(item.ChildrenOfType().ElementAt(0), offset);
});
- private void createPlaylist()
+ private void createPlaylist(Action setupPlaylist = null)
{
AddStep("create playlist", () =>
{
@@ -154,6 +159,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
}
});
}
+
+ setupPlaylist?.Invoke(playlist);
});
AddUntilStep("wait for items to load", () => playlist.ItemMap.Values.All(i => i.IsLoaded));
diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneEditDefaultSkin.cs b/osu.Game.Tests/Visual/Navigation/TestSceneEditDefaultSkin.cs
index 7d39d48378..bd75825da2 100644
--- a/osu.Game.Tests/Visual/Navigation/TestSceneEditDefaultSkin.cs
+++ b/osu.Game.Tests/Visual/Navigation/TestSceneEditDefaultSkin.cs
@@ -8,8 +8,8 @@ using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Testing;
using osu.Game.Overlays.Settings.Sections;
+using osu.Game.Overlays.SkinEditor;
using osu.Game.Skinning;
-using osu.Game.Skinning.Editor;
namespace osu.Game.Tests.Visual.Navigation
{
diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneInterProcessCommunication.cs b/osu.Game.Tests/Visual/Navigation/TestSceneInterProcessCommunication.cs
index 346f1d9f90..1ecd38e1d3 100644
--- a/osu.Game.Tests/Visual/Navigation/TestSceneInterProcessCommunication.cs
+++ b/osu.Game.Tests/Visual/Navigation/TestSceneInterProcessCommunication.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.IO;
using System.Linq;
using NUnit.Framework;
using osu.Framework;
@@ -14,6 +15,8 @@ using osu.Game.Online.API;
using osu.Game.Online.API.Requests;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Overlays;
+using osu.Game.Overlays.Notifications;
+using osu.Game.Tests.Resources;
namespace osu.Game.Tests.Visual.Navigation
{
@@ -23,11 +26,13 @@ namespace osu.Game.Tests.Visual.Navigation
{
private HeadlessGameHost ipcSenderHost = null!;
- private OsuSchemeLinkIPCChannel osuSchemeLinkIPCReceiver = null!;
private OsuSchemeLinkIPCChannel osuSchemeLinkIPCSender = null!;
+ private ArchiveImportIPCChannel archiveImportIPCSender = null!;
private const int requested_beatmap_set_id = 1;
+ protected override TestOsuGame CreateTestGame() => new IpcGame(LocalStorage, API);
+
[Resolved]
private GameHost gameHost { get; set; } = null!;
@@ -56,11 +61,11 @@ namespace osu.Game.Tests.Visual.Navigation
return false;
};
});
- AddStep("create IPC receiver channel", () => osuSchemeLinkIPCReceiver = new OsuSchemeLinkIPCChannel(gameHost, Game));
- AddStep("create IPC sender channel", () =>
+ AddStep("create IPC sender channels", () =>
{
ipcSenderHost = new HeadlessGameHost(gameHost.Name, new HostOptions { BindIPC = true });
osuSchemeLinkIPCSender = new OsuSchemeLinkIPCChannel(ipcSenderHost);
+ archiveImportIPCSender = new ArchiveImportIPCChannel(ipcSenderHost);
});
}
@@ -72,15 +77,50 @@ namespace osu.Game.Tests.Visual.Navigation
AddUntilStep("beatmap overlay showing content", () => Game.ChildrenOfType().FirstOrDefault()?.Header.BeatmapSet.Value.OnlineID == requested_beatmap_set_id);
}
+ [Test]
+ public void TestArchiveImportLinkIPCChannel()
+ {
+ string? beatmapFilepath = null;
+
+ AddStep("import beatmap via IPC", () => archiveImportIPCSender.ImportAsync(beatmapFilepath = TestResources.GetQuickTestBeatmapForImport()).WaitSafely());
+ AddUntilStep("import complete notification was presented", () => Game.Notifications.ChildrenOfType