mirror of
https://github.com/ppy/osu.git
synced 2025-01-12 17:23:22 +08:00
Merge branch 'master' into custom-ipc-location
This commit is contained in:
commit
5f79feaa8b
137
CONTRIBUTING.md
Normal file
137
CONTRIBUTING.md
Normal file
@ -0,0 +1,137 @@
|
|||||||
|
# Contributing Guidelines
|
||||||
|
|
||||||
|
Thank you for showing interest in the development of osu!lazer! We aim to provide a good collaborating environment for everyone involved, and as such have decided to list some of the most important things to keep in mind in the process. The guidelines below have been chosen based on past experience.
|
||||||
|
|
||||||
|
These are not "official rules" *per se*, but following them will help everyone deal with things in the most efficient manner.
|
||||||
|
|
||||||
|
## Table of contents
|
||||||
|
|
||||||
|
1. [I would like to submit an issue!](#i-would-like-to-submit-an-issue)
|
||||||
|
2. [I would like to submit a pull request!](#i-would-like-to-submit-a-pull-request)
|
||||||
|
|
||||||
|
## I would like to submit an issue!
|
||||||
|
|
||||||
|
Issues, bug reports and feature suggestions are welcomed, though please keep in mind that at any point in time, hundreds of issues are open, which vary in severity and the amount of time needed to address them. As such it's not uncommon for issues to remain unresolved for a long time or even closed outright if they are deemed not important enough to fix in the foreseeable future. Issues that are required to "go live" or otherwise achieve parity with stable are prioritised the most.
|
||||||
|
|
||||||
|
* **Before submitting an issue, try searching existing issues first.**
|
||||||
|
|
||||||
|
For housekeeping purposes, we close issues that overlap with or duplicate other pre-existing issues - you can help us not to have to do that by searching existing issues yourself first. The issue search box, as well as the issue tag system, are tools you can use to check if an issue has been reported before.
|
||||||
|
|
||||||
|
* **When submitting a bug report, please try to include as much detail as possible.**
|
||||||
|
|
||||||
|
Bugs are not equal - some of them will be reproducible every time on pretty much all hardware, while others will be hard to track down due to being specific to particular hardware or even somewhat random in nature. As such, providing as much detail as possible when reporting a bug is hugely appreciated. A good starting set of information consists of:
|
||||||
|
|
||||||
|
* the in-game logs, which are located at:
|
||||||
|
* `%AppData%/osu/logs` (on Windows),
|
||||||
|
* `~/.local/share/osu/logs` (on Linux and macOS),
|
||||||
|
* `Android/Data/sh.ppy.osulazer/logs` (on Android),
|
||||||
|
* on iOS they can be obtained by connecting your device to your desktop and [copying the `logs` directory from the app's own document storage using iTunes](https://support.apple.com/en-us/HT201301#copy-to-computer),
|
||||||
|
* your system specifications (including the operating system and platform you are playing on),
|
||||||
|
* a reproduction scenario (list of steps you have performed leading up to the occurrence of the bug),
|
||||||
|
* a video or picture of the bug, if at all possible.
|
||||||
|
|
||||||
|
* **Provide more information when asked to do so.**
|
||||||
|
|
||||||
|
Sometimes when a bug is more elusive or complicated, none of the information listed above will pinpoint a concrete cause of the problem. In this case we will most likely ask you for additional info, such as a Windows Event Log dump or a copy of your local lazer database (`client.db`). Providing that information is beneficial to both parties - we can track down the problem better, and hopefully fix it for you at some point once we know where it is!
|
||||||
|
|
||||||
|
* **When submitting a feature proposal, please describe it in the most understandable way you can.**
|
||||||
|
|
||||||
|
Communicating your idea for a feature can often be hard, and we would like to avoid any misunderstandings. As such, please try to explain your idea in a short, but understandable manner - it's best to avoid jargon or terms and references that could be considered obscure. A mock-up picture (doesn't have to be good!) of the feature can also go a long way in explaining.
|
||||||
|
|
||||||
|
* **Refrain from posting "+1" comments.**
|
||||||
|
|
||||||
|
If an issue has already been created, saying that you also experience it without providing any additional details doesn't really help us in any way. To express support for a proposal or indicate that you are also affected by a particular bug, you can use comment reactions instead.
|
||||||
|
|
||||||
|
* **Refrain from asking if an issue has been resolved yet.**
|
||||||
|
|
||||||
|
As mentioned above, the issue tracker has hundreds of issues open at any given time. Currently the game is being worked on by two members of the core team, and a handful of outside contributors who offer their free time to help out. As such, it can happen that an issue gets placed on the backburner due to being less important; generally posting a comment demanding its resolution some months or years after it is reported is not very likely to increase its priority.
|
||||||
|
|
||||||
|
* **Avoid long discussions about non-development topics.**
|
||||||
|
|
||||||
|
GitHub is mostly a developer space, and as such isn't really fit for lengthened discussions about gameplay mechanics (which might not even be in any way confirmed for the final release) and similar non-technical matters. Such matters are probably best addressed at the osu! forums.
|
||||||
|
|
||||||
|
## I would like to submit a pull request!
|
||||||
|
|
||||||
|
We also welcome pull requests from unaffiliated contributors. The [issue tracker](https://github.com/ppy/osu/issues) should provide plenty of issues that you can work on; we also mark issues that we think would be good for newcomers with the [`good-first-issue`](https://github.com/ppy/osu/issues?q=is%3Aissue+is%3Aopen+label%3Agood-first-issue) label.
|
||||||
|
|
||||||
|
However, do keep in mind that the core team is committed to bringing osu!lazer up to par with stable first and foremost, so depending on what your contribution concerns, it might not be merged and released right away. Our approach to managing issues and their priorities is described [in the wiki](https://github.com/ppy/osu/wiki/Project-management).
|
||||||
|
|
||||||
|
Here are some key things to note before jumping in:
|
||||||
|
|
||||||
|
* **Make sure you are comfortable with C\# and your development environment.**
|
||||||
|
|
||||||
|
While we are accepting of all kinds of contributions, we also have a certain quality standard we'd like to uphold and limited time to review your code. Therefore, we would like to avoid providing entry-level advice, and as such if you're not very familiar with C\# as a programming language, we'd recommend that you start off with a few personal projects to get acquainted with the language's syntax, toolchain and principles of object-oriented programming first.
|
||||||
|
|
||||||
|
In addition, please take the time to take a look at and get acquainted with the [development and testing](https://github.com/ppy/osu-framework/wiki/Development-and-Testing) procedure we have set up.
|
||||||
|
|
||||||
|
* **Make sure you are familiar with git and the pull request workflow.**
|
||||||
|
|
||||||
|
[git](https://git-scm.com/) is a distributed version control system that might not be very intuitive at the beginning if you're not familiar with version control. In particular, projects using git have a particular workflow for submitting code changes, which is called the pull request workflow.
|
||||||
|
|
||||||
|
To make things run more smoothly, we recommend that you look up some online resources to familiarise yourself with the git vocabulary and commands, and practice working with forks and submitting pull requests at your own pace. A high-level overview of the process can be found in [this article by GitHub](https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/proposing-changes-to-your-work-with-pull-requests).
|
||||||
|
|
||||||
|
* **Double-check designs before starting work on new functionality.**
|
||||||
|
|
||||||
|
When implementing new features, keep in mind that we already have a lot of the UI designed. If you wish to work on something with the intention of having it included in the official distribution, please open an issue for discussion and we will give you what you need from a design perspective to proceed. If you want to make *changes* to the design, we recommend you open an issue with your intentions before spending too much time to ensure no effort is wasted.
|
||||||
|
|
||||||
|
* **Make sure to submit pull requests off of a topic branch.**
|
||||||
|
|
||||||
|
As described in the article linked in the previous point, topic branches help you parallelise your work and separate it from the main `master` branch, and additionally are easier for maintainers to work with. Working with multiple `master` branches across many remotes is difficult to keep track of, and it's easy to make a mistake and push to the wrong `master` branch by accident.
|
||||||
|
|
||||||
|
* **Refrain from making changes through the GitHub web interface.**
|
||||||
|
|
||||||
|
Even though GitHub provides an option to edit code or replace files in the repository using the web interface, we strongly discourage using it in most scenarios. Editing files this way is inefficient and likely to introduce whitespace or file encoding changes that make it more difficult to review the code.
|
||||||
|
|
||||||
|
Code written through the web interface will also very likely be questioned outright by the reviewers, as it is likely that it has not been properly tested or that it will fail continuous integration checks. We strongly encourage using an IDE like [Visual Studio](https://visualstudio.microsoft.com/), [Visual Studio Code](https://code.visualstudio.com/) or [JetBrains Rider](https://www.jetbrains.com/rider/) instead.
|
||||||
|
|
||||||
|
* **Add tests for your code whenever possible.**
|
||||||
|
|
||||||
|
Automated tests are an essential part of a quality and reliable codebase. They help to make the code more maintainable by ensuring it is safe to reorganise (or refactor) the code in various ways, and also prevent regressions - bugs that resurface after having been fixed at some point in the past. If it is viable, please put in the time to add tests, so that the changes you make can last for a (hopefully) very long time.
|
||||||
|
|
||||||
|
* **Run tests before opening a pull request.**
|
||||||
|
|
||||||
|
Tying into the previous point, sometimes changes in one part of the codebase can result in unpredictable changes in behaviour in other pieces of the code. This is why it is best to always try to run tests before opening a PR.
|
||||||
|
|
||||||
|
Continuous integration will always run the tests for you (and us), too, but it is best not to rely on it, as there might be many builds queued at any time. Running tests on your own will help you be more certain that at the point of clicking the "Create pull request" button, your changes are as ready as can be.
|
||||||
|
|
||||||
|
* **Run code style analysis before opening a pull request.**
|
||||||
|
|
||||||
|
As part of continuous integration, we also run code style analysis, which is supposed to make sure that your code is formatted the same way as all the pre-existing code in the repository. The reason we enforce a particular code style everywhere is to make sure the codebase is consistent in that regard - having one whitespace convention in one place and another one elsewhere causes disorganisation.
|
||||||
|
|
||||||
|
* **Make sure that the pull request is complete before opening it.**
|
||||||
|
|
||||||
|
Whether it's fixing a bug or implementing new functionality, it's best that you make sure that the change you want to submit as a pull request is as complete as it can be before clicking the *Create pull request* button. Having to track if a pull request is ready for review or not places additional burden on reviewers.
|
||||||
|
|
||||||
|
Draft pull requests are an option, but use them sparingly and within reason. They are best suited to discuss code changes that cannot be easily described in natural language or have a potential large impact on the future direction of the project. When in doubt, don't open drafts unless a maintainer asks you to do so.
|
||||||
|
|
||||||
|
* **Only push code when it's ready.**
|
||||||
|
|
||||||
|
As an extension of the above, when making changes to an already-open PR, please try to only push changes you are reasonably certain of. Pushing after every commit causes the continuous integration build queue to grow in size, slowing down work and taking up time that could be spent verifying other changes.
|
||||||
|
|
||||||
|
* **Make sure to keep the *Allow edits from maintainers* check box checked.**
|
||||||
|
|
||||||
|
To speed up the merging process, collaborators and team members will sometimes want to push changes to your branch themselves, to make minor code style adjustments or to otherwise refactor the code without having to describe how they'd like the code to look like in painstaking detail. Having the *Allow edits from maintainers* check box checked lets them do that; without it they are forced to report issues back to you and wait for you to address them.
|
||||||
|
|
||||||
|
* **Refrain from continually merging the master branch back to the PR.**
|
||||||
|
|
||||||
|
Unless there are merge conflicts that need resolution, there is no need to keep merging `master` back to a branch over and over again. One of the maintainers will merge `master` themselves before merging the PR itself anyway, and continual merge commits can cause CI to get overwhelmed due to queueing up too many builds.
|
||||||
|
|
||||||
|
* **Refrain from force-pushing to the PR branch.**
|
||||||
|
|
||||||
|
Force-pushing should be avoided, as it can lead to accidentally overwriting a maintainer's changes or CI building wrong commits. We value all history in the project, so there is no need to squash or amend commits in most cases.
|
||||||
|
|
||||||
|
The cases in which force-pushing is warranted are very rare (such as accidentally leaking sensitive info in one of the files committed, adding unrelated files, or mis-merging a dependent PR).
|
||||||
|
|
||||||
|
* **Be patient when waiting for the code to be reviewed and merged.**
|
||||||
|
|
||||||
|
As much as we'd like to review all contributions as fast as possible, our time is limited, as team members have to work on their own tasks in addition to reviewing code. As such, work needs to be prioritised, and it can unfortunately take weeks or months for your PR to be merged, depending on how important it is deemed to be.
|
||||||
|
|
||||||
|
* **Don't mistake criticism of code for criticism of your person.**
|
||||||
|
|
||||||
|
As mentioned before, we are highly committed to quality when it comes to the lazer project. This means that contributions from less experienced community members can take multiple rounds of review to get to a mergeable state. We try our utmost best to never conflate a person with the code they authored, and to keep the discussion focused on the code at all times. Please consider our comments and requests a learning experience, and don't treat it as a personal attack.
|
||||||
|
|
||||||
|
* **Feel free to reach out for help.**
|
||||||
|
|
||||||
|
If you're uncertain about some part of the codebase or some inner workings of the game and framework, please reach out either by leaving a comment in the relevant issue or PR thread, or by posting a message in the [development Discord server](https://discord.gg/ppy). We will try to help you as much as we can.
|
||||||
|
|
||||||
|
When it comes to which form of communication is best, GitHub generally lends better to longer-form discussions, while Discord is better for snappy call-and-response answers. Use your best discretion when deciding, and try to keep a single discussion in one place instead of moving back and forth.
|
72
Gemfile.lock
72
Gemfile.lock
@ -5,6 +5,22 @@ GEM
|
|||||||
addressable (2.7.0)
|
addressable (2.7.0)
|
||||||
public_suffix (>= 2.0.2, < 5.0)
|
public_suffix (>= 2.0.2, < 5.0)
|
||||||
atomos (0.1.3)
|
atomos (0.1.3)
|
||||||
|
aws-eventstream (1.1.0)
|
||||||
|
aws-partitions (1.329.0)
|
||||||
|
aws-sdk-core (3.99.2)
|
||||||
|
aws-eventstream (~> 1, >= 1.0.2)
|
||||||
|
aws-partitions (~> 1, >= 1.239.0)
|
||||||
|
aws-sigv4 (~> 1.1)
|
||||||
|
jmespath (~> 1.0)
|
||||||
|
aws-sdk-kms (1.34.1)
|
||||||
|
aws-sdk-core (~> 3, >= 3.99.0)
|
||||||
|
aws-sigv4 (~> 1.1)
|
||||||
|
aws-sdk-s3 (1.68.1)
|
||||||
|
aws-sdk-core (~> 3, >= 3.99.0)
|
||||||
|
aws-sdk-kms (~> 1)
|
||||||
|
aws-sigv4 (~> 1.1)
|
||||||
|
aws-sigv4 (1.1.4)
|
||||||
|
aws-eventstream (~> 1.0, >= 1.0.2)
|
||||||
babosa (1.0.3)
|
babosa (1.0.3)
|
||||||
claide (1.0.3)
|
claide (1.0.3)
|
||||||
colored (1.2)
|
colored (1.2)
|
||||||
@ -13,23 +29,24 @@ GEM
|
|||||||
highline (~> 1.7.2)
|
highline (~> 1.7.2)
|
||||||
declarative (0.0.10)
|
declarative (0.0.10)
|
||||||
declarative-option (0.1.0)
|
declarative-option (0.1.0)
|
||||||
digest-crc (0.4.1)
|
digest-crc (0.5.1)
|
||||||
domain_name (0.5.20190701)
|
domain_name (0.5.20190701)
|
||||||
unf (>= 0.0.5, < 1.0.0)
|
unf (>= 0.0.5, < 1.0.0)
|
||||||
dotenv (2.7.5)
|
dotenv (2.7.5)
|
||||||
emoji_regex (1.0.1)
|
emoji_regex (1.0.1)
|
||||||
excon (0.71.1)
|
excon (0.74.0)
|
||||||
faraday (0.17.3)
|
faraday (1.0.1)
|
||||||
multipart-post (>= 1.2, < 3)
|
multipart-post (>= 1.2, < 3)
|
||||||
faraday-cookie_jar (0.0.6)
|
faraday-cookie_jar (0.0.6)
|
||||||
faraday (>= 0.7.4)
|
faraday (>= 0.7.4)
|
||||||
http-cookie (~> 1.0.0)
|
http-cookie (~> 1.0.0)
|
||||||
faraday_middleware (0.13.1)
|
faraday_middleware (1.0.0)
|
||||||
faraday (>= 0.7.4, < 1.0)
|
faraday (~> 1.0)
|
||||||
fastimage (2.1.7)
|
fastimage (2.1.7)
|
||||||
fastlane (2.140.0)
|
fastlane (2.149.1)
|
||||||
CFPropertyList (>= 2.3, < 4.0.0)
|
CFPropertyList (>= 2.3, < 4.0.0)
|
||||||
addressable (>= 2.3, < 3.0.0)
|
addressable (>= 2.3, < 3.0.0)
|
||||||
|
aws-sdk-s3 (~> 1.0)
|
||||||
babosa (>= 1.0.2, < 2.0.0)
|
babosa (>= 1.0.2, < 2.0.0)
|
||||||
bundler (>= 1.12.0, < 3.0.0)
|
bundler (>= 1.12.0, < 3.0.0)
|
||||||
colored
|
colored
|
||||||
@ -37,12 +54,12 @@ GEM
|
|||||||
dotenv (>= 2.1.1, < 3.0.0)
|
dotenv (>= 2.1.1, < 3.0.0)
|
||||||
emoji_regex (>= 0.1, < 2.0)
|
emoji_regex (>= 0.1, < 2.0)
|
||||||
excon (>= 0.71.0, < 1.0.0)
|
excon (>= 0.71.0, < 1.0.0)
|
||||||
faraday (~> 0.17)
|
faraday (>= 0.17, < 2.0)
|
||||||
faraday-cookie_jar (~> 0.0.6)
|
faraday-cookie_jar (~> 0.0.6)
|
||||||
faraday_middleware (~> 0.13.1)
|
faraday_middleware (>= 0.13.1, < 2.0)
|
||||||
fastimage (>= 2.1.0, < 3.0.0)
|
fastimage (>= 2.1.0, < 3.0.0)
|
||||||
gh_inspector (>= 1.1.2, < 2.0.0)
|
gh_inspector (>= 1.1.2, < 2.0.0)
|
||||||
google-api-client (>= 0.29.2, < 0.37.0)
|
google-api-client (>= 0.37.0, < 0.39.0)
|
||||||
google-cloud-storage (>= 1.15.0, < 2.0.0)
|
google-cloud-storage (>= 1.15.0, < 2.0.0)
|
||||||
highline (>= 1.7.2, < 2.0.0)
|
highline (>= 1.7.2, < 2.0.0)
|
||||||
json (< 3.0.0)
|
json (< 3.0.0)
|
||||||
@ -69,7 +86,7 @@ GEM
|
|||||||
souyuz (= 0.9.1)
|
souyuz (= 0.9.1)
|
||||||
fastlane-plugin-xamarin (0.6.3)
|
fastlane-plugin-xamarin (0.6.3)
|
||||||
gh_inspector (1.1.3)
|
gh_inspector (1.1.3)
|
||||||
google-api-client (0.36.4)
|
google-api-client (0.38.0)
|
||||||
addressable (~> 2.5, >= 2.5.1)
|
addressable (~> 2.5, >= 2.5.1)
|
||||||
googleauth (~> 0.9)
|
googleauth (~> 0.9)
|
||||||
httpclient (>= 2.8.1, < 3.0)
|
httpclient (>= 2.8.1, < 3.0)
|
||||||
@ -80,27 +97,28 @@ GEM
|
|||||||
google-cloud-core (1.5.0)
|
google-cloud-core (1.5.0)
|
||||||
google-cloud-env (~> 1.0)
|
google-cloud-env (~> 1.0)
|
||||||
google-cloud-errors (~> 1.0)
|
google-cloud-errors (~> 1.0)
|
||||||
google-cloud-env (1.3.0)
|
google-cloud-env (1.3.2)
|
||||||
faraday (~> 0.11)
|
faraday (>= 0.17.3, < 2.0)
|
||||||
google-cloud-errors (1.0.0)
|
google-cloud-errors (1.0.1)
|
||||||
google-cloud-storage (1.25.1)
|
google-cloud-storage (1.26.2)
|
||||||
addressable (~> 2.5)
|
addressable (~> 2.5)
|
||||||
digest-crc (~> 0.4)
|
digest-crc (~> 0.4)
|
||||||
google-api-client (~> 0.33)
|
google-api-client (~> 0.33)
|
||||||
google-cloud-core (~> 1.2)
|
google-cloud-core (~> 1.2)
|
||||||
googleauth (~> 0.9)
|
googleauth (~> 0.9)
|
||||||
mini_mime (~> 1.0)
|
mini_mime (~> 1.0)
|
||||||
googleauth (0.10.0)
|
googleauth (0.12.0)
|
||||||
faraday (~> 0.12)
|
faraday (>= 0.17.3, < 2.0)
|
||||||
jwt (>= 1.4, < 3.0)
|
jwt (>= 1.4, < 3.0)
|
||||||
memoist (~> 0.16)
|
memoist (~> 0.16)
|
||||||
multi_json (~> 1.11)
|
multi_json (~> 1.11)
|
||||||
os (>= 0.9, < 2.0)
|
os (>= 0.9, < 2.0)
|
||||||
signet (~> 0.12)
|
signet (~> 0.14)
|
||||||
highline (1.7.10)
|
highline (1.7.10)
|
||||||
http-cookie (1.0.3)
|
http-cookie (1.0.3)
|
||||||
domain_name (~> 0.5)
|
domain_name (~> 0.5)
|
||||||
httpclient (2.8.3)
|
httpclient (2.8.3)
|
||||||
|
jmespath (1.4.0)
|
||||||
json (2.3.0)
|
json (2.3.0)
|
||||||
jwt (2.1.0)
|
jwt (2.1.0)
|
||||||
memoist (0.16.2)
|
memoist (0.16.2)
|
||||||
@ -114,7 +132,7 @@ GEM
|
|||||||
naturally (2.2.0)
|
naturally (2.2.0)
|
||||||
nokogiri (1.10.7)
|
nokogiri (1.10.7)
|
||||||
mini_portile2 (~> 2.4.0)
|
mini_portile2 (~> 2.4.0)
|
||||||
os (1.0.1)
|
os (1.1.0)
|
||||||
plist (3.5.0)
|
plist (3.5.0)
|
||||||
public_suffix (2.0.5)
|
public_suffix (2.0.5)
|
||||||
representable (3.0.4)
|
representable (3.0.4)
|
||||||
@ -125,12 +143,12 @@ GEM
|
|||||||
rouge (2.0.7)
|
rouge (2.0.7)
|
||||||
rubyzip (1.3.0)
|
rubyzip (1.3.0)
|
||||||
security (0.1.3)
|
security (0.1.3)
|
||||||
signet (0.12.0)
|
signet (0.14.0)
|
||||||
addressable (~> 2.3)
|
addressable (~> 2.3)
|
||||||
faraday (~> 0.9)
|
faraday (>= 0.17.3, < 2.0)
|
||||||
jwt (>= 1.5, < 3.0)
|
jwt (>= 1.5, < 3.0)
|
||||||
multi_json (~> 1.10)
|
multi_json (~> 1.10)
|
||||||
simctl (1.6.7)
|
simctl (1.6.8)
|
||||||
CFPropertyList
|
CFPropertyList
|
||||||
naturally
|
naturally
|
||||||
slack-notifier (2.3.2)
|
slack-notifier (2.3.2)
|
||||||
@ -141,17 +159,17 @@ GEM
|
|||||||
terminal-notifier (2.0.0)
|
terminal-notifier (2.0.0)
|
||||||
terminal-table (1.8.0)
|
terminal-table (1.8.0)
|
||||||
unicode-display_width (~> 1.1, >= 1.1.1)
|
unicode-display_width (~> 1.1, >= 1.1.1)
|
||||||
tty-cursor (0.7.0)
|
tty-cursor (0.7.1)
|
||||||
tty-screen (0.7.0)
|
tty-screen (0.8.0)
|
||||||
tty-spinner (0.9.2)
|
tty-spinner (0.9.3)
|
||||||
tty-cursor (~> 0.7)
|
tty-cursor (~> 0.7)
|
||||||
uber (0.1.0)
|
uber (0.1.0)
|
||||||
unf (0.1.4)
|
unf (0.1.4)
|
||||||
unf_ext
|
unf_ext
|
||||||
unf_ext (0.0.7.6)
|
unf_ext (0.0.7.7)
|
||||||
unicode-display_width (1.6.1)
|
unicode-display_width (1.7.0)
|
||||||
word_wrap (1.0.0)
|
word_wrap (1.0.0)
|
||||||
xcodeproj (1.14.0)
|
xcodeproj (1.16.0)
|
||||||
CFPropertyList (>= 2.3.3, < 4.0)
|
CFPropertyList (>= 2.3.3, < 4.0)
|
||||||
atomos (~> 0.1.3)
|
atomos (~> 0.1.3)
|
||||||
claide (>= 1.0.2, < 2.0)
|
claide (>= 1.0.2, < 2.0)
|
||||||
|
@ -93,11 +93,7 @@ JetBrains ReSharper InspectCode is also used for wider rule sets. You can run it
|
|||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
We welcome all contributions, but keep in mind that we already have a lot of the UI designed. If you wish to work on something with the intention of having it included in the official distribution, please open an issue for discussion and we will give you what you need from a design perspective to proceed. If you want to make *changes* to the design, we recommend you open an issue with your intentions before spending too much time to ensure no effort is wasted.
|
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.
|
||||||
|
|
||||||
If you're unsure of what you can help with, check out the [list of open issues](https://github.com/ppy/osu/issues) (especially those with the ["good first issue"](https://github.com/ppy/osu/issues?q=is%3Aopen+label%3Agood-first-issue+sort%3Aupdated-desc) label).
|
|
||||||
|
|
||||||
Before starting, please make sure you are familiar with the [development and testing](https://github.com/ppy/osu-framework/wiki/Development-and-Testing) procedure we have set up. New component development, and where possible, bug fixing and debugging existing components **should always be done under VisualTests**.
|
|
||||||
|
|
||||||
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.
|
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.
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
#addin "nuget:?package=CodeFileSanity&version=0.0.33"
|
#addin "nuget:?package=CodeFileSanity&version=0.0.36"
|
||||||
#addin "nuget:?package=JetBrains.ReSharper.CommandLineTools&version=2019.3.2"
|
#addin "nuget:?package=JetBrains.ReSharper.CommandLineTools&version=2020.1.3"
|
||||||
#tool "nuget:?package=NVika.MSBuild&version=1.0.1"
|
#tool "nuget:?package=NVika.MSBuild&version=1.0.1"
|
||||||
var nVikaToolPath = GetFiles("./tools/NVika.MSBuild.*/tools/NVika.exe").First();
|
var nVikaToolPath = GetFiles("./tools/NVika.MSBuild.*/tools/NVika.exe").First();
|
||||||
|
|
||||||
|
@ -5,6 +5,6 @@
|
|||||||
"version": "3.1.100"
|
"version": "3.1.100"
|
||||||
},
|
},
|
||||||
"msbuild-sdks": {
|
"msbuild-sdks": {
|
||||||
"Microsoft.Build.Traversal": "2.0.34"
|
"Microsoft.Build.Traversal": "2.0.48"
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -51,7 +51,7 @@
|
|||||||
<Reference Include="Java.Interop" />
|
<Reference Include="Java.Interop" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2020.512.0" />
|
<PackageReference Include="ppy.osu.Game.Resources" Version="2020.602.0" />
|
||||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2020.528.0" />
|
<PackageReference Include="ppy.osu.Framework.Android" Version="2020.609.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
@ -10,7 +10,6 @@ using Microsoft.Win32;
|
|||||||
using osu.Desktop.Overlays;
|
using osu.Desktop.Overlays;
|
||||||
using osu.Framework.Platform;
|
using osu.Framework.Platform;
|
||||||
using osu.Game;
|
using osu.Game;
|
||||||
using osuTK.Input;
|
|
||||||
using osu.Desktop.Updater;
|
using osu.Desktop.Updater;
|
||||||
using osu.Framework;
|
using osu.Framework;
|
||||||
using osu.Framework.Logging;
|
using osu.Framework.Logging;
|
||||||
@ -59,7 +58,7 @@ namespace osu.Desktop
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
using (RegistryKey key = Registry.ClassesRoot.OpenSubKey("osu"))
|
using (RegistryKey key = Registry.ClassesRoot.OpenSubKey("osu"))
|
||||||
stableInstallPath = key?.OpenSubKey(@"shell\open\command")?.GetValue(string.Empty).ToString().Split('"')[1].Replace("osu!.exe", "");
|
stableInstallPath = key?.OpenSubKey(@"shell\open\command")?.GetValue(string.Empty).ToString()?.Split('"')[1].Replace("osu!.exe", "");
|
||||||
|
|
||||||
if (checkExists(stableInstallPath))
|
if (checkExists(stableInstallPath))
|
||||||
return stableInstallPath;
|
return stableInstallPath;
|
||||||
@ -122,21 +121,27 @@ namespace osu.Desktop
|
|||||||
{
|
{
|
||||||
base.SetHost(host);
|
base.SetHost(host);
|
||||||
|
|
||||||
if (host.Window is DesktopGameWindow desktopWindow)
|
switch (host.Window)
|
||||||
{
|
{
|
||||||
desktopWindow.CursorState |= CursorState.Hidden;
|
// Legacy osuTK DesktopGameWindow
|
||||||
|
case DesktopGameWindow desktopGameWindow:
|
||||||
|
desktopGameWindow.CursorState |= CursorState.Hidden;
|
||||||
|
desktopGameWindow.SetIconFromStream(Assembly.GetExecutingAssembly().GetManifestResourceStream(GetType(), "lazer.ico"));
|
||||||
|
desktopGameWindow.Title = Name;
|
||||||
|
desktopGameWindow.FileDrop += (_, e) => fileDrop(e.FileNames);
|
||||||
|
break;
|
||||||
|
|
||||||
desktopWindow.SetIconFromStream(Assembly.GetExecutingAssembly().GetManifestResourceStream(GetType(), "lazer.ico"));
|
// SDL2 DesktopWindow
|
||||||
|
case DesktopWindow desktopWindow:
|
||||||
|
desktopWindow.CursorState.Value |= CursorState.Hidden;
|
||||||
desktopWindow.Title = Name;
|
desktopWindow.Title = Name;
|
||||||
|
desktopWindow.DragDrop += f => fileDrop(new[] { f });
|
||||||
desktopWindow.FileDrop += fileDrop;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void fileDrop(object sender, FileDropEventArgs e)
|
private void fileDrop(string[] filePaths)
|
||||||
{
|
{
|
||||||
var filePaths = e.FileNames;
|
|
||||||
|
|
||||||
var firstExtension = Path.GetExtension(filePaths.First());
|
var firstExtension = Path.GetExtension(filePaths.First());
|
||||||
|
|
||||||
if (filePaths.Any(f => Path.GetExtension(f) != firstExtension)) return;
|
if (filePaths.Any(f => Path.GetExtension(f) != firstExtension)) return;
|
||||||
|
@ -33,13 +33,11 @@ namespace osu.Desktop
|
|||||||
if (args.Length > 0 && args[0].Contains('.')) // easy way to check for a file import in args
|
if (args.Length > 0 && args[0].Contains('.')) // easy way to check for a file import in args
|
||||||
{
|
{
|
||||||
var importer = new ArchiveImportIPCChannel(host);
|
var importer = new ArchiveImportIPCChannel(host);
|
||||||
// Restore the cwd so relative paths given at the command line work correctly
|
|
||||||
Directory.SetCurrentDirectory(cwd);
|
|
||||||
|
|
||||||
foreach (var file in args)
|
foreach (var file in args)
|
||||||
{
|
{
|
||||||
Console.WriteLine(@"Importing {0}", file);
|
Console.WriteLine(@"Importing {0}", file);
|
||||||
if (!importer.ImportAsync(Path.GetFullPath(file)).Wait(3000))
|
if (!importer.ImportAsync(Path.GetFullPath(file, cwd)).Wait(3000))
|
||||||
throw new TimeoutException(@"IPC took too long to send");
|
throw new TimeoutException(@"IPC took too long to send");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -48,7 +48,7 @@ namespace osu.Desktop.Updater
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (updateManager == null) updateManager = await UpdateManager.GitHubUpdateManager(@"https://github.com/ppy/osu", @"osulazer", null, null, true);
|
updateManager ??= await UpdateManager.GitHubUpdateManager(@"https://github.com/ppy/osu", @"osulazer", null, null, true);
|
||||||
|
|
||||||
var info = await updateManager.CheckForUpdate(!useDeltaPatching);
|
var info = await updateManager.CheckForUpdate(!useDeltaPatching);
|
||||||
if (info.ReleasesToApply.Count == 0)
|
if (info.ReleasesToApply.Count == 0)
|
||||||
|
@ -30,6 +30,10 @@
|
|||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="2.2.6" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="2.2.6" />
|
||||||
<PackageReference Include="Microsoft.Win32.Registry" Version="4.7.0" />
|
<PackageReference Include="Microsoft.Win32.Registry" Version="4.7.0" />
|
||||||
<PackageReference Include="DiscordRichPresence" Version="1.0.150" />
|
<PackageReference Include="DiscordRichPresence" Version="1.0.150" />
|
||||||
|
<!-- .NET 3.1 SDK seems to cause issues with a runtime specification. This will likely be resolved in .NET 5. -->
|
||||||
|
<PackageReference Include="System.IO.FileSystem.Primitives" Version="4.3.0" />
|
||||||
|
<PackageReference Include="System.Runtime.Handles" Version="4.3.0" />
|
||||||
|
<PackageReference Include="System.Runtime.InteropServices" Version="4.3.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup Label="Resources">
|
<ItemGroup Label="Resources">
|
||||||
<EmbeddedResource Include="lazer.ico" />
|
<EmbeddedResource Include="lazer.ico" />
|
||||||
|
@ -35,8 +35,7 @@ namespace osu.Game.Rulesets.Catch.Difficulty.Skills
|
|||||||
{
|
{
|
||||||
var catchCurrent = (CatchDifficultyHitObject)current;
|
var catchCurrent = (CatchDifficultyHitObject)current;
|
||||||
|
|
||||||
if (lastPlayerPosition == null)
|
lastPlayerPosition ??= catchCurrent.LastNormalizedPosition;
|
||||||
lastPlayerPosition = catchCurrent.LastNormalizedPosition;
|
|
||||||
|
|
||||||
float playerPosition = Math.Clamp(
|
float playerPosition = Math.Clamp(
|
||||||
lastPlayerPosition.Value,
|
lastPlayerPosition.Value,
|
||||||
|
@ -0,0 +1,76 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Utils;
|
||||||
|
using osu.Game.Audio;
|
||||||
|
using osu.Game.Rulesets.Mania.Objects;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Tests.Beatmaps;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Mania.Tests
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class ManiaBeatmapSampleConversionTest : BeatmapConversionTest<ConvertMapping<SampleConvertValue>, SampleConvertValue>
|
||||||
|
{
|
||||||
|
protected override string ResourceAssembly => "osu.Game.Rulesets.Mania";
|
||||||
|
|
||||||
|
[TestCase("convert-samples")]
|
||||||
|
[TestCase("mania-samples")]
|
||||||
|
public void Test(string name) => base.Test(name);
|
||||||
|
|
||||||
|
protected override IEnumerable<SampleConvertValue> CreateConvertValue(HitObject hitObject)
|
||||||
|
{
|
||||||
|
yield return new SampleConvertValue
|
||||||
|
{
|
||||||
|
StartTime = hitObject.StartTime,
|
||||||
|
EndTime = hitObject.GetEndTime(),
|
||||||
|
Column = ((ManiaHitObject)hitObject).Column,
|
||||||
|
NodeSamples = getSampleNames((hitObject as HoldNote)?.NodeSamples)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private IList<IList<string>> getSampleNames(List<IList<HitSampleInfo>> hitSampleInfo)
|
||||||
|
=> hitSampleInfo?.Select(samples =>
|
||||||
|
(IList<string>)samples.Select(sample => sample.LookupNames.First()).ToList())
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
protected override Ruleset CreateRuleset() => new ManiaRuleset();
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct SampleConvertValue : IEquatable<SampleConvertValue>
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A sane value to account for osu!stable using ints everywhere.
|
||||||
|
/// </summary>
|
||||||
|
private const float conversion_lenience = 2;
|
||||||
|
|
||||||
|
public double StartTime;
|
||||||
|
public double EndTime;
|
||||||
|
public int Column;
|
||||||
|
public IList<IList<string>> NodeSamples;
|
||||||
|
|
||||||
|
public bool Equals(SampleConvertValue other)
|
||||||
|
=> Precision.AlmostEquals(StartTime, other.StartTime, conversion_lenience)
|
||||||
|
&& Precision.AlmostEquals(EndTime, other.EndTime, conversion_lenience)
|
||||||
|
&& samplesEqual(NodeSamples, other.NodeSamples);
|
||||||
|
|
||||||
|
private static bool samplesEqual(ICollection<IList<string>> firstSampleList, ICollection<IList<string>> secondSampleList)
|
||||||
|
{
|
||||||
|
if (firstSampleList == null && secondSampleList == null)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
// both items can't be null now, so if any single one is, then they're not equal
|
||||||
|
if (firstSampleList == null || secondSampleList == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return firstSampleList.Count == secondSampleList.Count
|
||||||
|
// cannot use .Zip() without the selector function as it doesn't compile in android test project
|
||||||
|
&& firstSampleList.Zip(secondSampleList, (first, second) => (first, second))
|
||||||
|
.All(samples => samples.first.SequenceEqual(samples.second));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -6,6 +6,7 @@ using System;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
|
using osu.Game.Audio;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.Objects.Types;
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
@ -239,7 +240,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
|
|||||||
Duration = endTimeData.Duration,
|
Duration = endTimeData.Duration,
|
||||||
Column = column,
|
Column = column,
|
||||||
Samples = HitObject.Samples,
|
Samples = HitObject.Samples,
|
||||||
NodeSamples = (HitObject as IHasRepeats)?.NodeSamples
|
NodeSamples = (HitObject as IHasRepeats)?.NodeSamples ?? defaultNodeSamples
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
else if (HitObject is IHasXPosition)
|
else if (HitObject is IHasXPosition)
|
||||||
@ -254,6 +255,16 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
|
|||||||
|
|
||||||
return pattern;
|
return pattern;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <remarks>
|
||||||
|
/// osu!mania-specific beatmaps in stable only play samples at the start of the hold note.
|
||||||
|
/// </remarks>
|
||||||
|
private List<IList<HitSampleInfo>> defaultNodeSamples
|
||||||
|
=> new List<IList<HitSampleInfo>>
|
||||||
|
{
|
||||||
|
HitObject.Samples,
|
||||||
|
new List<HitSampleInfo>()
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -472,15 +472,23 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="time">The time to retrieve the sample info list from.</param>
|
/// <param name="time">The time to retrieve the sample info list from.</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
private IList<HitSampleInfo> sampleInfoListAt(double time)
|
private IList<HitSampleInfo> sampleInfoListAt(double time) => nodeSamplesAt(time)?.First() ?? HitObject.Samples;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieves the list of node samples that occur at time greater than or equal to <paramref name="time"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="time">The time to retrieve node samples at.</param>
|
||||||
|
private List<IList<HitSampleInfo>> nodeSamplesAt(double time)
|
||||||
{
|
{
|
||||||
if (!(HitObject is IHasPathWithRepeats curveData))
|
if (!(HitObject is IHasPathWithRepeats curveData))
|
||||||
return HitObject.Samples;
|
return null;
|
||||||
|
|
||||||
double segmentTime = (EndTime - HitObject.StartTime) / spanCount;
|
double segmentTime = (EndTime - HitObject.StartTime) / spanCount;
|
||||||
|
|
||||||
int index = (int)(segmentTime == 0 ? 0 : (time - HitObject.StartTime) / segmentTime);
|
int index = (int)(segmentTime == 0 ? 0 : (time - HitObject.StartTime) / segmentTime);
|
||||||
return curveData.NodeSamples[index];
|
|
||||||
|
// avoid slicing the list & creating copies, if at all possible.
|
||||||
|
return index == 0 ? curveData.NodeSamples : curveData.NodeSamples.Skip(index).ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -511,7 +519,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
|||||||
Duration = endTime - startTime,
|
Duration = endTime - startTime,
|
||||||
Column = column,
|
Column = column,
|
||||||
Samples = HitObject.Samples,
|
Samples = HitObject.Samples,
|
||||||
NodeSamples = (HitObject as IHasRepeats)?.NodeSamples
|
NodeSamples = nodeSamplesAt(startTime)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,30 @@
|
|||||||
|
{
|
||||||
|
"Mappings": [{
|
||||||
|
"StartTime": 1000.0,
|
||||||
|
"Objects": [{
|
||||||
|
"StartTime": 1000.0,
|
||||||
|
"EndTime": 2750.0,
|
||||||
|
"Column": 1,
|
||||||
|
"NodeSamples": [
|
||||||
|
["normal-hitnormal"],
|
||||||
|
["soft-hitnormal"],
|
||||||
|
["drum-hitnormal"]
|
||||||
|
]
|
||||||
|
}, {
|
||||||
|
"StartTime": 1875.0,
|
||||||
|
"EndTime": 2750.0,
|
||||||
|
"Column": 0,
|
||||||
|
"NodeSamples": [
|
||||||
|
["soft-hitnormal"],
|
||||||
|
["drum-hitnormal"]
|
||||||
|
]
|
||||||
|
}]
|
||||||
|
}, {
|
||||||
|
"StartTime": 3750.0,
|
||||||
|
"Objects": [{
|
||||||
|
"StartTime": 3750.0,
|
||||||
|
"EndTime": 3750.0,
|
||||||
|
"Column": 3
|
||||||
|
}]
|
||||||
|
}]
|
||||||
|
}
|
@ -0,0 +1,16 @@
|
|||||||
|
osu file format v14
|
||||||
|
|
||||||
|
[Difficulty]
|
||||||
|
HPDrainRate:5
|
||||||
|
CircleSize:5
|
||||||
|
OverallDifficulty:5
|
||||||
|
ApproachRate:5
|
||||||
|
SliderMultiplier:1.4
|
||||||
|
SliderTickRate:1
|
||||||
|
|
||||||
|
[TimingPoints]
|
||||||
|
0,500,4,1,0,100,1,0
|
||||||
|
|
||||||
|
[HitObjects]
|
||||||
|
88,99,1000,6,0,L|306:259,2,245,0|0|0,1:0|2:0|3:0,0:0:0:0:
|
||||||
|
259,118,3750,1,0,0:0:0:0:
|
@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"Mappings": [{
|
||||||
|
"StartTime": 500.0,
|
||||||
|
"Objects": [{
|
||||||
|
"StartTime": 500.0,
|
||||||
|
"EndTime": 1500.0,
|
||||||
|
"Column": 0,
|
||||||
|
"NodeSamples": [
|
||||||
|
["normal-hitnormal"],
|
||||||
|
[]
|
||||||
|
]
|
||||||
|
}]
|
||||||
|
}, {
|
||||||
|
"StartTime": 2000.0,
|
||||||
|
"Objects": [{
|
||||||
|
"StartTime": 2000.0,
|
||||||
|
"EndTime": 3000.0,
|
||||||
|
"Column": 2,
|
||||||
|
"NodeSamples": [
|
||||||
|
["drum-hitnormal"],
|
||||||
|
[]
|
||||||
|
]
|
||||||
|
}]
|
||||||
|
}]
|
||||||
|
}
|
@ -0,0 +1,19 @@
|
|||||||
|
osu file format v14
|
||||||
|
|
||||||
|
[General]
|
||||||
|
Mode: 3
|
||||||
|
|
||||||
|
[Difficulty]
|
||||||
|
HPDrainRate:5
|
||||||
|
CircleSize:5
|
||||||
|
OverallDifficulty:5
|
||||||
|
ApproachRate:5
|
||||||
|
SliderMultiplier:1.4
|
||||||
|
SliderTickRate:1
|
||||||
|
|
||||||
|
[TimingPoints]
|
||||||
|
0,500,4,1,0,100,1,0
|
||||||
|
|
||||||
|
[HitObjects]
|
||||||
|
51,192,500,128,0,1500:1:0:0:0:
|
||||||
|
256,192,2000,128,0,3000:3:0:0:0:
|
@ -61,7 +61,9 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
private DrawableSlider slider;
|
private DrawableSlider slider;
|
||||||
|
|
||||||
[SetUpSteps]
|
[SetUpSteps]
|
||||||
public override void SetUpSteps() { }
|
public override void SetUpSteps()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
[TestCase(0)]
|
[TestCase(0)]
|
||||||
[TestCase(1)]
|
[TestCase(1)]
|
||||||
@ -132,10 +134,9 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
checkPositionChange(16600, sliderRepeat, positionDecreased);
|
checkPositionChange(16600, sliderRepeat, positionDecreased);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void retrieveDrawableSlider(int index) => AddStep($"retrieve {(index + 1).ToOrdinalWords()} slider", () =>
|
private void retrieveDrawableSlider(int index) =>
|
||||||
{
|
AddStep($"retrieve {(index + 1).ToOrdinalWords()} slider", () =>
|
||||||
slider = (DrawableSlider)Player.DrawableRuleset.Playfield.AllHitObjects.ElementAt(index);
|
slider = (DrawableSlider)Player.DrawableRuleset.Playfield.AllHitObjects.ElementAt(index));
|
||||||
});
|
|
||||||
|
|
||||||
private void ensureSnakingIn(double startTime) => checkPositionChange(startTime, sliderEnd, positionIncreased);
|
private void ensureSnakingIn(double startTime) => checkPositionChange(startTime, sliderEnd, positionIncreased);
|
||||||
private void ensureNoSnakingIn(double startTime) => checkPositionChange(startTime, sliderEnd, positionRemainsSame);
|
private void ensureNoSnakingIn(double startTime) => checkPositionChange(startTime, sliderEnd, positionRemainsSame);
|
||||||
|
@ -135,8 +135,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
|
|||||||
fp.Alpha = 0;
|
fp.Alpha = 0;
|
||||||
fp.Scale = new Vector2(1.5f * osuEnd.Scale);
|
fp.Scale = new Vector2(1.5f * osuEnd.Scale);
|
||||||
|
|
||||||
if (firstTransformStartTime == null)
|
firstTransformStartTime ??= fadeInTime;
|
||||||
firstTransformStartTime = fadeInTime;
|
|
||||||
|
|
||||||
fp.AnimationStartTime = fadeInTime;
|
fp.AnimationStartTime = fadeInTime;
|
||||||
|
|
||||||
|
@ -237,6 +237,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
|
|||||||
{
|
{
|
||||||
Position = new Vector2(part.Position.X - size.X / 2, part.Position.Y + size.Y / 2),
|
Position = new Vector2(part.Position.X - size.X / 2, part.Position.Y + size.Y / 2),
|
||||||
TexturePosition = textureRect.BottomLeft,
|
TexturePosition = textureRect.BottomLeft,
|
||||||
|
TextureRect = new Vector4(0, 0, 1, 1),
|
||||||
Colour = DrawColourInfo.Colour.BottomLeft.Linear,
|
Colour = DrawColourInfo.Colour.BottomLeft.Linear,
|
||||||
Time = part.Time
|
Time = part.Time
|
||||||
});
|
});
|
||||||
@ -245,6 +246,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
|
|||||||
{
|
{
|
||||||
Position = new Vector2(part.Position.X + size.X / 2, part.Position.Y + size.Y / 2),
|
Position = new Vector2(part.Position.X + size.X / 2, part.Position.Y + size.Y / 2),
|
||||||
TexturePosition = textureRect.BottomRight,
|
TexturePosition = textureRect.BottomRight,
|
||||||
|
TextureRect = new Vector4(0, 0, 1, 1),
|
||||||
Colour = DrawColourInfo.Colour.BottomRight.Linear,
|
Colour = DrawColourInfo.Colour.BottomRight.Linear,
|
||||||
Time = part.Time
|
Time = part.Time
|
||||||
});
|
});
|
||||||
@ -253,6 +255,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
|
|||||||
{
|
{
|
||||||
Position = new Vector2(part.Position.X + size.X / 2, part.Position.Y - size.Y / 2),
|
Position = new Vector2(part.Position.X + size.X / 2, part.Position.Y - size.Y / 2),
|
||||||
TexturePosition = textureRect.TopRight,
|
TexturePosition = textureRect.TopRight,
|
||||||
|
TextureRect = new Vector4(0, 0, 1, 1),
|
||||||
Colour = DrawColourInfo.Colour.TopRight.Linear,
|
Colour = DrawColourInfo.Colour.TopRight.Linear,
|
||||||
Time = part.Time
|
Time = part.Time
|
||||||
});
|
});
|
||||||
@ -261,6 +264,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
|
|||||||
{
|
{
|
||||||
Position = new Vector2(part.Position.X - size.X / 2, part.Position.Y - size.Y / 2),
|
Position = new Vector2(part.Position.X - size.X / 2, part.Position.Y - size.Y / 2),
|
||||||
TexturePosition = textureRect.TopLeft,
|
TexturePosition = textureRect.TopLeft,
|
||||||
|
TextureRect = new Vector4(0, 0, 1, 1),
|
||||||
Colour = DrawColourInfo.Colour.TopLeft.Linear,
|
Colour = DrawColourInfo.Colour.TopLeft.Linear,
|
||||||
Time = part.Time
|
Time = part.Time
|
||||||
});
|
});
|
||||||
@ -290,6 +294,9 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
|
|||||||
[VertexMember(2, VertexAttribPointerType.Float)]
|
[VertexMember(2, VertexAttribPointerType.Float)]
|
||||||
public Vector2 TexturePosition;
|
public Vector2 TexturePosition;
|
||||||
|
|
||||||
|
[VertexMember(4, VertexAttribPointerType.Float)]
|
||||||
|
public Vector4 TextureRect;
|
||||||
|
|
||||||
[VertexMember(1, VertexAttribPointerType.Float)]
|
[VertexMember(1, VertexAttribPointerType.Float)]
|
||||||
public float Time;
|
public float Time;
|
||||||
|
|
||||||
|
17
osu.Game.Rulesets.Taiko.Tests/TestSceneEditor.cs
Normal file
17
osu.Game.Rulesets.Taiko.Tests/TestSceneEditor.cs
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Game.Tests.Visual;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Taiko.Tests
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class TestSceneEditor : EditorTestScene
|
||||||
|
{
|
||||||
|
public TestSceneEditor()
|
||||||
|
: base(new TaikoRuleset())
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,55 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Game.Rulesets.Edit;
|
||||||
|
using osu.Game.Rulesets.Taiko.Beatmaps;
|
||||||
|
using osu.Game.Rulesets.Taiko.Edit;
|
||||||
|
using osu.Game.Rulesets.Taiko.Objects;
|
||||||
|
using osu.Game.Screens.Edit;
|
||||||
|
using osu.Game.Tests.Visual;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Taiko.Tests
|
||||||
|
{
|
||||||
|
public class TestSceneTaikoHitObjectComposer : EditorClockTestScene
|
||||||
|
{
|
||||||
|
[SetUp]
|
||||||
|
public void Setup() => Schedule(() =>
|
||||||
|
{
|
||||||
|
BeatDivisor.Value = 8;
|
||||||
|
Clock.Seek(0);
|
||||||
|
|
||||||
|
Child = new TestComposer { RelativeSizeAxes = Axes.Both };
|
||||||
|
});
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void BasicTest()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TestComposer : CompositeDrawable
|
||||||
|
{
|
||||||
|
[Cached(typeof(EditorBeatmap))]
|
||||||
|
[Cached(typeof(IBeatSnapProvider))]
|
||||||
|
public readonly EditorBeatmap EditorBeatmap;
|
||||||
|
|
||||||
|
public TestComposer()
|
||||||
|
{
|
||||||
|
InternalChildren = new Drawable[]
|
||||||
|
{
|
||||||
|
EditorBeatmap = new EditorBeatmap(new TaikoBeatmap())
|
||||||
|
{
|
||||||
|
BeatmapInfo = { Ruleset = new TaikoRuleset().RulesetInfo }
|
||||||
|
},
|
||||||
|
new TaikoHitObjectComposer(new TaikoRuleset())
|
||||||
|
};
|
||||||
|
|
||||||
|
for (int i = 0; i < 10; i++)
|
||||||
|
EditorBeatmap.Add(new Hit { StartTime = 125 * i });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,15 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Game.Rulesets.Taiko.Objects;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Taiko.Edit.Blueprints
|
||||||
|
{
|
||||||
|
public class DrumRollPlacementBlueprint : TaikoSpanPlacementBlueprint
|
||||||
|
{
|
||||||
|
public DrumRollPlacementBlueprint()
|
||||||
|
: base(new DrumRoll())
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
35
osu.Game.Rulesets.Taiko/Edit/Blueprints/HitPiece.cs
Normal file
35
osu.Game.Rulesets.Taiko/Edit/Blueprints/HitPiece.cs
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Taiko.Edit.Blueprints
|
||||||
|
{
|
||||||
|
public class HitPiece : CompositeDrawable
|
||||||
|
{
|
||||||
|
public HitPiece()
|
||||||
|
{
|
||||||
|
Origin = Anchor.Centre;
|
||||||
|
|
||||||
|
InternalChild = new CircularContainer
|
||||||
|
{
|
||||||
|
Masking = true,
|
||||||
|
BorderThickness = 10,
|
||||||
|
BorderColour = Color4.Yellow,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new Box
|
||||||
|
{
|
||||||
|
AlwaysPresent = true,
|
||||||
|
Alpha = 0,
|
||||||
|
RelativeSizeAxes = Axes.Both
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,52 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Input.Events;
|
||||||
|
using osu.Game.Rulesets.Edit;
|
||||||
|
using osu.Game.Rulesets.Taiko.Objects;
|
||||||
|
using osu.Game.Rulesets.Taiko.UI;
|
||||||
|
using osuTK;
|
||||||
|
using osuTK.Input;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Taiko.Edit.Blueprints
|
||||||
|
{
|
||||||
|
public class HitPlacementBlueprint : PlacementBlueprint
|
||||||
|
{
|
||||||
|
private readonly HitPiece piece;
|
||||||
|
|
||||||
|
private static Hit hit;
|
||||||
|
|
||||||
|
public HitPlacementBlueprint()
|
||||||
|
: base(hit = new Hit())
|
||||||
|
{
|
||||||
|
InternalChild = piece = new HitPiece
|
||||||
|
{
|
||||||
|
Size = new Vector2(TaikoHitObject.DEFAULT_SIZE * TaikoPlayfield.DEFAULT_HEIGHT)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool OnMouseDown(MouseDownEvent e)
|
||||||
|
{
|
||||||
|
switch (e.Button)
|
||||||
|
{
|
||||||
|
case MouseButton.Left:
|
||||||
|
hit.Type = HitType.Centre;
|
||||||
|
EndPlacement(true);
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case MouseButton.Right:
|
||||||
|
hit.Type = HitType.Rim;
|
||||||
|
EndPlacement(true);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void UpdatePosition(SnapResult result)
|
||||||
|
{
|
||||||
|
piece.Position = ToLocalSpace(result.ScreenSpacePosition);
|
||||||
|
base.UpdatePosition(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
40
osu.Game.Rulesets.Taiko/Edit/Blueprints/LengthPiece.cs
Normal file
40
osu.Game.Rulesets.Taiko/Edit/Blueprints/LengthPiece.cs
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Taiko.Edit.Blueprints
|
||||||
|
{
|
||||||
|
public class LengthPiece : CompositeDrawable
|
||||||
|
{
|
||||||
|
public LengthPiece()
|
||||||
|
{
|
||||||
|
Origin = Anchor.CentreLeft;
|
||||||
|
|
||||||
|
InternalChild = new Container
|
||||||
|
{
|
||||||
|
Masking = true,
|
||||||
|
Colour = Color4.Yellow,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
Height = 8,
|
||||||
|
},
|
||||||
|
new Box
|
||||||
|
{
|
||||||
|
Origin = Anchor.BottomLeft,
|
||||||
|
Anchor = Anchor.BottomLeft,
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
Height = 8,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,15 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Game.Rulesets.Taiko.Objects;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Taiko.Edit.Blueprints
|
||||||
|
{
|
||||||
|
public class SwellPlacementBlueprint : TaikoSpanPlacementBlueprint
|
||||||
|
{
|
||||||
|
public SwellPlacementBlueprint()
|
||||||
|
: base(new Swell())
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,40 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Game.Rulesets.Edit;
|
||||||
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Taiko.Edit.Blueprints
|
||||||
|
{
|
||||||
|
public class TaikoSelectionBlueprint : OverlaySelectionBlueprint
|
||||||
|
{
|
||||||
|
public TaikoSelectionBlueprint(DrawableHitObject hitObject)
|
||||||
|
: base(hitObject)
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.None;
|
||||||
|
|
||||||
|
AddInternal(new HitPiece
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Origin = Anchor.TopLeft
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Update()
|
||||||
|
{
|
||||||
|
base.Update();
|
||||||
|
|
||||||
|
// Move the rectangle to cover the hitobjects
|
||||||
|
var topLeft = new Vector2(float.MaxValue, float.MaxValue);
|
||||||
|
var bottomRight = new Vector2(float.MinValue, float.MinValue);
|
||||||
|
|
||||||
|
topLeft = Vector2.ComponentMin(topLeft, Parent.ToLocalSpace(DrawableObject.ScreenSpaceDrawQuad.TopLeft));
|
||||||
|
bottomRight = Vector2.ComponentMax(bottomRight, Parent.ToLocalSpace(DrawableObject.ScreenSpaceDrawQuad.BottomRight));
|
||||||
|
|
||||||
|
Size = bottomRight - topLeft;
|
||||||
|
Position = topLeft;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,110 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Input.Events;
|
||||||
|
using osu.Game.Rulesets.Edit;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
|
using osu.Game.Rulesets.Taiko.Objects;
|
||||||
|
using osu.Game.Rulesets.Taiko.UI;
|
||||||
|
using osuTK;
|
||||||
|
using osuTK.Input;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Taiko.Edit.Blueprints
|
||||||
|
{
|
||||||
|
public class TaikoSpanPlacementBlueprint : PlacementBlueprint
|
||||||
|
{
|
||||||
|
private readonly HitPiece headPiece;
|
||||||
|
private readonly HitPiece tailPiece;
|
||||||
|
|
||||||
|
private readonly LengthPiece lengthPiece;
|
||||||
|
|
||||||
|
private readonly IHasDuration spanPlacementObject;
|
||||||
|
|
||||||
|
public TaikoSpanPlacementBlueprint(HitObject hitObject)
|
||||||
|
: base(hitObject)
|
||||||
|
{
|
||||||
|
spanPlacementObject = hitObject as IHasDuration;
|
||||||
|
|
||||||
|
RelativeSizeAxes = Axes.Both;
|
||||||
|
|
||||||
|
InternalChildren = new Drawable[]
|
||||||
|
{
|
||||||
|
headPiece = new HitPiece
|
||||||
|
{
|
||||||
|
Size = new Vector2(TaikoHitObject.DEFAULT_SIZE * TaikoPlayfield.DEFAULT_HEIGHT)
|
||||||
|
},
|
||||||
|
lengthPiece = new LengthPiece
|
||||||
|
{
|
||||||
|
Height = TaikoHitObject.DEFAULT_SIZE * TaikoPlayfield.DEFAULT_HEIGHT
|
||||||
|
},
|
||||||
|
tailPiece = new HitPiece
|
||||||
|
{
|
||||||
|
Size = new Vector2(TaikoHitObject.DEFAULT_SIZE * TaikoPlayfield.DEFAULT_HEIGHT)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private double originalStartTime;
|
||||||
|
private Vector2 originalPosition;
|
||||||
|
|
||||||
|
protected override bool OnMouseDown(MouseDownEvent e)
|
||||||
|
{
|
||||||
|
if (e.Button != MouseButton.Left)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
BeginPlacement(true);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnMouseUp(MouseUpEvent e)
|
||||||
|
{
|
||||||
|
if (e.Button != MouseButton.Left)
|
||||||
|
return;
|
||||||
|
|
||||||
|
base.OnMouseUp(e);
|
||||||
|
EndPlacement(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void UpdatePosition(SnapResult result)
|
||||||
|
{
|
||||||
|
base.UpdatePosition(result);
|
||||||
|
|
||||||
|
if (PlacementActive)
|
||||||
|
{
|
||||||
|
if (result.Time is double dragTime)
|
||||||
|
{
|
||||||
|
if (dragTime < originalStartTime)
|
||||||
|
{
|
||||||
|
HitObject.StartTime = dragTime;
|
||||||
|
spanPlacementObject.Duration = Math.Abs(dragTime - originalStartTime);
|
||||||
|
headPiece.Position = ToLocalSpace(result.ScreenSpacePosition);
|
||||||
|
tailPiece.Position = originalPosition;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
HitObject.StartTime = originalStartTime;
|
||||||
|
spanPlacementObject.Duration = Math.Abs(dragTime - originalStartTime);
|
||||||
|
tailPiece.Position = ToLocalSpace(result.ScreenSpacePosition);
|
||||||
|
headPiece.Position = originalPosition;
|
||||||
|
}
|
||||||
|
|
||||||
|
lengthPiece.X = headPiece.X;
|
||||||
|
lengthPiece.Width = tailPiece.X - headPiece.X;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
lengthPiece.Position = headPiece.Position = tailPiece.Position = ToLocalSpace(result.ScreenSpacePosition);
|
||||||
|
|
||||||
|
if (result.Time is double startTime)
|
||||||
|
{
|
||||||
|
originalStartTime = HitObject.StartTime = startTime;
|
||||||
|
originalPosition = ToLocalSpace(result.ScreenSpacePosition);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
20
osu.Game.Rulesets.Taiko/Edit/DrumRollCompositionTool.cs
Normal file
20
osu.Game.Rulesets.Taiko/Edit/DrumRollCompositionTool.cs
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Game.Rulesets.Edit;
|
||||||
|
using osu.Game.Rulesets.Edit.Tools;
|
||||||
|
using osu.Game.Rulesets.Taiko.Edit.Blueprints;
|
||||||
|
using osu.Game.Rulesets.Taiko.Objects;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Taiko.Edit
|
||||||
|
{
|
||||||
|
public class DrumRollCompositionTool : HitObjectCompositionTool
|
||||||
|
{
|
||||||
|
public DrumRollCompositionTool()
|
||||||
|
: base(nameof(DrumRoll))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public override PlacementBlueprint CreatePlacementBlueprint() => new DrumRollPlacementBlueprint();
|
||||||
|
}
|
||||||
|
}
|
20
osu.Game.Rulesets.Taiko/Edit/HitCompositionTool.cs
Normal file
20
osu.Game.Rulesets.Taiko/Edit/HitCompositionTool.cs
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Game.Rulesets.Edit;
|
||||||
|
using osu.Game.Rulesets.Edit.Tools;
|
||||||
|
using osu.Game.Rulesets.Taiko.Edit.Blueprints;
|
||||||
|
using osu.Game.Rulesets.Taiko.Objects;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Taiko.Edit
|
||||||
|
{
|
||||||
|
public class HitCompositionTool : HitObjectCompositionTool
|
||||||
|
{
|
||||||
|
public HitCompositionTool()
|
||||||
|
: base(nameof(Hit))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public override PlacementBlueprint CreatePlacementBlueprint() => new HitPlacementBlueprint();
|
||||||
|
}
|
||||||
|
}
|
20
osu.Game.Rulesets.Taiko/Edit/SwellCompositionTool.cs
Normal file
20
osu.Game.Rulesets.Taiko/Edit/SwellCompositionTool.cs
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Game.Rulesets.Edit;
|
||||||
|
using osu.Game.Rulesets.Edit.Tools;
|
||||||
|
using osu.Game.Rulesets.Taiko.Edit.Blueprints;
|
||||||
|
using osu.Game.Rulesets.Taiko.Objects;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Taiko.Edit
|
||||||
|
{
|
||||||
|
public class SwellCompositionTool : HitObjectCompositionTool
|
||||||
|
{
|
||||||
|
public SwellCompositionTool()
|
||||||
|
: base(nameof(Swell))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public override PlacementBlueprint CreatePlacementBlueprint() => new SwellPlacementBlueprint();
|
||||||
|
}
|
||||||
|
}
|
24
osu.Game.Rulesets.Taiko/Edit/TaikoBlueprintContainer.cs
Normal file
24
osu.Game.Rulesets.Taiko/Edit/TaikoBlueprintContainer.cs
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using osu.Game.Rulesets.Edit;
|
||||||
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
|
using osu.Game.Rulesets.Taiko.Edit.Blueprints;
|
||||||
|
using osu.Game.Screens.Edit.Compose.Components;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Taiko.Edit
|
||||||
|
{
|
||||||
|
public class TaikoBlueprintContainer : ComposeBlueprintContainer
|
||||||
|
{
|
||||||
|
public TaikoBlueprintContainer(IEnumerable<DrawableHitObject> hitObjects)
|
||||||
|
: base(hitObjects)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override SelectionHandler CreateSelectionHandler() => new TaikoSelectionHandler();
|
||||||
|
|
||||||
|
public override OverlaySelectionBlueprint CreateBlueprintFor(DrawableHitObject hitObject) =>
|
||||||
|
new TaikoSelectionBlueprint(hitObject);
|
||||||
|
}
|
||||||
|
}
|
30
osu.Game.Rulesets.Taiko/Edit/TaikoHitObjectComposer.cs
Normal file
30
osu.Game.Rulesets.Taiko/Edit/TaikoHitObjectComposer.cs
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using osu.Game.Rulesets.Edit;
|
||||||
|
using osu.Game.Rulesets.Edit.Tools;
|
||||||
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
|
using osu.Game.Rulesets.Taiko.Objects;
|
||||||
|
using osu.Game.Screens.Edit.Compose.Components;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Taiko.Edit
|
||||||
|
{
|
||||||
|
public class TaikoHitObjectComposer : HitObjectComposer<TaikoHitObject>
|
||||||
|
{
|
||||||
|
public TaikoHitObjectComposer(TaikoRuleset ruleset)
|
||||||
|
: base(ruleset)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override IReadOnlyList<HitObjectCompositionTool> CompositionTools => new HitObjectCompositionTool[]
|
||||||
|
{
|
||||||
|
new HitCompositionTool(),
|
||||||
|
new DrumRollCompositionTool(),
|
||||||
|
new SwellCompositionTool()
|
||||||
|
};
|
||||||
|
|
||||||
|
protected override ComposeBlueprintContainer CreateBlueprintContainer(IEnumerable<DrawableHitObject> hitObjects)
|
||||||
|
=> new TaikoBlueprintContainer(hitObjects);
|
||||||
|
}
|
||||||
|
}
|
80
osu.Game.Rulesets.Taiko/Edit/TaikoSelectionHandler.cs
Normal file
80
osu.Game.Rulesets.Taiko/Edit/TaikoSelectionHandler.cs
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using osu.Framework.Graphics.UserInterface;
|
||||||
|
using osu.Game.Graphics.UserInterface;
|
||||||
|
using osu.Game.Rulesets.Edit;
|
||||||
|
using osu.Game.Rulesets.Taiko.Objects;
|
||||||
|
using osu.Game.Screens.Edit.Compose.Components;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Taiko.Edit
|
||||||
|
{
|
||||||
|
public class TaikoSelectionHandler : SelectionHandler
|
||||||
|
{
|
||||||
|
protected override IEnumerable<MenuItem> GetContextMenuItemsForSelection(IEnumerable<SelectionBlueprint> selection)
|
||||||
|
{
|
||||||
|
if (selection.All(s => s.HitObject is Hit))
|
||||||
|
{
|
||||||
|
var hits = selection.Select(s => s.HitObject).OfType<Hit>();
|
||||||
|
|
||||||
|
yield return new TernaryStateMenuItem("Rim", action: state =>
|
||||||
|
{
|
||||||
|
foreach (var h in hits)
|
||||||
|
{
|
||||||
|
switch (state)
|
||||||
|
{
|
||||||
|
case TernaryState.True:
|
||||||
|
h.Type = HitType.Rim;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TernaryState.False:
|
||||||
|
h.Type = HitType.Centre;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
{
|
||||||
|
State = { Value = getTernaryState(hits, h => h.Type == HitType.Rim) }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selection.All(s => s.HitObject is TaikoHitObject))
|
||||||
|
{
|
||||||
|
var hits = selection.Select(s => s.HitObject).OfType<TaikoHitObject>();
|
||||||
|
|
||||||
|
yield return new TernaryStateMenuItem("Strong", action: state =>
|
||||||
|
{
|
||||||
|
foreach (var h in hits)
|
||||||
|
{
|
||||||
|
switch (state)
|
||||||
|
{
|
||||||
|
case TernaryState.True:
|
||||||
|
h.IsStrong = true;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TernaryState.False:
|
||||||
|
h.IsStrong = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
EditorBeatmap?.UpdateHitObject(h);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
{
|
||||||
|
State = { Value = getTernaryState(hits, h => h.IsStrong) }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private TernaryState getTernaryState<T>(IEnumerable<T> selection, Func<T, bool> func)
|
||||||
|
{
|
||||||
|
if (selection.Any(func))
|
||||||
|
return selection.All(func) ? TernaryState.True : TernaryState.Indeterminate;
|
||||||
|
|
||||||
|
return TernaryState.False;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -48,12 +48,11 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
|||||||
colourIdle = colours.YellowDark;
|
colourIdle = colours.YellowDark;
|
||||||
colourEngaged = colours.YellowDarker;
|
colourEngaged = colours.YellowDarker;
|
||||||
|
|
||||||
updateColour();
|
Content.Add(tickContainer = new Container
|
||||||
|
{
|
||||||
Content.Add(tickContainer = new Container { RelativeSizeAxes = Axes.Both });
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Depth = float.MinValue
|
||||||
if (MainPiece.Drawable is IHasAccentColour accentMain)
|
});
|
||||||
accentMain.AccentColour = colourIdle;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void LoadComplete()
|
protected override void LoadComplete()
|
||||||
@ -63,6 +62,12 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
|||||||
OnNewResult += onNewResult;
|
OnNewResult += onNewResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void RecreatePieces()
|
||||||
|
{
|
||||||
|
base.RecreatePieces();
|
||||||
|
updateColour();
|
||||||
|
}
|
||||||
|
|
||||||
protected override void AddNestedHitObject(DrawableHitObject hitObject)
|
protected override void AddNestedHitObject(DrawableHitObject hitObject)
|
||||||
{
|
{
|
||||||
base.AddNestedHitObject(hitObject);
|
base.AddNestedHitObject(hitObject);
|
||||||
|
@ -5,6 +5,8 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Game.Audio;
|
using osu.Game.Audio;
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
@ -19,7 +21,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// A list of keys which can result in hits for this HitObject.
|
/// A list of keys which can result in hits for this HitObject.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public TaikoAction[] HitActions { get; }
|
public TaikoAction[] HitActions { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The action that caused this <see cref="DrawableHit"/> to be hit.
|
/// The action that caused this <see cref="DrawableHit"/> to be hit.
|
||||||
@ -34,15 +36,35 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
|||||||
|
|
||||||
private bool pressHandledThisFrame;
|
private bool pressHandledThisFrame;
|
||||||
|
|
||||||
|
private Bindable<HitType> type;
|
||||||
|
|
||||||
public DrawableHit(Hit hit)
|
public DrawableHit(Hit hit)
|
||||||
: base(hit)
|
: base(hit)
|
||||||
{
|
{
|
||||||
FillMode = FillMode.Fit;
|
FillMode = FillMode.Fit;
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
type = HitObject.TypeBindable.GetBoundCopy();
|
||||||
|
type.BindValueChanged(_ =>
|
||||||
|
{
|
||||||
|
updateType();
|
||||||
|
RecreatePieces();
|
||||||
|
});
|
||||||
|
|
||||||
|
updateType();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateType()
|
||||||
|
{
|
||||||
HitActions =
|
HitActions =
|
||||||
HitObject.Type == HitType.Centre
|
HitObject.Type == HitType.Centre
|
||||||
? new[] { TaikoAction.LeftCentre, TaikoAction.RightCentre }
|
? new[] { TaikoAction.LeftCentre, TaikoAction.RightCentre }
|
||||||
: new[] { TaikoAction.LeftRim, TaikoAction.RightRim };
|
: new[] { TaikoAction.LeftRim, TaikoAction.RightRim };
|
||||||
|
|
||||||
|
RecreatePieces();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override SkinnableDrawable CreateMainPiece() => HitObject.Type == HitType.Centre
|
protected override SkinnableDrawable CreateMainPiece() => HitObject.Type == HitType.Centre
|
||||||
|
@ -8,6 +8,8 @@ using osuTK;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Game.Audio;
|
using osu.Game.Audio;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Primitives;
|
using osu.Framework.Graphics.Primitives;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
@ -115,8 +117,10 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
|||||||
|
|
||||||
public new TObject HitObject;
|
public new TObject HitObject;
|
||||||
|
|
||||||
protected readonly Vector2 BaseSize;
|
protected Vector2 BaseSize;
|
||||||
protected readonly SkinnableDrawable MainPiece;
|
protected SkinnableDrawable MainPiece;
|
||||||
|
|
||||||
|
private Bindable<bool> isStrong;
|
||||||
|
|
||||||
private readonly Container<DrawableStrongNestedHit> strongHitContainer;
|
private readonly Container<DrawableStrongNestedHit> strongHitContainer;
|
||||||
|
|
||||||
@ -129,13 +133,25 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
|||||||
Origin = Anchor.Custom;
|
Origin = Anchor.Custom;
|
||||||
|
|
||||||
RelativeSizeAxes = Axes.Both;
|
RelativeSizeAxes = Axes.Both;
|
||||||
Size = BaseSize = new Vector2(HitObject.IsStrong ? TaikoHitObject.DEFAULT_STRONG_SIZE : TaikoHitObject.DEFAULT_SIZE);
|
|
||||||
|
|
||||||
Content.Add(MainPiece = CreateMainPiece());
|
|
||||||
|
|
||||||
AddInternal(strongHitContainer = new Container<DrawableStrongNestedHit>());
|
AddInternal(strongHitContainer = new Container<DrawableStrongNestedHit>());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
isStrong = HitObject.IsStrongBindable.GetBoundCopy();
|
||||||
|
isStrong.BindValueChanged(_ => RecreatePieces(), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void RecreatePieces()
|
||||||
|
{
|
||||||
|
Size = BaseSize = new Vector2(HitObject.IsStrong ? TaikoHitObject.DEFAULT_STRONG_SIZE : TaikoHitObject.DEFAULT_SIZE);
|
||||||
|
|
||||||
|
MainPiece?.Expire();
|
||||||
|
Content.Add(MainPiece = CreateMainPiece());
|
||||||
|
}
|
||||||
|
|
||||||
protected override void AddNestedHitObject(DrawableHitObject hitObject)
|
protected override void AddNestedHitObject(DrawableHitObject hitObject)
|
||||||
{
|
{
|
||||||
base.AddNestedHitObject(hitObject);
|
base.AddNestedHitObject(hitObject);
|
||||||
|
@ -1,13 +1,21 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Taiko.Objects
|
namespace osu.Game.Rulesets.Taiko.Objects
|
||||||
{
|
{
|
||||||
public class Hit : TaikoHitObject
|
public class Hit : TaikoHitObject
|
||||||
{
|
{
|
||||||
|
public readonly Bindable<HitType> TypeBindable = new Bindable<HitType>();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The <see cref="HitType"/> that actuates this <see cref="Hit"/>.
|
/// The <see cref="HitType"/> that actuates this <see cref="Hit"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public HitType Type { get; set; }
|
public HitType Type
|
||||||
|
{
|
||||||
|
get => TypeBindable.Value;
|
||||||
|
set => TypeBindable.Value = value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using osu.Game.Rulesets.Objects.Types;
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
using osu.Game.Rulesets.Judgements;
|
using osu.Game.Rulesets.Judgements;
|
||||||
@ -25,11 +24,6 @@ namespace osu.Game.Rulesets.Taiko.Objects
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public int RequiredHits = 10;
|
public int RequiredHits = 10;
|
||||||
|
|
||||||
public override bool IsStrong
|
|
||||||
{
|
|
||||||
set => throw new NotSupportedException($"{nameof(Swell)} cannot be a strong hitobject.");
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void CreateNestedHitObjects(CancellationToken cancellationToken)
|
protected override void CreateNestedHitObjects(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
base.CreateNestedHitObjects(cancellationToken);
|
base.CreateNestedHitObjects(cancellationToken);
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
using osu.Game.Rulesets.Judgements;
|
using osu.Game.Rulesets.Judgements;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
@ -27,11 +28,17 @@ namespace osu.Game.Rulesets.Taiko.Objects
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public const float DEFAULT_STRONG_SIZE = DEFAULT_SIZE * STRONG_SCALE;
|
public const float DEFAULT_STRONG_SIZE = DEFAULT_SIZE * STRONG_SCALE;
|
||||||
|
|
||||||
|
public readonly Bindable<bool> IsStrongBindable = new BindableBool();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether this HitObject is a "strong" type.
|
/// Whether this HitObject is a "strong" type.
|
||||||
/// Strong hit objects give more points for hitting the hit object with both keys.
|
/// Strong hit objects give more points for hitting the hit object with both keys.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual bool IsStrong { get; set; }
|
public bool IsStrong
|
||||||
|
{
|
||||||
|
get => IsStrongBindable.Value;
|
||||||
|
set => IsStrongBindable.Value = value;
|
||||||
|
}
|
||||||
|
|
||||||
protected override void CreateNestedHitObjects(CancellationToken cancellationToken)
|
protected override void CreateNestedHitObjects(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
@ -21,6 +21,8 @@ using osu.Game.Rulesets.Taiko.Difficulty;
|
|||||||
using osu.Game.Rulesets.Taiko.Scoring;
|
using osu.Game.Rulesets.Taiko.Scoring;
|
||||||
using osu.Game.Scoring;
|
using osu.Game.Scoring;
|
||||||
using System;
|
using System;
|
||||||
|
using osu.Game.Rulesets.Edit;
|
||||||
|
using osu.Game.Rulesets.Taiko.Edit;
|
||||||
using osu.Game.Rulesets.Taiko.Skinning;
|
using osu.Game.Rulesets.Taiko.Skinning;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
|
|
||||||
@ -144,6 +146,8 @@ namespace osu.Game.Rulesets.Taiko
|
|||||||
|
|
||||||
public override Drawable CreateIcon() => new SpriteIcon { Icon = OsuIcon.RulesetTaiko };
|
public override Drawable CreateIcon() => new SpriteIcon { Icon = OsuIcon.RulesetTaiko };
|
||||||
|
|
||||||
|
public override HitObjectComposer CreateHitObjectComposer() => new TaikoHitObjectComposer(this);
|
||||||
|
|
||||||
public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new TaikoDifficultyCalculator(this, beatmap);
|
public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new TaikoDifficultyCalculator(this, beatmap);
|
||||||
|
|
||||||
public override PerformanceCalculator CreatePerformanceCalculator(WorkingBeatmap beatmap, ScoreInfo score) => new TaikoPerformanceCalculator(this, beatmap, score);
|
public override PerformanceCalculator CreatePerformanceCalculator(WorkingBeatmap beatmap, ScoreInfo score) => new TaikoPerformanceCalculator(this, beatmap, score);
|
||||||
|
@ -5,16 +5,15 @@ using System;
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Platform;
|
using osu.Framework.Platform;
|
||||||
using osu.Game.IPC;
|
using osu.Game.IPC;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Extensions;
|
||||||
using osu.Framework.Logging;
|
using osu.Framework.Logging;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Beatmaps.Formats;
|
|
||||||
using osu.Game.IO;
|
using osu.Game.IO;
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
using osu.Game.Tests.Resources;
|
using osu.Game.Tests.Resources;
|
||||||
@ -22,6 +21,7 @@ using SharpCompress.Archives;
|
|||||||
using SharpCompress.Archives.Zip;
|
using SharpCompress.Archives.Zip;
|
||||||
using SharpCompress.Common;
|
using SharpCompress.Common;
|
||||||
using SharpCompress.Writers.Zip;
|
using SharpCompress.Writers.Zip;
|
||||||
|
using FileInfo = System.IO.FileInfo;
|
||||||
|
|
||||||
namespace osu.Game.Tests.Beatmaps.IO
|
namespace osu.Game.Tests.Beatmaps.IO
|
||||||
{
|
{
|
||||||
@ -93,6 +93,166 @@ namespace osu.Game.Tests.Beatmaps.IO
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public async Task TestImportThenImportWithReZip()
|
||||||
|
{
|
||||||
|
using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestImportThenImportWithReZip)))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var osu = loadOsu(host);
|
||||||
|
|
||||||
|
var temp = TestResources.GetTestBeatmapForImport();
|
||||||
|
|
||||||
|
string extractedFolder = $"{temp}_extracted";
|
||||||
|
Directory.CreateDirectory(extractedFolder);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var imported = await LoadOszIntoOsu(osu);
|
||||||
|
|
||||||
|
string hashBefore = hashFile(temp);
|
||||||
|
|
||||||
|
using (var zip = ZipArchive.Open(temp))
|
||||||
|
zip.WriteToDirectory(extractedFolder);
|
||||||
|
|
||||||
|
using (var zip = ZipArchive.Create())
|
||||||
|
{
|
||||||
|
zip.AddAllFromDirectory(extractedFolder);
|
||||||
|
zip.SaveTo(temp, new ZipWriterOptions(CompressionType.Deflate));
|
||||||
|
}
|
||||||
|
|
||||||
|
// zip files differ because different compression or encoder.
|
||||||
|
Assert.AreNotEqual(hashBefore, hashFile(temp));
|
||||||
|
|
||||||
|
var importedSecondTime = await osu.Dependencies.Get<BeatmapManager>().Import(temp);
|
||||||
|
|
||||||
|
ensureLoaded(osu);
|
||||||
|
|
||||||
|
// but contents doesn't, so existing should still be used.
|
||||||
|
Assert.IsTrue(imported.ID == importedSecondTime.ID);
|
||||||
|
Assert.IsTrue(imported.Beatmaps.First().ID == importedSecondTime.Beatmaps.First().ID);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Directory.Delete(extractedFolder, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
host.Exit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private string hashFile(string filename)
|
||||||
|
{
|
||||||
|
using (var s = File.OpenRead(filename))
|
||||||
|
return s.ComputeMD5Hash();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public async Task TestImportThenImportWithChangedFile()
|
||||||
|
{
|
||||||
|
using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestImportThenImportWithChangedFile)))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var osu = loadOsu(host);
|
||||||
|
|
||||||
|
var temp = TestResources.GetTestBeatmapForImport();
|
||||||
|
|
||||||
|
string extractedFolder = $"{temp}_extracted";
|
||||||
|
Directory.CreateDirectory(extractedFolder);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var imported = await LoadOszIntoOsu(osu);
|
||||||
|
|
||||||
|
using (var zip = ZipArchive.Open(temp))
|
||||||
|
zip.WriteToDirectory(extractedFolder);
|
||||||
|
|
||||||
|
// arbitrary write to non-hashed file
|
||||||
|
using (var sw = new FileInfo(Directory.GetFiles(extractedFolder, "*.mp3").First()).AppendText())
|
||||||
|
await sw.WriteLineAsync("text");
|
||||||
|
|
||||||
|
using (var zip = ZipArchive.Create())
|
||||||
|
{
|
||||||
|
zip.AddAllFromDirectory(extractedFolder);
|
||||||
|
zip.SaveTo(temp, new ZipWriterOptions(CompressionType.Deflate));
|
||||||
|
}
|
||||||
|
|
||||||
|
var importedSecondTime = await osu.Dependencies.Get<BeatmapManager>().Import(temp);
|
||||||
|
|
||||||
|
ensureLoaded(osu);
|
||||||
|
|
||||||
|
// check the newly "imported" beatmap is not the original.
|
||||||
|
Assert.IsTrue(imported.ID != importedSecondTime.ID);
|
||||||
|
Assert.IsTrue(imported.Beatmaps.First().ID != importedSecondTime.Beatmaps.First().ID);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Directory.Delete(extractedFolder, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
host.Exit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public async Task TestImportThenImportWithDifferentFilename()
|
||||||
|
{
|
||||||
|
using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestImportThenImportWithDifferentFilename)))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var osu = loadOsu(host);
|
||||||
|
|
||||||
|
var temp = TestResources.GetTestBeatmapForImport();
|
||||||
|
|
||||||
|
string extractedFolder = $"{temp}_extracted";
|
||||||
|
Directory.CreateDirectory(extractedFolder);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var imported = await LoadOszIntoOsu(osu);
|
||||||
|
|
||||||
|
using (var zip = ZipArchive.Open(temp))
|
||||||
|
zip.WriteToDirectory(extractedFolder);
|
||||||
|
|
||||||
|
// change filename
|
||||||
|
var firstFile = new FileInfo(Directory.GetFiles(extractedFolder).First());
|
||||||
|
firstFile.MoveTo(Path.Combine(firstFile.DirectoryName, $"{firstFile.Name}-changed{firstFile.Extension}"));
|
||||||
|
|
||||||
|
using (var zip = ZipArchive.Create())
|
||||||
|
{
|
||||||
|
zip.AddAllFromDirectory(extractedFolder);
|
||||||
|
zip.SaveTo(temp, new ZipWriterOptions(CompressionType.Deflate));
|
||||||
|
}
|
||||||
|
|
||||||
|
var importedSecondTime = await osu.Dependencies.Get<BeatmapManager>().Import(temp);
|
||||||
|
|
||||||
|
ensureLoaded(osu);
|
||||||
|
|
||||||
|
// check the newly "imported" beatmap is not the original.
|
||||||
|
Assert.IsTrue(imported.ID != importedSecondTime.ID);
|
||||||
|
Assert.IsTrue(imported.Beatmaps.First().ID != importedSecondTime.Beatmaps.First().ID);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Directory.Delete(extractedFolder, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
host.Exit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public async Task TestImportCorruptThenImport()
|
public async Task TestImportCorruptThenImport()
|
||||||
{
|
{
|
||||||
@ -175,7 +335,7 @@ namespace osu.Game.Tests.Beatmaps.IO
|
|||||||
var breakTemp = TestResources.GetTestBeatmapForImport();
|
var breakTemp = TestResources.GetTestBeatmapForImport();
|
||||||
|
|
||||||
MemoryStream brokenOsu = new MemoryStream();
|
MemoryStream brokenOsu = new MemoryStream();
|
||||||
MemoryStream brokenOsz = new MemoryStream(File.ReadAllBytes(breakTemp));
|
MemoryStream brokenOsz = new MemoryStream(await File.ReadAllBytesAsync(breakTemp));
|
||||||
|
|
||||||
File.Delete(breakTemp);
|
File.Delete(breakTemp);
|
||||||
|
|
||||||
@ -212,37 +372,6 @@ namespace osu.Game.Tests.Beatmaps.IO
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
|
||||||
public async Task TestImportThenImportDifferentHash()
|
|
||||||
{
|
|
||||||
// unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
|
|
||||||
using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestImportThenImportDifferentHash)))
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var osu = loadOsu(host);
|
|
||||||
var manager = osu.Dependencies.Get<BeatmapManager>();
|
|
||||||
|
|
||||||
var imported = await LoadOszIntoOsu(osu);
|
|
||||||
|
|
||||||
imported.Hash += "-changed";
|
|
||||||
manager.Update(imported);
|
|
||||||
|
|
||||||
var importedSecondTime = await LoadOszIntoOsu(osu);
|
|
||||||
|
|
||||||
Assert.IsTrue(imported.ID != importedSecondTime.ID);
|
|
||||||
Assert.IsTrue(imported.Beatmaps.First().ID < importedSecondTime.Beatmaps.First().ID);
|
|
||||||
|
|
||||||
// only one beatmap will exist as the online set ID matched, causing purging of the first import.
|
|
||||||
checkBeatmapSetCount(osu, 1);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
host.Exit();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public async Task TestImportThenDeleteThenImport()
|
public async Task TestImportThenDeleteThenImport()
|
||||||
{
|
{
|
||||||
@ -522,7 +651,7 @@ namespace osu.Game.Tests.Beatmaps.IO
|
|||||||
|
|
||||||
using (var resourceForkFile = File.CreateText(resourceForkFilePath))
|
using (var resourceForkFile = File.CreateText(resourceForkFilePath))
|
||||||
{
|
{
|
||||||
resourceForkFile.WriteLine("adding content so that it's not empty");
|
await resourceForkFile.WriteLineAsync("adding content so that it's not empty");
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
@ -599,23 +728,17 @@ namespace osu.Game.Tests.Beatmaps.IO
|
|||||||
await osu.Dependencies.Get<BeatmapManager>().Import(temp);
|
await osu.Dependencies.Get<BeatmapManager>().Import(temp);
|
||||||
|
|
||||||
BeatmapSetInfo setToUpdate = manager.GetAllUsableBeatmapSets()[0];
|
BeatmapSetInfo setToUpdate = manager.GetAllUsableBeatmapSets()[0];
|
||||||
|
|
||||||
|
var beatmapInfo = setToUpdate.Beatmaps.First(b => b.RulesetID == 0);
|
||||||
Beatmap beatmapToUpdate = (Beatmap)manager.GetWorkingBeatmap(setToUpdate.Beatmaps.First(b => b.RulesetID == 0)).Beatmap;
|
Beatmap beatmapToUpdate = (Beatmap)manager.GetWorkingBeatmap(setToUpdate.Beatmaps.First(b => b.RulesetID == 0)).Beatmap;
|
||||||
BeatmapSetFileInfo fileToUpdate = setToUpdate.Files.First(f => beatmapToUpdate.BeatmapInfo.Path.Contains(f.Filename));
|
BeatmapSetFileInfo fileToUpdate = setToUpdate.Files.First(f => beatmapToUpdate.BeatmapInfo.Path.Contains(f.Filename));
|
||||||
|
|
||||||
using (var stream = new MemoryStream())
|
string oldMd5Hash = beatmapToUpdate.BeatmapInfo.MD5Hash;
|
||||||
{
|
|
||||||
using (var writer = new StreamWriter(stream, Encoding.UTF8, 1024, true))
|
|
||||||
{
|
|
||||||
beatmapToUpdate.HitObjects.Clear();
|
beatmapToUpdate.HitObjects.Clear();
|
||||||
beatmapToUpdate.HitObjects.Add(new HitCircle { StartTime = 5000 });
|
beatmapToUpdate.HitObjects.Add(new HitCircle { StartTime = 5000 });
|
||||||
|
|
||||||
new LegacyBeatmapEncoder(beatmapToUpdate).Encode(writer);
|
manager.Save(beatmapInfo, beatmapToUpdate);
|
||||||
}
|
|
||||||
|
|
||||||
stream.Seek(0, SeekOrigin.Begin);
|
|
||||||
|
|
||||||
manager.UpdateFile(setToUpdate, fileToUpdate, stream);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check that the old file reference has been removed
|
// Check that the old file reference has been removed
|
||||||
Assert.That(manager.QueryBeatmapSet(s => s.ID == setToUpdate.ID).Files.All(f => f.ID != fileToUpdate.ID));
|
Assert.That(manager.QueryBeatmapSet(s => s.ID == setToUpdate.ID).Files.All(f => f.ID != fileToUpdate.ID));
|
||||||
@ -624,6 +747,7 @@ namespace osu.Game.Tests.Beatmaps.IO
|
|||||||
Beatmap updatedBeatmap = (Beatmap)manager.GetWorkingBeatmap(manager.QueryBeatmap(b => b.ID == beatmapToUpdate.BeatmapInfo.ID)).Beatmap;
|
Beatmap updatedBeatmap = (Beatmap)manager.GetWorkingBeatmap(manager.QueryBeatmap(b => b.ID == beatmapToUpdate.BeatmapInfo.ID)).Beatmap;
|
||||||
Assert.That(updatedBeatmap.HitObjects.Count, Is.EqualTo(1));
|
Assert.That(updatedBeatmap.HitObjects.Count, Is.EqualTo(1));
|
||||||
Assert.That(updatedBeatmap.HitObjects[0].StartTime, Is.EqualTo(5000));
|
Assert.That(updatedBeatmap.HitObjects[0].StartTime, Is.EqualTo(5000));
|
||||||
|
Assert.That(updatedBeatmap.BeatmapInfo.MD5Hash, Is.Not.EqualTo(oldMd5Hash));
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
|
@ -428,22 +428,27 @@ namespace osu.Game.Tests.Chat
|
|||||||
Assert.AreEqual(5, result.Links.Count);
|
Assert.AreEqual(5, result.Links.Count);
|
||||||
|
|
||||||
Link f = result.Links.Find(l => l.Url == "https://osu.ppy.sh/wiki/wiki links");
|
Link f = result.Links.Find(l => l.Url == "https://osu.ppy.sh/wiki/wiki links");
|
||||||
|
Assert.That(f, Is.Not.Null);
|
||||||
Assert.AreEqual(44, f.Index);
|
Assert.AreEqual(44, f.Index);
|
||||||
Assert.AreEqual(10, f.Length);
|
Assert.AreEqual(10, f.Length);
|
||||||
|
|
||||||
f = result.Links.Find(l => l.Url == "http://www.simple-test.com");
|
f = result.Links.Find(l => l.Url == "http://www.simple-test.com");
|
||||||
|
Assert.That(f, Is.Not.Null);
|
||||||
Assert.AreEqual(10, f.Index);
|
Assert.AreEqual(10, f.Index);
|
||||||
Assert.AreEqual(11, f.Length);
|
Assert.AreEqual(11, f.Length);
|
||||||
|
|
||||||
f = result.Links.Find(l => l.Url == "http://google.com");
|
f = result.Links.Find(l => l.Url == "http://google.com");
|
||||||
|
Assert.That(f, Is.Not.Null);
|
||||||
Assert.AreEqual(97, f.Index);
|
Assert.AreEqual(97, f.Index);
|
||||||
Assert.AreEqual(4, f.Length);
|
Assert.AreEqual(4, f.Length);
|
||||||
|
|
||||||
f = result.Links.Find(l => l.Url == "https://osu.ppy.sh");
|
f = result.Links.Find(l => l.Url == "https://osu.ppy.sh");
|
||||||
|
Assert.That(f, Is.Not.Null);
|
||||||
Assert.AreEqual(78, f.Index);
|
Assert.AreEqual(78, f.Index);
|
||||||
Assert.AreEqual(18, f.Length);
|
Assert.AreEqual(18, f.Length);
|
||||||
|
|
||||||
f = result.Links.Find(l => l.Url == "\uD83D\uDE12");
|
f = result.Links.Find(l => l.Url == "\uD83D\uDE12");
|
||||||
|
Assert.That(f, Is.Not.Null);
|
||||||
Assert.AreEqual(101, f.Index);
|
Assert.AreEqual(101, f.Index);
|
||||||
Assert.AreEqual(3, f.Length);
|
Assert.AreEqual(3, f.Length);
|
||||||
}
|
}
|
||||||
|
@ -2,12 +2,12 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Bindables;
|
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
using osu.Framework.Timing;
|
using osu.Framework.Timing;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Beatmaps.Timing;
|
||||||
using osu.Game.Rulesets.Judgements;
|
using osu.Game.Rulesets.Judgements;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
@ -18,7 +18,6 @@ namespace osu.Game.Tests.Gameplay
|
|||||||
[HeadlessTest]
|
[HeadlessTest]
|
||||||
public class TestSceneDrainingHealthProcessor : OsuTestScene
|
public class TestSceneDrainingHealthProcessor : OsuTestScene
|
||||||
{
|
{
|
||||||
private Bindable<bool> breakTime;
|
|
||||||
private HealthProcessor processor;
|
private HealthProcessor processor;
|
||||||
private ManualClock clock;
|
private ManualClock clock;
|
||||||
|
|
||||||
@ -41,6 +40,64 @@ namespace osu.Game.Tests.Gameplay
|
|||||||
assertHealthEqualTo(1);
|
assertHealthEqualTo(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestHealthDrainBetweenBreakAndObjects()
|
||||||
|
{
|
||||||
|
createProcessor(createBeatmap(0, 2000, new BreakPeriod(325, 375)));
|
||||||
|
|
||||||
|
// 275 300 325 350 375 400 425
|
||||||
|
// hitobjects o o
|
||||||
|
// break [-------------]
|
||||||
|
// no drain [---------------------------]
|
||||||
|
|
||||||
|
setTime(285);
|
||||||
|
setHealth(1);
|
||||||
|
|
||||||
|
setTime(295);
|
||||||
|
assertHealthNotEqualTo(1);
|
||||||
|
|
||||||
|
setTime(305);
|
||||||
|
setHealth(1);
|
||||||
|
|
||||||
|
setTime(315);
|
||||||
|
assertHealthEqualTo(1);
|
||||||
|
|
||||||
|
setTime(365);
|
||||||
|
assertHealthEqualTo(1);
|
||||||
|
|
||||||
|
setTime(395);
|
||||||
|
assertHealthEqualTo(1);
|
||||||
|
|
||||||
|
setTime(425);
|
||||||
|
assertHealthNotEqualTo(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestHealthDrainDuringMaximalBreak()
|
||||||
|
{
|
||||||
|
createProcessor(createBeatmap(0, 2000, new BreakPeriod(300, 400)));
|
||||||
|
|
||||||
|
// 275 300 325 350 375 400 425
|
||||||
|
// hitobjects o o
|
||||||
|
// break [---------------------------]
|
||||||
|
// no drain [---------------------------]
|
||||||
|
|
||||||
|
setTime(285);
|
||||||
|
setHealth(1);
|
||||||
|
|
||||||
|
setTime(295);
|
||||||
|
assertHealthNotEqualTo(1);
|
||||||
|
|
||||||
|
setTime(305);
|
||||||
|
setHealth(1);
|
||||||
|
|
||||||
|
setTime(395);
|
||||||
|
assertHealthEqualTo(1);
|
||||||
|
|
||||||
|
setTime(425);
|
||||||
|
assertHealthNotEqualTo(1);
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestHealthNotDrainedAfterGameplayEnd()
|
public void TestHealthNotDrainedAfterGameplayEnd()
|
||||||
{
|
{
|
||||||
@ -54,18 +111,6 @@ namespace osu.Game.Tests.Gameplay
|
|||||||
assertHealthEqualTo(1);
|
assertHealthEqualTo(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void TestHealthNotDrainedDuringBreak()
|
|
||||||
{
|
|
||||||
createProcessor(createBeatmap(0, 2000));
|
|
||||||
setBreak(true);
|
|
||||||
|
|
||||||
setTime(700);
|
|
||||||
assertHealthEqualTo(1);
|
|
||||||
setTime(900);
|
|
||||||
assertHealthEqualTo(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestHealthDrainedDuringGameplay()
|
public void TestHealthDrainedDuringGameplay()
|
||||||
{
|
{
|
||||||
@ -112,30 +157,31 @@ namespace osu.Game.Tests.Gameplay
|
|||||||
assertHealthNotEqualTo(1);
|
assertHealthNotEqualTo(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Beatmap createBeatmap(double startTime, double endTime)
|
private Beatmap createBeatmap(double startTime, double endTime, params BreakPeriod[] breaks)
|
||||||
{
|
{
|
||||||
var beatmap = new Beatmap
|
var beatmap = new Beatmap
|
||||||
{
|
{
|
||||||
BeatmapInfo = { BaseDifficulty = { DrainRate = 5 } },
|
BeatmapInfo = { BaseDifficulty = { DrainRate = 10 } },
|
||||||
};
|
};
|
||||||
|
|
||||||
for (double time = startTime; time <= endTime; time += 100)
|
for (double time = startTime; time <= endTime; time += 100)
|
||||||
|
{
|
||||||
beatmap.HitObjects.Add(new JudgeableHitObject { StartTime = time });
|
beatmap.HitObjects.Add(new JudgeableHitObject { StartTime = time });
|
||||||
|
}
|
||||||
|
|
||||||
|
beatmap.Breaks.AddRange(breaks);
|
||||||
|
|
||||||
return beatmap;
|
return beatmap;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void createProcessor(Beatmap beatmap) => AddStep("create processor", () =>
|
private void createProcessor(Beatmap beatmap) => AddStep("create processor", () =>
|
||||||
{
|
{
|
||||||
breakTime = new Bindable<bool>();
|
|
||||||
|
|
||||||
Child = processor = new DrainingHealthProcessor(beatmap.HitObjects[0].StartTime).With(d =>
|
Child = processor = new DrainingHealthProcessor(beatmap.HitObjects[0].StartTime).With(d =>
|
||||||
{
|
{
|
||||||
d.RelativeSizeAxes = Axes.Both;
|
d.RelativeSizeAxes = Axes.Both;
|
||||||
d.Clock = new FramedClock(clock = new ManualClock());
|
d.Clock = new FramedClock(clock = new ManualClock());
|
||||||
});
|
});
|
||||||
|
|
||||||
processor.IsBreakTime.BindTo(breakTime);
|
|
||||||
processor.ApplyBeatmap(beatmap);
|
processor.ApplyBeatmap(beatmap);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -143,8 +189,6 @@ namespace osu.Game.Tests.Gameplay
|
|||||||
|
|
||||||
private void setHealth(double health) => AddStep($"set health = {health}", () => processor.Health.Value = health);
|
private void setHealth(double health) => AddStep($"set health = {health}", () => processor.Health.Value = health);
|
||||||
|
|
||||||
private void setBreak(bool enabled) => AddStep($"{(enabled ? "enable" : "disable")} break", () => breakTime.Value = enabled);
|
|
||||||
|
|
||||||
private void assertHealthEqualTo(double value)
|
private void assertHealthEqualTo(double value)
|
||||||
=> AddAssert($"health = {value}", () => Precision.AlmostEquals(value, processor.Health.Value, 0.0001f));
|
=> AddAssert($"health = {value}", () => Precision.AlmostEquals(value, processor.Health.Value, 0.0001f));
|
||||||
|
|
||||||
|
@ -7,6 +7,7 @@ using System.Linq;
|
|||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
|
using osu.Framework;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Configuration;
|
using osu.Framework.Configuration;
|
||||||
using osu.Framework.Platform;
|
using osu.Framework.Platform;
|
||||||
@ -35,8 +36,7 @@ namespace osu.Game.Tests.NonVisual
|
|||||||
var osu = loadOsu(host);
|
var osu = loadOsu(host);
|
||||||
var storage = osu.Dependencies.Get<Storage>();
|
var storage = osu.Dependencies.Get<Storage>();
|
||||||
|
|
||||||
string defaultStorageLocation = Path.Combine(Environment.CurrentDirectory, "headless", nameof(TestDefaultDirectory));
|
string defaultStorageLocation = Path.Combine(RuntimeInfo.StartupDirectory, "headless", nameof(TestDefaultDirectory));
|
||||||
|
|
||||||
Assert.That(storage.GetFullPath("."), Is.EqualTo(defaultStorageLocation));
|
Assert.That(storage.GetFullPath("."), Is.EqualTo(defaultStorageLocation));
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
@ -46,17 +46,17 @@ namespace osu.Game.Tests.NonVisual
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private string customPath => Path.Combine(Environment.CurrentDirectory, "custom-path");
|
private string customPath => Path.Combine(RuntimeInfo.StartupDirectory, "custom-path");
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestCustomDirectory()
|
public void TestCustomDirectory()
|
||||||
{
|
{
|
||||||
using (var host = new HeadlessGameHost(nameof(TestCustomDirectory)))
|
using (var host = new HeadlessGameHost(nameof(TestCustomDirectory)))
|
||||||
{
|
{
|
||||||
string headlessPrefix = Path.Combine("headless", nameof(TestCustomDirectory));
|
string defaultStorageLocation = Path.Combine(RuntimeInfo.StartupDirectory, "headless", nameof(TestCustomDirectory));
|
||||||
|
|
||||||
// need access before the game has constructed its own storage yet.
|
// need access before the game has constructed its own storage yet.
|
||||||
Storage storage = new DesktopStorage(headlessPrefix, host);
|
Storage storage = new DesktopStorage(defaultStorageLocation, host);
|
||||||
// manual cleaning so we can prepare a config file.
|
// manual cleaning so we can prepare a config file.
|
||||||
storage.DeleteDirectory(string.Empty);
|
storage.DeleteDirectory(string.Empty);
|
||||||
|
|
||||||
@ -84,10 +84,10 @@ namespace osu.Game.Tests.NonVisual
|
|||||||
{
|
{
|
||||||
using (var host = new HeadlessGameHost(nameof(TestSubDirectoryLookup)))
|
using (var host = new HeadlessGameHost(nameof(TestSubDirectoryLookup)))
|
||||||
{
|
{
|
||||||
string headlessPrefix = Path.Combine("headless", nameof(TestSubDirectoryLookup));
|
string defaultStorageLocation = Path.Combine(RuntimeInfo.StartupDirectory, "headless", nameof(TestSubDirectoryLookup));
|
||||||
|
|
||||||
// need access before the game has constructed its own storage yet.
|
// need access before the game has constructed its own storage yet.
|
||||||
Storage storage = new DesktopStorage(headlessPrefix, host);
|
Storage storage = new DesktopStorage(defaultStorageLocation, host);
|
||||||
// manual cleaning so we can prepare a config file.
|
// manual cleaning so we can prepare a config file.
|
||||||
storage.DeleteDirectory(string.Empty);
|
storage.DeleteDirectory(string.Empty);
|
||||||
|
|
||||||
@ -136,7 +136,7 @@ namespace osu.Game.Tests.NonVisual
|
|||||||
// for testing nested files are not ignored (only top level)
|
// for testing nested files are not ignored (only top level)
|
||||||
host.Storage.GetStorageForDirectory("test-nested").GetStorageForDirectory("cache");
|
host.Storage.GetStorageForDirectory("test-nested").GetStorageForDirectory("cache");
|
||||||
|
|
||||||
string defaultStorageLocation = Path.Combine(Environment.CurrentDirectory, "headless", nameof(TestMigration));
|
string defaultStorageLocation = Path.Combine(RuntimeInfo.StartupDirectory, "headless", nameof(TestMigration));
|
||||||
|
|
||||||
Assert.That(storage.GetFullPath("."), Is.EqualTo(defaultStorageLocation));
|
Assert.That(storage.GetFullPath("."), Is.EqualTo(defaultStorageLocation));
|
||||||
|
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Graphics.OpenGL.Textures;
|
||||||
using osu.Framework.Graphics.Textures;
|
using osu.Framework.Graphics.Textures;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
|
|
||||||
@ -103,7 +104,7 @@ namespace osu.Game.Tests.NonVisual.Skinning
|
|||||||
Textures = fileNames.ToDictionary(fileName => fileName, fileName => new Texture(1, 1));
|
Textures = fileNames.ToDictionary(fileName => fileName, fileName => new Texture(1, 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
public override Texture Get(string name) => Textures.GetValueOrDefault(name);
|
public override Texture Get(string name, WrapMode wrapModeS, WrapMode wrapModeT) => Textures.GetValueOrDefault(name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,14 +17,14 @@ namespace osu.Game.Tests.Resources
|
|||||||
|
|
||||||
public static string GetTestBeatmapForImport(bool virtualTrack = false)
|
public static string GetTestBeatmapForImport(bool virtualTrack = false)
|
||||||
{
|
{
|
||||||
var temp = Path.GetTempFileName() + ".osz";
|
var tempPath = Path.GetTempFileName() + ".osz";
|
||||||
|
|
||||||
using (var stream = GetTestBeatmapStream(virtualTrack))
|
using (var stream = GetTestBeatmapStream(virtualTrack))
|
||||||
using (var newFile = File.Create(temp))
|
using (var newFile = File.Create(tempPath))
|
||||||
stream.CopyTo(newFile);
|
stream.CopyTo(newFile);
|
||||||
|
|
||||||
Assert.IsTrue(File.Exists(temp));
|
Assert.IsTrue(File.Exists(tempPath));
|
||||||
return temp;
|
return tempPath;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -183,11 +183,8 @@ namespace osu.Game.Tests.Scores.IO
|
|||||||
{
|
{
|
||||||
var beatmapManager = osu.Dependencies.Get<BeatmapManager>();
|
var beatmapManager = osu.Dependencies.Get<BeatmapManager>();
|
||||||
|
|
||||||
if (score.Beatmap == null)
|
score.Beatmap ??= beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps.First();
|
||||||
score.Beatmap = beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps.First();
|
score.Ruleset ??= new OsuRuleset().RulesetInfo;
|
||||||
|
|
||||||
if (score.Ruleset == null)
|
|
||||||
score.Ruleset = new OsuRuleset().RulesetInfo;
|
|
||||||
|
|
||||||
var scoreManager = osu.Dependencies.Get<ScoreManager>();
|
var scoreManager = osu.Dependencies.Get<ScoreManager>();
|
||||||
await scoreManager.Import(score, archive);
|
await scoreManager.Import(score, archive);
|
||||||
|
@ -19,6 +19,7 @@ using osu.Game.Graphics;
|
|||||||
using osu.Game.Graphics.Containers;
|
using osu.Game.Graphics.Containers;
|
||||||
using osu.Game.Graphics.Sprites;
|
using osu.Game.Graphics.Sprites;
|
||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
|
using osu.Game.Rulesets.Osu;
|
||||||
using osu.Game.Rulesets.Osu.Mods;
|
using osu.Game.Rulesets.Osu.Mods;
|
||||||
using osu.Game.Scoring;
|
using osu.Game.Scoring;
|
||||||
using osu.Game.Screens;
|
using osu.Game.Screens;
|
||||||
@ -27,6 +28,7 @@ using osu.Game.Screens.Play;
|
|||||||
using osu.Game.Screens.Play.PlayerSettings;
|
using osu.Game.Screens.Play.PlayerSettings;
|
||||||
using osu.Game.Screens.Ranking;
|
using osu.Game.Screens.Ranking;
|
||||||
using osu.Game.Screens.Select;
|
using osu.Game.Screens.Select;
|
||||||
|
using osu.Game.Tests.Beatmaps;
|
||||||
using osu.Game.Tests.Resources;
|
using osu.Game.Tests.Resources;
|
||||||
using osu.Game.Users;
|
using osu.Game.Users;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
@ -186,9 +188,15 @@ namespace osu.Game.Tests.Visual.Background
|
|||||||
public void TestTransition()
|
public void TestTransition()
|
||||||
{
|
{
|
||||||
performFullSetup();
|
performFullSetup();
|
||||||
|
|
||||||
FadeAccessibleResults results = null;
|
FadeAccessibleResults results = null;
|
||||||
AddStep("Transition to Results", () => player.Push(results =
|
|
||||||
new FadeAccessibleResults(new ScoreInfo { User = new User { Username = "osu!" } })));
|
AddStep("Transition to Results", () => player.Push(results = new FadeAccessibleResults(new ScoreInfo
|
||||||
|
{
|
||||||
|
User = new User { Username = "osu!" },
|
||||||
|
Beatmap = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo
|
||||||
|
})));
|
||||||
|
|
||||||
AddUntilStep("Wait for results is current", () => results.IsCurrentScreen());
|
AddUntilStep("Wait for results is current", () => results.IsCurrentScreen());
|
||||||
AddUntilStep("Screen is undimmed, original background retained", () =>
|
AddUntilStep("Screen is undimmed, original background retained", () =>
|
||||||
songSelect.IsBackgroundUndimmed() && songSelect.IsBackgroundCurrent() && results.IsBlurCorrect());
|
songSelect.IsBackgroundUndimmed() && songSelect.IsBackgroundCurrent() && results.IsBlurCorrect());
|
||||||
|
@ -19,7 +19,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
{
|
{
|
||||||
var beatmap = Beatmap.Value.GetPlayableBeatmap(ruleset.RulesetInfo, Array.Empty<Mod>());
|
var beatmap = Beatmap.Value.GetPlayableBeatmap(ruleset.RulesetInfo, Array.Empty<Mod>());
|
||||||
|
|
||||||
return new ScoreAccessibleReplayPlayer(ruleset.GetAutoplayMod().CreateReplayScore(beatmap));
|
return new ScoreAccessibleReplayPlayer(ruleset.GetAutoplayMod()?.CreateReplayScore(beatmap));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void AddCheckSteps()
|
protected override void AddCheckSteps()
|
||||||
|
@ -4,12 +4,18 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Audio;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Platform;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Graphics.Containers;
|
using osu.Game.Graphics.Containers;
|
||||||
using osu.Game.Graphics.UserInterface;
|
using osu.Game.Graphics.UserInterface;
|
||||||
using osu.Game.Online.Multiplayer;
|
using osu.Game.Online.Multiplayer;
|
||||||
|
using osu.Game.Overlays;
|
||||||
|
using osu.Game.Rulesets;
|
||||||
using osu.Game.Rulesets.Osu;
|
using osu.Game.Rulesets.Osu;
|
||||||
using osu.Game.Rulesets.Osu.Mods;
|
using osu.Game.Rulesets.Osu.Mods;
|
||||||
using osu.Game.Screens.Multi;
|
using osu.Game.Screens.Multi;
|
||||||
@ -23,6 +29,18 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
{
|
{
|
||||||
private TestPlaylist playlist;
|
private TestPlaylist playlist;
|
||||||
|
|
||||||
|
private BeatmapManager manager;
|
||||||
|
private RulesetStore rulesets;
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(GameHost host, AudioManager audio)
|
||||||
|
{
|
||||||
|
Dependencies.Cache(rulesets = new RulesetStore(ContextFactory));
|
||||||
|
Dependencies.Cache(manager = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, host, Beatmap.Default));
|
||||||
|
|
||||||
|
manager.Import(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo.BeatmapSet).Wait();
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestNonEditableNonSelectable()
|
public void TestNonEditableNonSelectable()
|
||||||
{
|
{
|
||||||
@ -182,6 +200,28 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
AddStep("click delete button", () => InputManager.Click(MouseButton.Left));
|
AddStep("click delete button", () => InputManager.Click(MouseButton.Left));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestDownloadButtonHiddenInitiallyWhenBeatmapExists()
|
||||||
|
{
|
||||||
|
createPlaylist(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo);
|
||||||
|
|
||||||
|
AddAssert("download button hidden", () => !playlist.ChildrenOfType<BeatmapDownloadTrackingComposite>().Single().IsPresent);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestDownloadButtonVisibleInitiallyWhenBeatmapDoesNotExist()
|
||||||
|
{
|
||||||
|
var byOnlineId = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo;
|
||||||
|
byOnlineId.BeatmapSet.OnlineBeatmapSetID = 1337; // Some random ID that does not exist locally.
|
||||||
|
|
||||||
|
var byChecksum = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo;
|
||||||
|
byChecksum.MD5Hash = "1337"; // Some random checksum that does not exist locally.
|
||||||
|
|
||||||
|
createPlaylist(byOnlineId, byChecksum);
|
||||||
|
|
||||||
|
AddAssert("download buttons shown", () => playlist.ChildrenOfType<BeatmapDownloadTrackingComposite>().All(d => d.IsPresent));
|
||||||
|
}
|
||||||
|
|
||||||
private void moveToItem(int index, Vector2? offset = null)
|
private void moveToItem(int index, Vector2? offset = null)
|
||||||
=> AddStep($"move mouse to item {index}", () => InputManager.MoveMouseTo(playlist.ChildrenOfType<OsuRearrangeableListItem<PlaylistItem>>().ElementAt(index), offset));
|
=> AddStep($"move mouse to item {index}", () => InputManager.MoveMouseTo(playlist.ChildrenOfType<OsuRearrangeableListItem<PlaylistItem>>().ElementAt(index), offset));
|
||||||
|
|
||||||
@ -235,6 +275,39 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
AddUntilStep("wait for items to load", () => playlist.ItemMap.Values.All(i => i.IsLoaded));
|
AddUntilStep("wait for items to load", () => playlist.ItemMap.Values.All(i => i.IsLoaded));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void createPlaylist(params BeatmapInfo[] beatmaps)
|
||||||
|
{
|
||||||
|
AddStep("create playlist", () =>
|
||||||
|
{
|
||||||
|
Child = playlist = new TestPlaylist(false, false)
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Size = new Vector2(500, 300)
|
||||||
|
};
|
||||||
|
|
||||||
|
int index = 0;
|
||||||
|
|
||||||
|
foreach (var b in beatmaps)
|
||||||
|
{
|
||||||
|
playlist.Items.Add(new PlaylistItem
|
||||||
|
{
|
||||||
|
ID = index++,
|
||||||
|
Beatmap = { Value = b },
|
||||||
|
Ruleset = { Value = new OsuRuleset().RulesetInfo },
|
||||||
|
RequiredMods =
|
||||||
|
{
|
||||||
|
new OsuModHardRock(),
|
||||||
|
new OsuModDoubleTime(),
|
||||||
|
new OsuModAutoplay()
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
AddUntilStep("wait for items to load", () => playlist.ItemMap.Values.All(i => i.IsLoaded));
|
||||||
|
}
|
||||||
|
|
||||||
private class TestPlaylist : DrawableRoomPlaylist
|
private class TestPlaylist : DrawableRoomPlaylist
|
||||||
{
|
{
|
||||||
public new IReadOnlyDictionary<PlaylistItem, RearrangeableListItem<PlaylistItem>> ItemMap => base.ItemMap;
|
public new IReadOnlyDictionary<PlaylistItem, RearrangeableListItem<PlaylistItem>> ItemMap => base.ItemMap;
|
||||||
|
@ -141,6 +141,9 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
}
|
}
|
||||||
|
|
||||||
public readonly BindableList<Room> Rooms = new BindableList<Room>();
|
public readonly BindableList<Room> Rooms = new BindableList<Room>();
|
||||||
|
|
||||||
|
public Bindable<bool> InitialRoomsReceived { get; } = new Bindable<bool>(true);
|
||||||
|
|
||||||
IBindableList<Room> IRoomManager.Rooms => Rooms;
|
IBindableList<Room> IRoomManager.Rooms => Rooms;
|
||||||
|
|
||||||
public void CreateRoom(Room room, Action<Room> onSuccess = null, Action<string> onError = null) => Rooms.Add(room);
|
public void CreateRoom(Room room, Action<Room> onSuccess = null, Action<string> onError = null) => Rooms.Add(room);
|
||||||
|
@ -133,6 +133,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
remove { }
|
remove { }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Bindable<bool> InitialRoomsReceived { get; } = new Bindable<bool>(true);
|
||||||
|
|
||||||
public IBindableList<Room> Rooms { get; } = null;
|
public IBindableList<Room> Rooms { get; } = null;
|
||||||
|
|
||||||
public void CreateRoom(Room room, Action<Room> onSuccess = null, Action<string> onError = null)
|
public void CreateRoom(Room room, Action<Room> onSuccess = null, Action<string> onError = null)
|
||||||
|
@ -5,7 +5,9 @@ using System;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Audio;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Platform;
|
||||||
using osu.Framework.Screens;
|
using osu.Framework.Screens;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
@ -29,14 +31,20 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
[Cached(typeof(IRoomManager))]
|
[Cached(typeof(IRoomManager))]
|
||||||
private readonly TestRoomManager roomManager = new TestRoomManager();
|
private readonly TestRoomManager roomManager = new TestRoomManager();
|
||||||
|
|
||||||
[Resolved]
|
private BeatmapManager manager;
|
||||||
private BeatmapManager beatmaps { get; set; }
|
private RulesetStore rulesets;
|
||||||
|
|
||||||
[Resolved]
|
|
||||||
private RulesetStore rulesets { get; set; }
|
|
||||||
|
|
||||||
private TestMatchSubScreen match;
|
private TestMatchSubScreen match;
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(GameHost host, AudioManager audio)
|
||||||
|
{
|
||||||
|
Dependencies.Cache(rulesets = new RulesetStore(ContextFactory));
|
||||||
|
Dependencies.Cache(manager = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, host, Beatmap.Default));
|
||||||
|
|
||||||
|
manager.Import(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo.BeatmapSet).Wait();
|
||||||
|
}
|
||||||
|
|
||||||
[SetUp]
|
[SetUp]
|
||||||
public void Setup() => Schedule(() =>
|
public void Setup() => Schedule(() =>
|
||||||
{
|
{
|
||||||
@ -75,10 +83,49 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
AddAssert("first playlist item selected", () => match.SelectedItem.Value == Room.Playlist[0]);
|
AddAssert("first playlist item selected", () => match.SelectedItem.Value == Room.Playlist[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestBeatmapUpdatedOnReImport()
|
||||||
|
{
|
||||||
|
BeatmapSetInfo importedSet = null;
|
||||||
|
|
||||||
|
AddStep("import altered beatmap", () =>
|
||||||
|
{
|
||||||
|
var beatmap = new TestBeatmap(new OsuRuleset().RulesetInfo);
|
||||||
|
beatmap.BeatmapInfo.BaseDifficulty.CircleSize = 1;
|
||||||
|
|
||||||
|
importedSet = manager.Import(beatmap.BeatmapInfo.BeatmapSet).Result;
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("load room", () =>
|
||||||
|
{
|
||||||
|
Room.Name.Value = "my awesome room";
|
||||||
|
Room.Host.Value = new User { Id = 2, Username = "peppy" };
|
||||||
|
Room.Playlist.Add(new PlaylistItem
|
||||||
|
{
|
||||||
|
Beatmap = { Value = importedSet.Beatmaps[0] },
|
||||||
|
Ruleset = { Value = new OsuRuleset().RulesetInfo }
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("create room", () =>
|
||||||
|
{
|
||||||
|
InputManager.MoveMouseTo(match.ChildrenOfType<MatchSettingsOverlay.CreateRoomButton>().Single());
|
||||||
|
InputManager.Click(MouseButton.Left);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddAssert("match has altered beatmap", () => match.Beatmap.Value.Beatmap.BeatmapInfo.BaseDifficulty.CircleSize == 1);
|
||||||
|
|
||||||
|
AddStep("re-import original beatmap", () => manager.Import(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo.BeatmapSet).Wait());
|
||||||
|
|
||||||
|
AddAssert("match has original beatmap", () => match.Beatmap.Value.Beatmap.BeatmapInfo.BaseDifficulty.CircleSize != 1);
|
||||||
|
}
|
||||||
|
|
||||||
private class TestMatchSubScreen : MatchSubScreen
|
private class TestMatchSubScreen : MatchSubScreen
|
||||||
{
|
{
|
||||||
public new Bindable<PlaylistItem> SelectedItem => base.SelectedItem;
|
public new Bindable<PlaylistItem> SelectedItem => base.SelectedItem;
|
||||||
|
|
||||||
|
public new Bindable<WorkingBeatmap> Beatmap => base.Beatmap;
|
||||||
|
|
||||||
public TestMatchSubScreen(Room room)
|
public TestMatchSubScreen(Room room)
|
||||||
: base(room)
|
: base(room)
|
||||||
{
|
{
|
||||||
@ -93,6 +140,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
remove => throw new NotImplementedException();
|
remove => throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Bindable<bool> InitialRoomsReceived { get; } = new Bindable<bool>(true);
|
||||||
|
|
||||||
public IBindableList<Room> Rooms { get; } = new BindableList<Room>();
|
public IBindableList<Room> Rooms { get; } = new BindableList<Room>();
|
||||||
|
|
||||||
public void CreateRoom(Room room, Action<Room> onSuccess = null, Action<string> onError = null)
|
public void CreateRoom(Room room, Action<Room> onSuccess = null, Action<string> onError = null)
|
||||||
|
@ -0,0 +1,124 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Game.Online.API;
|
||||||
|
using osu.Game.Online.API.Requests;
|
||||||
|
using osu.Game.Online.Multiplayer;
|
||||||
|
using osu.Game.Rulesets.Osu;
|
||||||
|
using osu.Game.Rulesets.Scoring;
|
||||||
|
using osu.Game.Scoring;
|
||||||
|
using osu.Game.Screens.Multi.Ranking;
|
||||||
|
using osu.Game.Tests.Beatmaps;
|
||||||
|
using osu.Game.Users;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Visual.Multiplayer
|
||||||
|
{
|
||||||
|
public class TestSceneTimeshiftResultsScreen : ScreenTestScene
|
||||||
|
{
|
||||||
|
private bool roomsReceived;
|
||||||
|
|
||||||
|
[SetUp]
|
||||||
|
public void Setup() => Schedule(() =>
|
||||||
|
{
|
||||||
|
roomsReceived = false;
|
||||||
|
bindHandler();
|
||||||
|
});
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestShowResultsWithScore()
|
||||||
|
{
|
||||||
|
createResults(new TestScoreInfo(new OsuRuleset().RulesetInfo));
|
||||||
|
AddWaitStep("wait for display", 5);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestShowResultsNullScore()
|
||||||
|
{
|
||||||
|
createResults(null);
|
||||||
|
AddWaitStep("wait for display", 5);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestShowResultsNullScoreWithDelay()
|
||||||
|
{
|
||||||
|
AddStep("bind delayed handler", () => bindHandler(3000));
|
||||||
|
createResults(null);
|
||||||
|
AddUntilStep("wait for rooms to be received", () => roomsReceived);
|
||||||
|
AddWaitStep("wait for display", 5);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createResults(ScoreInfo score)
|
||||||
|
{
|
||||||
|
AddStep("load results", () =>
|
||||||
|
{
|
||||||
|
LoadScreen(new TimeshiftResultsScreen(score, 1, new PlaylistItem
|
||||||
|
{
|
||||||
|
Beatmap = { Value = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo },
|
||||||
|
Ruleset = { Value = new OsuRuleset().RulesetInfo }
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void bindHandler(double delay = 0)
|
||||||
|
{
|
||||||
|
var roomScores = new List<RoomScore>();
|
||||||
|
|
||||||
|
for (int i = 0; i < 10; i++)
|
||||||
|
{
|
||||||
|
roomScores.Add(new RoomScore
|
||||||
|
{
|
||||||
|
ID = i,
|
||||||
|
Accuracy = 0.9 - 0.01 * i,
|
||||||
|
EndedAt = DateTimeOffset.Now.Subtract(TimeSpan.FromHours(i)),
|
||||||
|
Passed = true,
|
||||||
|
Rank = ScoreRank.B,
|
||||||
|
MaxCombo = 999,
|
||||||
|
TotalScore = 999999 - i * 1000,
|
||||||
|
User = new User
|
||||||
|
{
|
||||||
|
Id = 2,
|
||||||
|
Username = $"peppy{i}",
|
||||||
|
CoverUrl = "https://osu.ppy.sh/images/headers/profile-covers/c3.jpg",
|
||||||
|
},
|
||||||
|
Statistics =
|
||||||
|
{
|
||||||
|
{ HitResult.Miss, 1 },
|
||||||
|
{ HitResult.Meh, 50 },
|
||||||
|
{ HitResult.Good, 100 },
|
||||||
|
{ HitResult.Great, 300 },
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
((DummyAPIAccess)API).HandleRequest = request =>
|
||||||
|
{
|
||||||
|
switch (request)
|
||||||
|
{
|
||||||
|
case GetRoomPlaylistScoresRequest r:
|
||||||
|
if (delay == 0)
|
||||||
|
success();
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Task.Run(async () =>
|
||||||
|
{
|
||||||
|
await Task.Delay(TimeSpan.FromMilliseconds(delay));
|
||||||
|
Schedule(success);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void success()
|
||||||
|
{
|
||||||
|
r.TriggerSuccess(new RoomPlaylistScores { Scores = roomScores });
|
||||||
|
roomsReceived = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -246,7 +246,12 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
{
|
{
|
||||||
((BindableList<Channel>)ChannelManager.AvailableChannels).AddRange(channels);
|
((BindableList<Channel>)ChannelManager.AvailableChannels).AddRange(channels);
|
||||||
|
|
||||||
Child = ChatOverlay = new TestChatOverlay { RelativeSizeAxes = Axes.Both, };
|
InternalChildren = new Drawable[]
|
||||||
|
{
|
||||||
|
ChannelManager,
|
||||||
|
ChatOverlay = new TestChatOverlay { RelativeSizeAxes = Axes.Both, },
|
||||||
|
};
|
||||||
|
|
||||||
ChatOverlay.Show();
|
ChatOverlay.Show();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,6 @@
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
|
||||||
using osu.Framework.Extensions.Color4Extensions;
|
using osu.Framework.Extensions.Color4Extensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
@ -33,7 +32,10 @@ namespace osu.Game.Tests.Visual.Ranking
|
|||||||
{
|
{
|
||||||
var author = new User { Username = "mapper_name" };
|
var author = new User { Username = "mapper_name" };
|
||||||
|
|
||||||
AddStep("show example score", () => showPanel(createTestBeatmap(author), new TestScoreInfo(new OsuRuleset().RulesetInfo)));
|
AddStep("show example score", () => showPanel(new TestScoreInfo(new OsuRuleset().RulesetInfo)
|
||||||
|
{
|
||||||
|
Beatmap = createTestBeatmap(author)
|
||||||
|
}));
|
||||||
|
|
||||||
AddAssert("mapper name present", () => this.ChildrenOfType<OsuSpriteText>().Any(spriteText => spriteText.Text == "mapper_name"));
|
AddAssert("mapper name present", () => this.ChildrenOfType<OsuSpriteText>().Any(spriteText => spriteText.Text == "mapper_name"));
|
||||||
}
|
}
|
||||||
@ -41,38 +43,34 @@ namespace osu.Game.Tests.Visual.Ranking
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestMapWithUnknownMapper()
|
public void TestMapWithUnknownMapper()
|
||||||
{
|
{
|
||||||
AddStep("show example score", () => showPanel(createTestBeatmap(null), new TestScoreInfo(new OsuRuleset().RulesetInfo)));
|
AddStep("show example score", () => showPanel(new TestScoreInfo(new OsuRuleset().RulesetInfo)
|
||||||
|
{
|
||||||
|
Beatmap = createTestBeatmap(null)
|
||||||
|
}));
|
||||||
|
|
||||||
AddAssert("mapped by text not present", () =>
|
AddAssert("mapped by text not present", () =>
|
||||||
this.ChildrenOfType<OsuSpriteText>().All(spriteText => !containsAny(spriteText.Text, "mapped", "by")));
|
this.ChildrenOfType<OsuSpriteText>().All(spriteText => !containsAny(spriteText.Text, "mapped", "by")));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void showPanel(WorkingBeatmap workingBeatmap, ScoreInfo score)
|
private void showPanel(ScoreInfo score) => Child = new ExpandedPanelMiddleContentContainer(score);
|
||||||
{
|
|
||||||
Child = new ExpandedPanelMiddleContentContainer(workingBeatmap, score);
|
|
||||||
}
|
|
||||||
|
|
||||||
private WorkingBeatmap createTestBeatmap(User author)
|
private BeatmapInfo createTestBeatmap(User author)
|
||||||
{
|
{
|
||||||
var beatmap = new TestBeatmap(rulesetStore.GetRuleset(0));
|
var beatmap = new TestBeatmap(rulesetStore.GetRuleset(0)).BeatmapInfo;
|
||||||
|
|
||||||
beatmap.Metadata.Author = author;
|
beatmap.Metadata.Author = author;
|
||||||
beatmap.Metadata.Title = "Verrrrrrrrrrrrrrrrrrry looooooooooooooooooooooooong beatmap title";
|
beatmap.Metadata.Title = "Verrrrrrrrrrrrrrrrrrry looooooooooooooooooooooooong beatmap title";
|
||||||
beatmap.Metadata.Artist = "Verrrrrrrrrrrrrrrrrrry looooooooooooooooooooooooong beatmap artist";
|
beatmap.Metadata.Artist = "Verrrrrrrrrrrrrrrrrrry looooooooooooooooooooooooong beatmap artist";
|
||||||
|
|
||||||
return new TestWorkingBeatmap(beatmap);
|
return beatmap;
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool containsAny(string text, params string[] stringsToMatch) => stringsToMatch.Any(text.Contains);
|
private bool containsAny(string text, params string[] stringsToMatch) => stringsToMatch.Any(text.Contains);
|
||||||
|
|
||||||
private class ExpandedPanelMiddleContentContainer : Container
|
private class ExpandedPanelMiddleContentContainer : Container
|
||||||
{
|
{
|
||||||
[Cached]
|
public ExpandedPanelMiddleContentContainer(ScoreInfo score)
|
||||||
private Bindable<WorkingBeatmap> workingBeatmap { get; set; }
|
|
||||||
|
|
||||||
public ExpandedPanelMiddleContentContainer(WorkingBeatmap beatmap, ScoreInfo score)
|
|
||||||
{
|
{
|
||||||
workingBeatmap = new Bindable<WorkingBeatmap>(beatmap);
|
|
||||||
|
|
||||||
Anchor = Anchor.Centre;
|
Anchor = Anchor.Centre;
|
||||||
Origin = Anchor.Centre;
|
Origin = Anchor.Centre;
|
||||||
Size = new Vector2(ScorePanel.EXPANDED_WIDTH, 700);
|
Size = new Vector2(ScorePanel.EXPANDED_WIDTH, 700);
|
||||||
|
@ -36,12 +36,14 @@ namespace osu.Game.Tests.Visual.Ranking
|
|||||||
Beatmap.Value = beatmaps.GetWorkingBeatmap(beatmapInfo);
|
Beatmap.Value = beatmaps.GetWorkingBeatmap(beatmapInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
private TestSoloResults createResultsScreen() => new TestSoloResults(new TestScoreInfo(new OsuRuleset().RulesetInfo));
|
private TestResultsScreen createResultsScreen() => new TestResultsScreen(new TestScoreInfo(new OsuRuleset().RulesetInfo));
|
||||||
|
|
||||||
|
private UnrankedSoloResultsScreen createUnrankedSoloResultsScreen() => new UnrankedSoloResultsScreen(new TestScoreInfo(new OsuRuleset().RulesetInfo));
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void ResultsWithoutPlayer()
|
public void ResultsWithoutPlayer()
|
||||||
{
|
{
|
||||||
TestSoloResults screen = null;
|
TestResultsScreen screen = null;
|
||||||
OsuScreenStack stack;
|
OsuScreenStack stack;
|
||||||
|
|
||||||
AddStep("load results", () =>
|
AddStep("load results", () =>
|
||||||
@ -60,13 +62,23 @@ namespace osu.Game.Tests.Visual.Ranking
|
|||||||
[Test]
|
[Test]
|
||||||
public void ResultsWithPlayer()
|
public void ResultsWithPlayer()
|
||||||
{
|
{
|
||||||
TestSoloResults screen = null;
|
TestResultsScreen screen = null;
|
||||||
|
|
||||||
AddStep("load results", () => Child = new TestResultsContainer(screen = createResultsScreen()));
|
AddStep("load results", () => Child = new TestResultsContainer(screen = createResultsScreen()));
|
||||||
AddUntilStep("wait for loaded", () => screen.IsLoaded);
|
AddUntilStep("wait for loaded", () => screen.IsLoaded);
|
||||||
AddAssert("retry overlay present", () => screen.RetryOverlay != null);
|
AddAssert("retry overlay present", () => screen.RetryOverlay != null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void ResultsForUnranked()
|
||||||
|
{
|
||||||
|
UnrankedSoloResultsScreen screen = null;
|
||||||
|
|
||||||
|
AddStep("load results", () => Child = new TestResultsContainer(screen = createUnrankedSoloResultsScreen()));
|
||||||
|
AddUntilStep("wait for loaded", () => screen.IsLoaded);
|
||||||
|
AddAssert("retry overlay present", () => screen.RetryOverlay != null);
|
||||||
|
}
|
||||||
|
|
||||||
private class TestResultsContainer : Container
|
private class TestResultsContainer : Container
|
||||||
{
|
{
|
||||||
[Cached(typeof(Player))]
|
[Cached(typeof(Player))]
|
||||||
@ -86,11 +98,11 @@ namespace osu.Game.Tests.Visual.Ranking
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class TestSoloResults : ResultsScreen
|
private class TestResultsScreen : ResultsScreen
|
||||||
{
|
{
|
||||||
public HotkeyRetryOverlay RetryOverlay;
|
public HotkeyRetryOverlay RetryOverlay;
|
||||||
|
|
||||||
public TestSoloResults(ScoreInfo score)
|
public TestResultsScreen(ScoreInfo score)
|
||||||
: base(score)
|
: base(score)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
@ -102,5 +114,24 @@ namespace osu.Game.Tests.Visual.Ranking
|
|||||||
RetryOverlay = InternalChildren.OfType<HotkeyRetryOverlay>().SingleOrDefault();
|
RetryOverlay = InternalChildren.OfType<HotkeyRetryOverlay>().SingleOrDefault();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class UnrankedSoloResultsScreen : SoloResultsScreen
|
||||||
|
{
|
||||||
|
public HotkeyRetryOverlay RetryOverlay;
|
||||||
|
|
||||||
|
public UnrankedSoloResultsScreen(ScoreInfo score)
|
||||||
|
: base(score)
|
||||||
|
{
|
||||||
|
Score.Beatmap.OnlineBeatmapID = 0;
|
||||||
|
Score.Beatmap.Status = BeatmapSetOnlineStatus.Pending;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
RetryOverlay = InternalChildren.OfType<HotkeyRetryOverlay>().SingleOrDefault();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,19 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Testing;
|
||||||
|
using osu.Framework.Threading;
|
||||||
using osu.Game.Overlays;
|
using osu.Game.Overlays;
|
||||||
|
using osu.Game.Overlays.KeyBinding;
|
||||||
|
using osuTK.Input;
|
||||||
|
|
||||||
namespace osu.Game.Tests.Visual.Settings
|
namespace osu.Game.Tests.Visual.Settings
|
||||||
{
|
{
|
||||||
[TestFixture]
|
[TestFixture]
|
||||||
public class TestSceneKeyBindingPanel : OsuTestScene
|
public class TestSceneKeyBindingPanel : OsuManualInputManagerTestScene
|
||||||
{
|
{
|
||||||
private readonly KeyBindingPanel panel;
|
private readonly KeyBindingPanel panel;
|
||||||
|
|
||||||
@ -21,5 +27,42 @@ namespace osu.Game.Tests.Visual.Settings
|
|||||||
base.LoadComplete();
|
base.LoadComplete();
|
||||||
panel.Show();
|
panel.Show();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestClickTwiceOnClearButton()
|
||||||
|
{
|
||||||
|
KeyBindingRow firstRow = null;
|
||||||
|
|
||||||
|
AddStep("click first row", () =>
|
||||||
|
{
|
||||||
|
firstRow = panel.ChildrenOfType<KeyBindingRow>().First();
|
||||||
|
InputManager.MoveMouseTo(firstRow);
|
||||||
|
InputManager.Click(MouseButton.Left);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("schedule button clicks", () =>
|
||||||
|
{
|
||||||
|
var clearButton = firstRow.ChildrenOfType<KeyBindingRow.ClearButton>().Single();
|
||||||
|
|
||||||
|
InputManager.MoveMouseTo(clearButton);
|
||||||
|
|
||||||
|
int buttonClicks = 0;
|
||||||
|
ScheduledDelegate clickDelegate = null;
|
||||||
|
|
||||||
|
clickDelegate = Scheduler.AddDelayed(() =>
|
||||||
|
{
|
||||||
|
InputManager.PressButton(MouseButton.Left);
|
||||||
|
InputManager.ReleaseButton(MouseButton.Left);
|
||||||
|
|
||||||
|
if (++buttonClicks == 2)
|
||||||
|
{
|
||||||
|
// ReSharper disable once AccessToModifiedClosure
|
||||||
|
Debug.Assert(clickDelegate != null);
|
||||||
|
// ReSharper disable once AccessToModifiedClosure
|
||||||
|
clickDelegate.Cancel();
|
||||||
|
}
|
||||||
|
}, 0, true);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -38,13 +38,9 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
public class TestScenePlaySongSelect : ScreenTestScene
|
public class TestScenePlaySongSelect : ScreenTestScene
|
||||||
{
|
{
|
||||||
private BeatmapManager manager;
|
private BeatmapManager manager;
|
||||||
|
|
||||||
private RulesetStore rulesets;
|
private RulesetStore rulesets;
|
||||||
|
|
||||||
private MusicController music;
|
private MusicController music;
|
||||||
|
|
||||||
private WorkingBeatmap defaultBeatmap;
|
private WorkingBeatmap defaultBeatmap;
|
||||||
|
|
||||||
private TestSongSelect songSelect;
|
private TestSongSelect songSelect;
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
@ -308,15 +304,13 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
|
|
||||||
AddAssert("random map selected", () => songSelect.CurrentBeatmap != defaultBeatmap);
|
AddAssert("random map selected", () => songSelect.CurrentBeatmap != defaultBeatmap);
|
||||||
|
|
||||||
var sortMode = config.GetBindable<SortMode>(OsuSetting.SongSelectSortingMode);
|
AddStep(@"Sort by Artist", () => config.Set(OsuSetting.SongSelectSortingMode, SortMode.Artist));
|
||||||
|
AddStep(@"Sort by Title", () => config.Set(OsuSetting.SongSelectSortingMode, SortMode.Title));
|
||||||
AddStep(@"Sort by Artist", delegate { sortMode.Value = SortMode.Artist; });
|
AddStep(@"Sort by Author", () => config.Set(OsuSetting.SongSelectSortingMode, SortMode.Author));
|
||||||
AddStep(@"Sort by Title", delegate { sortMode.Value = SortMode.Title; });
|
AddStep(@"Sort by DateAdded", () => config.Set(OsuSetting.SongSelectSortingMode, SortMode.DateAdded));
|
||||||
AddStep(@"Sort by Author", delegate { sortMode.Value = SortMode.Author; });
|
AddStep(@"Sort by BPM", () => config.Set(OsuSetting.SongSelectSortingMode, SortMode.BPM));
|
||||||
AddStep(@"Sort by DateAdded", delegate { sortMode.Value = SortMode.DateAdded; });
|
AddStep(@"Sort by Length", () => config.Set(OsuSetting.SongSelectSortingMode, SortMode.Length));
|
||||||
AddStep(@"Sort by BPM", delegate { sortMode.Value = SortMode.BPM; });
|
AddStep(@"Sort by Difficulty", () => config.Set(OsuSetting.SongSelectSortingMode, SortMode.Difficulty));
|
||||||
AddStep(@"Sort by Length", delegate { sortMode.Value = SortMode.Length; });
|
|
||||||
AddStep(@"Sort by Difficulty", delegate { sortMode.Value = SortMode.Difficulty; });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Diagnostics;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||||
@ -45,7 +46,12 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
});
|
});
|
||||||
|
|
||||||
foreach (var p in typeof(OsuIcon).GetProperties(BindingFlags.Public | BindingFlags.Static))
|
foreach (var p in typeof(OsuIcon).GetProperties(BindingFlags.Public | BindingFlags.Static))
|
||||||
flow.Add(new Icon($"{nameof(OsuIcon)}.{p.Name}", (IconUsage)p.GetValue(null)));
|
{
|
||||||
|
var propValue = p.GetValue(null);
|
||||||
|
Debug.Assert(propValue != null);
|
||||||
|
|
||||||
|
flow.Add(new Icon($"{nameof(OsuIcon)}.{p.Name}", (IconUsage)propValue));
|
||||||
|
}
|
||||||
|
|
||||||
AddStep("toggle shadows", () => flow.Children.ForEach(i => i.SpriteIcon.Shadow = !i.SpriteIcon.Shadow));
|
AddStep("toggle shadows", () => flow.Children.ForEach(i => i.SpriteIcon.Shadow = !i.SpriteIcon.Shadow));
|
||||||
AddStep("change icons", () => flow.Children.ForEach(i => i.SpriteIcon.Icon = new IconUsage((char)(i.SpriteIcon.Icon.Icon + 1))));
|
AddStep("change icons", () => flow.Children.ForEach(i => i.SpriteIcon.Icon = new IconUsage((char)(i.SpriteIcon.Icon.Icon + 1))));
|
||||||
|
@ -24,8 +24,7 @@ namespace osu.Game.Tournament.Tests
|
|||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load()
|
||||||
{
|
{
|
||||||
if (Ladder.Ruleset.Value == null)
|
Ladder.Ruleset.Value ??= rulesetStore.AvailableRulesets.First();
|
||||||
Ladder.Ruleset.Value = rulesetStore.AvailableRulesets.First();
|
|
||||||
|
|
||||||
Ruleset.BindTo(Ladder.Ruleset);
|
Ruleset.BindTo(Ladder.Ruleset);
|
||||||
}
|
}
|
||||||
|
@ -36,6 +36,7 @@ namespace osu.Game.Tournament.IPC
|
|||||||
|
|
||||||
private int lastBeatmapId;
|
private int lastBeatmapId;
|
||||||
private ScheduledDelegate scheduled;
|
private ScheduledDelegate scheduled;
|
||||||
|
private GetBeatmapRequest beatmapLookupRequest;
|
||||||
|
|
||||||
public StableInfo StableInfo { get; private set; }
|
public StableInfo StableInfo { get; private set; }
|
||||||
|
|
||||||
@ -86,6 +87,8 @@ namespace osu.Game.Tournament.IPC
|
|||||||
|
|
||||||
if (lastBeatmapId != beatmapId)
|
if (lastBeatmapId != beatmapId)
|
||||||
{
|
{
|
||||||
|
beatmapLookupRequest?.Cancel();
|
||||||
|
|
||||||
lastBeatmapId = beatmapId;
|
lastBeatmapId = beatmapId;
|
||||||
|
|
||||||
var existing = ladder.CurrentMatch.Value?.Round.Value?.Beatmaps.FirstOrDefault(b => b.ID == beatmapId && b.BeatmapInfo != null);
|
var existing = ladder.CurrentMatch.Value?.Round.Value?.Beatmaps.FirstOrDefault(b => b.ID == beatmapId && b.BeatmapInfo != null);
|
||||||
@ -94,9 +97,9 @@ namespace osu.Game.Tournament.IPC
|
|||||||
Beatmap.Value = existing.BeatmapInfo;
|
Beatmap.Value = existing.BeatmapInfo;
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var req = new GetBeatmapRequest(new BeatmapInfo { OnlineBeatmapID = beatmapId });
|
beatmapLookupRequest = new GetBeatmapRequest(new BeatmapInfo { OnlineBeatmapID = beatmapId });
|
||||||
req.Success += b => Beatmap.Value = b.ToBeatmap(Rulesets);
|
beatmapLookupRequest.Success += b => Beatmap.Value = b.ToBeatmap(Rulesets);
|
||||||
API.Queue(req);
|
API.Queue(beatmapLookupRequest);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -47,8 +47,7 @@ namespace osu.Game.Tournament.Screens.Drawings
|
|||||||
|
|
||||||
this.storage = storage;
|
this.storage = storage;
|
||||||
|
|
||||||
if (TeamList == null)
|
TeamList ??= new StorageBackedTeamList(storage);
|
||||||
TeamList = new StorageBackedTeamList(storage);
|
|
||||||
|
|
||||||
if (!TeamList.Teams.Any())
|
if (!TeamList.Teams.Any())
|
||||||
{
|
{
|
||||||
|
@ -14,7 +14,10 @@ namespace osu.Game.Tournament.Screens.Gameplay.Components
|
|||||||
{
|
{
|
||||||
private readonly TeamScore score;
|
private readonly TeamScore score;
|
||||||
|
|
||||||
public bool ShowScore { set => score.FadeTo(value ? 1 : 0, 200); }
|
public bool ShowScore
|
||||||
|
{
|
||||||
|
set => score.FadeTo(value ? 1 : 0, 200);
|
||||||
|
}
|
||||||
|
|
||||||
public TeamDisplay(TournamentTeam team, TeamColour colour, Bindable<int?> currentTeamScore, int pointsToWin)
|
public TeamDisplay(TournamentTeam team, TeamColour colour, Bindable<int?> currentTeamScore, int pointsToWin)
|
||||||
: base(team)
|
: base(team)
|
||||||
|
@ -21,7 +21,10 @@ namespace osu.Game.Tournament.Screens.Gameplay.Components
|
|||||||
|
|
||||||
private TeamDisplay teamDisplay;
|
private TeamDisplay teamDisplay;
|
||||||
|
|
||||||
public bool ShowScore { set => teamDisplay.ShowScore = value; }
|
public bool ShowScore
|
||||||
|
{
|
||||||
|
set => teamDisplay.ShowScore = value;
|
||||||
|
}
|
||||||
|
|
||||||
public TeamScoreDisplay(TeamColour teamColour)
|
public TeamScoreDisplay(TeamColour teamColour)
|
||||||
{
|
{
|
||||||
|
@ -26,7 +26,7 @@ namespace osu.Game.Tournament.Screens
|
|||||||
private FillFlowContainer fillFlow;
|
private FillFlowContainer fillFlow;
|
||||||
|
|
||||||
private LoginOverlay loginOverlay;
|
private LoginOverlay loginOverlay;
|
||||||
private ActionableInfo resolution;
|
private ResolutionSelector resolution;
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private MatchIPCInfo ipc { get; set; }
|
private MatchIPCInfo ipc { get; set; }
|
||||||
@ -115,18 +115,20 @@ namespace osu.Game.Tournament.Screens
|
|||||||
Items = rulesets.AvailableRulesets,
|
Items = rulesets.AvailableRulesets,
|
||||||
Current = LadderInfo.Ruleset,
|
Current = LadderInfo.Ruleset,
|
||||||
},
|
},
|
||||||
resolution = new ActionableInfo
|
resolution = new ResolutionSelector
|
||||||
{
|
{
|
||||||
Label = "Stream area resolution",
|
Label = "Stream area resolution",
|
||||||
ButtonText = "Set to 1080p",
|
ButtonText = "Set height",
|
||||||
Action = () =>
|
Action = height =>
|
||||||
{
|
{
|
||||||
windowSize.Value = new Size((int)(1920 / TournamentSceneManager.STREAM_AREA_WIDTH * TournamentSceneManager.REQUIRED_WIDTH), 1080);
|
windowSize.Value = new Size((int)(height * aspect_ratio / TournamentSceneManager.STREAM_AREA_WIDTH * TournamentSceneManager.REQUIRED_WIDTH), height);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private const float aspect_ratio = 16f / 9f;
|
||||||
|
|
||||||
protected override void Update()
|
protected override void Update()
|
||||||
{
|
{
|
||||||
base.Update();
|
base.Update();
|
||||||
@ -181,6 +183,7 @@ namespace osu.Game.Tournament.Screens
|
|||||||
public Action Action;
|
public Action Action;
|
||||||
|
|
||||||
private TournamentSpriteText valueText;
|
private TournamentSpriteText valueText;
|
||||||
|
protected FillFlowContainer FlowContainer;
|
||||||
|
|
||||||
protected override Drawable CreateComponent() => new Container
|
protected override Drawable CreateComponent() => new Container
|
||||||
{
|
{
|
||||||
@ -193,15 +196,67 @@ namespace osu.Game.Tournament.Screens
|
|||||||
Anchor = Anchor.CentreLeft,
|
Anchor = Anchor.CentreLeft,
|
||||||
Origin = Anchor.CentreLeft,
|
Origin = Anchor.CentreLeft,
|
||||||
},
|
},
|
||||||
button = new TriangleButton
|
FlowContainer = new FillFlowContainer
|
||||||
{
|
{
|
||||||
Anchor = Anchor.CentreRight,
|
Anchor = Anchor.CentreRight,
|
||||||
Origin = Anchor.CentreRight,
|
Origin = Anchor.CentreRight,
|
||||||
Size = new Vector2(100, 30),
|
AutoSizeAxes = Axes.Both,
|
||||||
|
Spacing = new Vector2(10, 0),
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
button = new TriangleButton
|
||||||
|
{
|
||||||
|
Size = new Vector2(100, 40),
|
||||||
Action = () => Action?.Invoke()
|
Action = () => Action?.Invoke()
|
||||||
},
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class ResolutionSelector : ActionableInfo
|
||||||
|
{
|
||||||
|
private const int minimum_window_height = 480;
|
||||||
|
private const int maximum_window_height = 2160;
|
||||||
|
|
||||||
|
public new Action<int> Action;
|
||||||
|
|
||||||
|
private OsuNumberBox numberBox;
|
||||||
|
|
||||||
|
protected override Drawable CreateComponent()
|
||||||
|
{
|
||||||
|
var drawable = base.CreateComponent();
|
||||||
|
FlowContainer.Insert(-1, numberBox = new OsuNumberBox
|
||||||
|
{
|
||||||
|
Text = "1080",
|
||||||
|
Width = 100
|
||||||
|
});
|
||||||
|
|
||||||
|
base.Action = () =>
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(numberBox.Text))
|
||||||
|
return;
|
||||||
|
|
||||||
|
// box contains text
|
||||||
|
if (!int.TryParse(numberBox.Text, out var number))
|
||||||
|
{
|
||||||
|
// at this point, the only reason we can arrive here is if the input number was too big to parse into an int
|
||||||
|
// so clamp to max allowed value
|
||||||
|
number = maximum_window_height;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
number = Math.Clamp(number, minimum_window_height, maximum_window_height);
|
||||||
|
}
|
||||||
|
|
||||||
|
// in case number got clamped, reset number in numberBox
|
||||||
|
numberBox.Text = number.ToString();
|
||||||
|
|
||||||
|
Action?.Invoke(number);
|
||||||
|
};
|
||||||
|
return drawable;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,19 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Drawing;
|
||||||
using osu.Framework.Extensions.Color4Extensions;
|
using osu.Framework.Extensions.Color4Extensions;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Configuration;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
using osu.Framework.Graphics.Colour;
|
using osu.Framework.Graphics.Colour;
|
||||||
using osu.Game.Graphics.Cursor;
|
using osu.Game.Graphics.Cursor;
|
||||||
using osu.Game.Tournament.Models;
|
using osu.Game.Tournament.Models;
|
||||||
|
using osu.Game.Graphics;
|
||||||
|
using osuTK;
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
|
|
||||||
namespace osu.Game.Tournament
|
namespace osu.Game.Tournament
|
||||||
@ -21,19 +29,87 @@ namespace osu.Game.Tournament
|
|||||||
public static readonly Color4 ELEMENT_FOREGROUND_COLOUR = Color4Extensions.FromHex("#000");
|
public static readonly Color4 ELEMENT_FOREGROUND_COLOUR = Color4Extensions.FromHex("#000");
|
||||||
|
|
||||||
public static readonly Color4 TEXT_COLOUR = Color4Extensions.FromHex("#fff");
|
public static readonly Color4 TEXT_COLOUR = Color4Extensions.FromHex("#fff");
|
||||||
|
private Drawable heightWarning;
|
||||||
|
private Bindable<Size> windowSize;
|
||||||
|
|
||||||
protected override void LoadComplete()
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(FrameworkConfigManager frameworkConfig)
|
||||||
{
|
{
|
||||||
base.LoadComplete();
|
windowSize = frameworkConfig.GetBindable<Size>(FrameworkSetting.WindowedSize);
|
||||||
|
windowSize.BindValueChanged(size => ScheduleAfterChildren(() =>
|
||||||
|
{
|
||||||
|
var minWidth = (int)(size.NewValue.Height / 768f * TournamentSceneManager.REQUIRED_WIDTH) - 1;
|
||||||
|
|
||||||
Add(new OsuContextMenuContainer
|
heightWarning.Alpha = size.NewValue.Width < minWidth ? 1 : 0;
|
||||||
|
}), true);
|
||||||
|
|
||||||
|
AddRange(new[]
|
||||||
|
{
|
||||||
|
new Container
|
||||||
|
{
|
||||||
|
CornerRadius = 10,
|
||||||
|
Depth = float.MinValue,
|
||||||
|
Position = new Vector2(5),
|
||||||
|
Masking = true,
|
||||||
|
AutoSizeAxes = Axes.Both,
|
||||||
|
Anchor = Anchor.BottomRight,
|
||||||
|
Origin = Anchor.BottomRight,
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new Box
|
||||||
|
{
|
||||||
|
Colour = OsuColour.Gray(0.2f),
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
},
|
||||||
|
new TourneyButton
|
||||||
|
{
|
||||||
|
Text = "Save Changes",
|
||||||
|
Width = 140,
|
||||||
|
Height = 50,
|
||||||
|
Padding = new MarginPadding
|
||||||
|
{
|
||||||
|
Top = 10,
|
||||||
|
Left = 10,
|
||||||
|
},
|
||||||
|
Margin = new MarginPadding
|
||||||
|
{
|
||||||
|
Right = 10,
|
||||||
|
Bottom = 10,
|
||||||
|
},
|
||||||
|
Action = SaveChanges,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
heightWarning = new Container
|
||||||
|
{
|
||||||
|
Masking = true,
|
||||||
|
CornerRadius = 5,
|
||||||
|
Depth = float.MinValue,
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
AutoSizeAxes = Axes.Both,
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new Box
|
||||||
|
{
|
||||||
|
Colour = Color4.Red,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
},
|
||||||
|
new TournamentSpriteText
|
||||||
|
{
|
||||||
|
Text = "Please make the window wider",
|
||||||
|
Font = OsuFont.Torus.With(weight: FontWeight.Bold),
|
||||||
|
Colour = Color4.White,
|
||||||
|
Padding = new MarginPadding(20)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
new OsuContextMenuContainer
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Child = new TournamentSceneManager()
|
Child = new TournamentSceneManager()
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// we don't want to show the menu cursor as it would appear on stream output.
|
|
||||||
MenuCursorContainer.Cursor.Alpha = 0;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,34 +2,25 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Drawing;
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
|
||||||
using osu.Framework.Configuration;
|
|
||||||
using osu.Framework.Graphics;
|
|
||||||
using osu.Framework.Graphics.Containers;
|
|
||||||
using osu.Framework.Graphics.Shapes;
|
|
||||||
using osu.Framework.Graphics.Textures;
|
using osu.Framework.Graphics.Textures;
|
||||||
using osu.Framework.Input;
|
using osu.Framework.Input;
|
||||||
using osu.Framework.IO.Stores;
|
using osu.Framework.IO.Stores;
|
||||||
using osu.Framework.Platform;
|
using osu.Framework.Platform;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Graphics;
|
|
||||||
using osu.Game.Online.API.Requests;
|
using osu.Game.Online.API.Requests;
|
||||||
using osu.Game.Tournament.IPC;
|
using osu.Game.Tournament.IPC;
|
||||||
using osu.Game.Tournament.Models;
|
using osu.Game.Tournament.Models;
|
||||||
using osu.Game.Users;
|
using osu.Game.Users;
|
||||||
using osuTK;
|
|
||||||
using osuTK.Graphics;
|
|
||||||
using osuTK.Input;
|
using osuTK.Input;
|
||||||
|
|
||||||
namespace osu.Game.Tournament
|
namespace osu.Game.Tournament
|
||||||
{
|
{
|
||||||
[Cached(typeof(TournamentGameBase))]
|
[Cached(typeof(TournamentGameBase))]
|
||||||
public abstract class TournamentGameBase : OsuGameBase
|
public class TournamentGameBase : OsuGameBase
|
||||||
{
|
{
|
||||||
private const string bracket_filename = "bracket.json";
|
private const string bracket_filename = "bracket.json";
|
||||||
|
|
||||||
@ -40,19 +31,15 @@ namespace osu.Game.Tournament
|
|||||||
private TournamentStorage tournamentStorage;
|
private TournamentStorage tournamentStorage;
|
||||||
|
|
||||||
private DependencyContainer dependencies;
|
private DependencyContainer dependencies;
|
||||||
|
|
||||||
private Bindable<Size> windowSize;
|
|
||||||
private FileBasedIPC ipc;
|
private FileBasedIPC ipc;
|
||||||
|
|
||||||
private Drawable heightWarning;
|
|
||||||
|
|
||||||
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
|
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
|
||||||
{
|
{
|
||||||
return dependencies = new DependencyContainer(base.CreateChildDependencies(parent));
|
return dependencies = new DependencyContainer(base.CreateChildDependencies(parent));
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(Storage storage, FrameworkConfigManager frameworkConfig)
|
private void load(Storage storage)
|
||||||
{
|
{
|
||||||
Resources.AddStore(new DllResourceStore(typeof(TournamentGameBase).Assembly));
|
Resources.AddStore(new DllResourceStore(typeof(TournamentGameBase).Assembly));
|
||||||
|
|
||||||
@ -62,83 +49,12 @@ namespace osu.Game.Tournament
|
|||||||
|
|
||||||
this.storage = storage;
|
this.storage = storage;
|
||||||
|
|
||||||
windowSize = frameworkConfig.GetBindable<Size>(FrameworkSetting.WindowedSize);
|
|
||||||
windowSize.BindValueChanged(size => ScheduleAfterChildren(() =>
|
|
||||||
{
|
|
||||||
var minWidth = (int)(size.NewValue.Height / 768f * TournamentSceneManager.REQUIRED_WIDTH) - 1;
|
|
||||||
|
|
||||||
heightWarning.Alpha = size.NewValue.Width < minWidth ? 1 : 0;
|
|
||||||
}), true);
|
|
||||||
|
|
||||||
readBracket();
|
readBracket();
|
||||||
|
|
||||||
ladder.CurrentMatch.Value = ladder.Matches.FirstOrDefault(p => p.Current.Value);
|
ladder.CurrentMatch.Value = ladder.Matches.FirstOrDefault(p => p.Current.Value);
|
||||||
|
|
||||||
dependencies.CacheAs<MatchIPCInfo>(ipc = new FileBasedIPC());
|
dependencies.CacheAs<MatchIPCInfo>(ipc = new FileBasedIPC());
|
||||||
Add(ipc);
|
Add(ipc);
|
||||||
|
|
||||||
AddRange(new[]
|
|
||||||
{
|
|
||||||
new Container
|
|
||||||
{
|
|
||||||
CornerRadius = 10,
|
|
||||||
Depth = float.MinValue,
|
|
||||||
Position = new Vector2(5),
|
|
||||||
Masking = true,
|
|
||||||
AutoSizeAxes = Axes.Both,
|
|
||||||
Anchor = Anchor.BottomRight,
|
|
||||||
Origin = Anchor.BottomRight,
|
|
||||||
Children = new Drawable[]
|
|
||||||
{
|
|
||||||
new Box
|
|
||||||
{
|
|
||||||
Colour = OsuColour.Gray(0.2f),
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
},
|
|
||||||
new TourneyButton
|
|
||||||
{
|
|
||||||
Text = "Save Changes",
|
|
||||||
Width = 140,
|
|
||||||
Height = 50,
|
|
||||||
Padding = new MarginPadding
|
|
||||||
{
|
|
||||||
Top = 10,
|
|
||||||
Left = 10,
|
|
||||||
},
|
|
||||||
Margin = new MarginPadding
|
|
||||||
{
|
|
||||||
Right = 10,
|
|
||||||
Bottom = 10,
|
|
||||||
},
|
|
||||||
Action = SaveChanges,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
heightWarning = new Container
|
|
||||||
{
|
|
||||||
Masking = true,
|
|
||||||
CornerRadius = 5,
|
|
||||||
Depth = float.MinValue,
|
|
||||||
Anchor = Anchor.Centre,
|
|
||||||
Origin = Anchor.Centre,
|
|
||||||
AutoSizeAxes = Axes.Both,
|
|
||||||
Children = new Drawable[]
|
|
||||||
{
|
|
||||||
new Box
|
|
||||||
{
|
|
||||||
Colour = Color4.Red,
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
},
|
|
||||||
new TournamentSpriteText
|
|
||||||
{
|
|
||||||
Text = "Please make the window wider",
|
|
||||||
Font = OsuFont.Torus.With(weight: FontWeight.Bold),
|
|
||||||
Colour = Color4.White,
|
|
||||||
Padding = new MarginPadding(20)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void readBracket()
|
private void readBracket()
|
||||||
@ -150,11 +66,8 @@ namespace osu.Game.Tournament
|
|||||||
ladder = JsonConvert.DeserializeObject<LadderInfo>(sr.ReadToEnd());
|
ladder = JsonConvert.DeserializeObject<LadderInfo>(sr.ReadToEnd());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ladder == null)
|
ladder ??= new LadderInfo();
|
||||||
ladder = new LadderInfo();
|
ladder.Ruleset.Value ??= RulesetStore.AvailableRulesets.First();
|
||||||
|
|
||||||
if (ladder.Ruleset.Value == null)
|
|
||||||
ladder.Ruleset.Value = RulesetStore.AvailableRulesets.First();
|
|
||||||
|
|
||||||
Ruleset.BindTo(ladder.Ruleset);
|
Ruleset.BindTo(ladder.Ruleset);
|
||||||
|
|
||||||
@ -316,6 +229,8 @@ namespace osu.Game.Tournament
|
|||||||
protected override void LoadComplete()
|
protected override void LoadComplete()
|
||||||
{
|
{
|
||||||
MenuCursorContainer.Cursor.AlwaysPresent = true; // required for tooltip display
|
MenuCursorContainer.Cursor.AlwaysPresent = true; // required for tooltip display
|
||||||
|
|
||||||
|
// we don't want to show the menu cursor as it would appear on stream output.
|
||||||
MenuCursorContainer.Cursor.Alpha = 0;
|
MenuCursorContainer.Cursor.Alpha = 0;
|
||||||
|
|
||||||
base.LoadComplete();
|
base.LoadComplete();
|
||||||
|
@ -79,6 +79,8 @@ namespace osu.Game.Beatmaps
|
|||||||
beatmaps = (BeatmapStore)ModelStore;
|
beatmaps = (BeatmapStore)ModelStore;
|
||||||
beatmaps.BeatmapHidden += b => beatmapHidden.Value = new WeakReference<BeatmapInfo>(b);
|
beatmaps.BeatmapHidden += b => beatmapHidden.Value = new WeakReference<BeatmapInfo>(b);
|
||||||
beatmaps.BeatmapRestored += b => beatmapRestored.Value = new WeakReference<BeatmapInfo>(b);
|
beatmaps.BeatmapRestored += b => beatmapRestored.Value = new WeakReference<BeatmapInfo>(b);
|
||||||
|
beatmaps.ItemRemoved += removeWorkingCache;
|
||||||
|
beatmaps.ItemUpdated += removeWorkingCache;
|
||||||
|
|
||||||
onlineLookupQueue = new BeatmapOnlineLookupQueue(api, storage);
|
onlineLookupQueue = new BeatmapOnlineLookupQueue(api, storage);
|
||||||
}
|
}
|
||||||
@ -203,12 +205,17 @@ namespace osu.Game.Beatmaps
|
|||||||
|
|
||||||
stream.Seek(0, SeekOrigin.Begin);
|
stream.Seek(0, SeekOrigin.Begin);
|
||||||
|
|
||||||
|
using (ContextFactory.GetForWrite())
|
||||||
|
{
|
||||||
|
var beatmapInfo = setInfo.Beatmaps.Single(b => b.ID == info.ID);
|
||||||
|
beatmapInfo.MD5Hash = stream.ComputeMD5Hash();
|
||||||
|
|
||||||
|
stream.Seek(0, SeekOrigin.Begin);
|
||||||
UpdateFile(setInfo, setInfo.Files.Single(f => string.Equals(f.Filename, info.Path, StringComparison.OrdinalIgnoreCase)), stream);
|
UpdateFile(setInfo, setInfo.Files.Single(f => string.Equals(f.Filename, info.Path, StringComparison.OrdinalIgnoreCase)), stream);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var working = workingCache.FirstOrDefault(w => w.BeatmapInfo?.ID == info.ID);
|
removeWorkingCache(info);
|
||||||
if (working != null)
|
|
||||||
workingCache.Remove(working);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly WeakList<WorkingBeatmap> workingCache = new WeakList<WorkingBeatmap>();
|
private readonly WeakList<WorkingBeatmap> workingCache = new WeakList<WorkingBeatmap>();
|
||||||
@ -239,8 +246,7 @@ namespace osu.Game.Beatmaps
|
|||||||
|
|
||||||
if (working == null)
|
if (working == null)
|
||||||
{
|
{
|
||||||
if (beatmapInfo.Metadata == null)
|
beatmapInfo.Metadata ??= beatmapInfo.BeatmapSet.Metadata;
|
||||||
beatmapInfo.Metadata = beatmapInfo.BeatmapSet.Metadata;
|
|
||||||
|
|
||||||
workingCache.Add(working = new BeatmapManagerWorkingBeatmap(Files.Store,
|
workingCache.Add(working = new BeatmapManagerWorkingBeatmap(Files.Store,
|
||||||
new LargeTextureStore(host?.CreateTextureLoaderStore(Files.Store)), beatmapInfo, audioManager));
|
new LargeTextureStore(host?.CreateTextureLoaderStore(Files.Store)), beatmapInfo, audioManager));
|
||||||
@ -258,9 +264,9 @@ namespace osu.Game.Beatmaps
|
|||||||
/// <returns>The first result for the provided query, or null if no results were found.</returns>
|
/// <returns>The first result for the provided query, or null if no results were found.</returns>
|
||||||
public BeatmapSetInfo QueryBeatmapSet(Expression<Func<BeatmapSetInfo, bool>> query) => beatmaps.ConsumableItems.AsNoTracking().FirstOrDefault(query);
|
public BeatmapSetInfo QueryBeatmapSet(Expression<Func<BeatmapSetInfo, bool>> query) => beatmaps.ConsumableItems.AsNoTracking().FirstOrDefault(query);
|
||||||
|
|
||||||
protected override bool CanUndelete(BeatmapSetInfo existing, BeatmapSetInfo import)
|
protected override bool CanReuseExisting(BeatmapSetInfo existing, BeatmapSetInfo import)
|
||||||
{
|
{
|
||||||
if (!base.CanUndelete(existing, import))
|
if (!base.CanReuseExisting(existing, import))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
var existingIds = existing.Beatmaps.Select(b => b.OnlineBeatmapID).OrderBy(i => i);
|
var existingIds = existing.Beatmaps.Select(b => b.OnlineBeatmapID).OrderBy(i => i);
|
||||||
@ -410,6 +416,24 @@ namespace osu.Game.Beatmaps
|
|||||||
return endTime - startTime;
|
return endTime - startTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void removeWorkingCache(BeatmapSetInfo info)
|
||||||
|
{
|
||||||
|
if (info.Beatmaps == null) return;
|
||||||
|
|
||||||
|
foreach (var b in info.Beatmaps)
|
||||||
|
removeWorkingCache(b);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void removeWorkingCache(BeatmapInfo info)
|
||||||
|
{
|
||||||
|
lock (workingCache)
|
||||||
|
{
|
||||||
|
var working = workingCache.FirstOrDefault(w => w.BeatmapInfo?.ID == info.ID);
|
||||||
|
if (working != null)
|
||||||
|
workingCache.Remove(working);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
onlineLookupQueue?.Dispose();
|
onlineLookupQueue?.Dispose();
|
||||||
|
@ -42,7 +42,7 @@ namespace osu.Game.Beatmaps
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private string getPathForFile(string filename) => BeatmapSetInfo.Files.FirstOrDefault(f => string.Equals(f.Filename, filename, StringComparison.OrdinalIgnoreCase))?.FileInfo.StoragePath;
|
private string getPathForFile(string filename) => BeatmapSetInfo.Files.SingleOrDefault(f => string.Equals(f.Filename, filename, StringComparison.OrdinalIgnoreCase))?.FileInfo.StoragePath;
|
||||||
|
|
||||||
private TextureStore textureStore;
|
private TextureStore textureStore;
|
||||||
|
|
||||||
|
@ -425,8 +425,7 @@ namespace osu.Game.Beatmaps.Formats
|
|||||||
private void handleHitObject(string line)
|
private void handleHitObject(string line)
|
||||||
{
|
{
|
||||||
// If the ruleset wasn't specified, assume the osu!standard ruleset.
|
// If the ruleset wasn't specified, assume the osu!standard ruleset.
|
||||||
if (parser == null)
|
parser ??= new Rulesets.Objects.Legacy.Osu.ConvertHitObjectParser(getOffsetTime(), FormatVersion);
|
||||||
parser = new Rulesets.Objects.Legacy.Osu.ConvertHitObjectParser(getOffsetTime(), FormatVersion);
|
|
||||||
|
|
||||||
var obj = parser.Parse(line);
|
var obj = parser.Parse(line);
|
||||||
if (obj != null)
|
if (obj != null)
|
||||||
|
@ -276,7 +276,7 @@ namespace osu.Game.Database
|
|||||||
// for now, concatenate all .osu files in the set to create a unique hash.
|
// for now, concatenate all .osu files in the set to create a unique hash.
|
||||||
MemoryStream hashable = new MemoryStream();
|
MemoryStream hashable = new MemoryStream();
|
||||||
|
|
||||||
foreach (TFileModel file in item.Files.Where(f => HashableFileTypes.Any(f.Filename.EndsWith)))
|
foreach (TFileModel file in item.Files.Where(f => HashableFileTypes.Any(f.Filename.EndsWith)).OrderBy(f => f.Filename))
|
||||||
{
|
{
|
||||||
using (Stream s = Files.Store.GetStream(file.FileInfo.StoragePath))
|
using (Stream s = Files.Store.GetStream(file.FileInfo.StoragePath))
|
||||||
s.CopyTo(hashable);
|
s.CopyTo(hashable);
|
||||||
@ -332,7 +332,7 @@ namespace osu.Game.Database
|
|||||||
|
|
||||||
if (existing != null)
|
if (existing != null)
|
||||||
{
|
{
|
||||||
if (CanUndelete(existing, item))
|
if (CanReuseExisting(existing, item))
|
||||||
{
|
{
|
||||||
Undelete(existing);
|
Undelete(existing);
|
||||||
LogForModel(item, $"Found existing {HumanisedModelName} for {item} (ID {existing.ID}) – skipping import.");
|
LogForModel(item, $"Found existing {HumanisedModelName} for {item} (ID {existing.ID}) – skipping import.");
|
||||||
@ -429,7 +429,6 @@ namespace osu.Game.Database
|
|||||||
using (ContextFactory.GetForWrite())
|
using (ContextFactory.GetForWrite())
|
||||||
{
|
{
|
||||||
item.Hash = computeHash(item);
|
item.Hash = computeHash(item);
|
||||||
|
|
||||||
ModelStore.Update(item);
|
ModelStore.Update(item);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -660,13 +659,29 @@ namespace osu.Game.Database
|
|||||||
protected TModel CheckForExisting(TModel model) => model.Hash == null ? null : ModelStore.ConsumableItems.FirstOrDefault(b => b.Hash == model.Hash);
|
protected TModel CheckForExisting(TModel model) => model.Hash == null ? null : ModelStore.ConsumableItems.FirstOrDefault(b => b.Hash == model.Hash);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// After an existing <typeparamref name="TModel"/> is found during an import process, the default behaviour is to restore the existing
|
/// After an existing <typeparamref name="TModel"/> is found during an import process, the default behaviour is to use/restore the existing
|
||||||
/// item and skip the import. This method allows changing that behaviour.
|
/// item and skip the import. This method allows changing that behaviour.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="existing">The existing model.</param>
|
/// <param name="existing">The existing model.</param>
|
||||||
/// <param name="import">The newly imported model.</param>
|
/// <param name="import">The newly imported model.</param>
|
||||||
/// <returns>Whether the existing model should be restored and used. Returning false will delete the existing and force a re-import.</returns>
|
/// <returns>Whether the existing model should be restored and used. Returning false will delete the existing and force a re-import.</returns>
|
||||||
protected virtual bool CanUndelete(TModel existing, TModel import) => true;
|
protected virtual bool CanReuseExisting(TModel existing, TModel import) =>
|
||||||
|
// for the best or worst, we copy and import files of a new import before checking whether
|
||||||
|
// it is a duplicate. so to check if anything has changed, we can just compare all FileInfo IDs.
|
||||||
|
getIDs(existing.Files).SequenceEqual(getIDs(import.Files)) &&
|
||||||
|
getFilenames(existing.Files).SequenceEqual(getFilenames(import.Files));
|
||||||
|
|
||||||
|
private IEnumerable<long> getIDs(List<TFileModel> files)
|
||||||
|
{
|
||||||
|
foreach (var f in files.OrderBy(f => f.Filename))
|
||||||
|
yield return f.FileInfo.ID;
|
||||||
|
}
|
||||||
|
|
||||||
|
private IEnumerable<string> getFilenames(List<TFileModel> files)
|
||||||
|
{
|
||||||
|
foreach (var f in files.OrderBy(f => f.Filename))
|
||||||
|
yield return f.Filename;
|
||||||
|
}
|
||||||
|
|
||||||
private DbSet<TModel> queryModel() => ContextFactory.Get().Set<TModel>();
|
private DbSet<TModel> queryModel() => ContextFactory.Get().Set<TModel>();
|
||||||
|
|
||||||
|
@ -17,6 +17,8 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
{
|
{
|
||||||
private readonly SpriteIcon spinner;
|
private readonly SpriteIcon spinner;
|
||||||
|
|
||||||
|
protected override bool StartHidden => true;
|
||||||
|
|
||||||
protected Container MainContents;
|
protected Container MainContents;
|
||||||
|
|
||||||
public const float TRANSITION_DURATION = 500;
|
public const float TRANSITION_DURATION = 500;
|
||||||
|
@ -11,23 +11,13 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class TernaryStateMenuItem : StatefulMenuItem<TernaryState>
|
public class TernaryStateMenuItem : StatefulMenuItem<TernaryState>
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// Creates a new <see cref="TernaryStateMenuItem"/>.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="text">The text to display.</param>
|
|
||||||
/// <param name="type">The type of action which this <see cref="TernaryStateMenuItem"/> performs.</param>
|
|
||||||
public TernaryStateMenuItem(string text, MenuItemType type = MenuItemType.Standard)
|
|
||||||
: this(text, type, null)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a new <see cref="TernaryStateMenuItem"/>.
|
/// Creates a new <see cref="TernaryStateMenuItem"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="text">The text to display.</param>
|
/// <param name="text">The text to display.</param>
|
||||||
/// <param name="type">The type of action which this <see cref="TernaryStateMenuItem"/> performs.</param>
|
/// <param name="type">The type of action which this <see cref="TernaryStateMenuItem"/> performs.</param>
|
||||||
/// <param name="action">A delegate to be invoked when this <see cref="TernaryStateMenuItem"/> is pressed.</param>
|
/// <param name="action">A delegate to be invoked when this <see cref="TernaryStateMenuItem"/> is pressed.</param>
|
||||||
public TernaryStateMenuItem(string text, MenuItemType type, Action<TernaryState> action)
|
public TernaryStateMenuItem(string text, MenuItemType type = MenuItemType.Standard, Action<TernaryState> action = null)
|
||||||
: this(text, getNextState, type, action)
|
: this(text, getNextState, type, action)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
@ -41,12 +41,24 @@ namespace osu.Game.IO.Serialization.Converters
|
|||||||
var list = new List<T>();
|
var list = new List<T>();
|
||||||
|
|
||||||
var obj = JObject.Load(reader);
|
var obj = JObject.Load(reader);
|
||||||
|
|
||||||
|
if (obj["$lookup_table"] == null)
|
||||||
|
return list;
|
||||||
|
|
||||||
var lookupTable = serializer.Deserialize<List<string>>(obj["$lookup_table"].CreateReader());
|
var lookupTable = serializer.Deserialize<List<string>>(obj["$lookup_table"].CreateReader());
|
||||||
|
if (lookupTable == null)
|
||||||
|
return list;
|
||||||
|
|
||||||
|
if (obj["$items"] == null)
|
||||||
|
return list;
|
||||||
|
|
||||||
foreach (var tok in obj["$items"])
|
foreach (var tok in obj["$items"])
|
||||||
{
|
{
|
||||||
var itemReader = tok.CreateReader();
|
var itemReader = tok.CreateReader();
|
||||||
|
|
||||||
|
if (tok["$type"] == null)
|
||||||
|
throw new JsonException("Expected $type token.");
|
||||||
|
|
||||||
var typeName = lookupTable[(int)tok["$type"]];
|
var typeName = lookupTable[(int)tok["$type"]];
|
||||||
var instance = (T)Activator.CreateInstance(Type.GetType(typeName));
|
var instance = (T)Activator.CreateInstance(Type.GetType(typeName));
|
||||||
serializer.Populate(itemReader, instance);
|
serializer.Populate(itemReader, instance);
|
||||||
|
@ -11,6 +11,7 @@ using System.Threading.Tasks;
|
|||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Extensions.ExceptionExtensions;
|
using osu.Framework.Extensions.ExceptionExtensions;
|
||||||
|
using osu.Framework.Extensions.ObjectExtensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Logging;
|
using osu.Framework.Logging;
|
||||||
using osu.Game.Configuration;
|
using osu.Game.Configuration;
|
||||||
@ -250,7 +251,7 @@ namespace osu.Game.Online.API
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
return JObject.Parse(req.GetResponseString()).SelectToken("form_error", true).ToObject<RegistrationRequest.RegistrationRequestErrors>();
|
return JObject.Parse(req.GetResponseString()).SelectToken("form_error", true).AsNonNull().ToObject<RegistrationRequest.RegistrationRequestErrors>();
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
|
23
osu.Game/Online/API/APIPlaylistBeatmap.cs
Normal file
23
osu.Game/Online/API/APIPlaylistBeatmap.cs
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Online.API.Requests.Responses;
|
||||||
|
using osu.Game.Rulesets;
|
||||||
|
|
||||||
|
namespace osu.Game.Online.API
|
||||||
|
{
|
||||||
|
public class APIPlaylistBeatmap : APIBeatmap
|
||||||
|
{
|
||||||
|
[JsonProperty("checksum")]
|
||||||
|
public string Checksum { get; set; }
|
||||||
|
|
||||||
|
public override BeatmapInfo ToBeatmap(RulesetStore rulesets)
|
||||||
|
{
|
||||||
|
var b = base.ToBeatmap(rulesets);
|
||||||
|
b.MD5Hash = Checksum;
|
||||||
|
return b;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
34
osu.Game/Online/API/Requests/CreateChannelRequest.cs
Normal file
34
osu.Game/Online/API/Requests/CreateChannelRequest.cs
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Linq;
|
||||||
|
using System.Net.Http;
|
||||||
|
using osu.Framework.IO.Network;
|
||||||
|
using osu.Game.Online.API.Requests.Responses;
|
||||||
|
using osu.Game.Online.Chat;
|
||||||
|
|
||||||
|
namespace osu.Game.Online.API.Requests
|
||||||
|
{
|
||||||
|
public class CreateChannelRequest : APIRequest<APIChatChannel>
|
||||||
|
{
|
||||||
|
private readonly Channel channel;
|
||||||
|
|
||||||
|
public CreateChannelRequest(Channel channel)
|
||||||
|
{
|
||||||
|
this.channel = channel;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override WebRequest CreateWebRequest()
|
||||||
|
{
|
||||||
|
var req = base.CreateWebRequest();
|
||||||
|
req.Method = HttpMethod.Post;
|
||||||
|
|
||||||
|
req.AddParameter("type", $"{ChannelType.PM}");
|
||||||
|
req.AddParameter("target_id", $"{channel.Users.First().Id}");
|
||||||
|
|
||||||
|
return req;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override string Target => @"chat/channels";
|
||||||
|
}
|
||||||
|
}
|
28
osu.Game/Online/API/Requests/GetRoomPlaylistScoresRequest.cs
Normal file
28
osu.Game/Online/API/Requests/GetRoomPlaylistScoresRequest.cs
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
namespace osu.Game.Online.API.Requests
|
||||||
|
{
|
||||||
|
public class GetRoomPlaylistScoresRequest : APIRequest<RoomPlaylistScores>
|
||||||
|
{
|
||||||
|
private readonly int roomId;
|
||||||
|
private readonly int playlistItemId;
|
||||||
|
|
||||||
|
public GetRoomPlaylistScoresRequest(int roomId, int playlistItemId)
|
||||||
|
{
|
||||||
|
this.roomId = roomId;
|
||||||
|
this.playlistItemId = playlistItemId;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override string Target => $@"rooms/{roomId}/playlist/{playlistItemId}/scores";
|
||||||
|
}
|
||||||
|
|
||||||
|
public class RoomPlaylistScores
|
||||||
|
{
|
||||||
|
[JsonProperty("scores")]
|
||||||
|
public List<RoomScore> Scores { get; set; }
|
||||||
|
}
|
||||||
|
}
|
@ -64,7 +64,7 @@ namespace osu.Game.Online.API.Requests.Responses
|
|||||||
[JsonProperty(@"max_combo")]
|
[JsonProperty(@"max_combo")]
|
||||||
private int? maxCombo { get; set; }
|
private int? maxCombo { get; set; }
|
||||||
|
|
||||||
public BeatmapInfo ToBeatmap(RulesetStore rulesets)
|
public virtual BeatmapInfo ToBeatmap(RulesetStore rulesets)
|
||||||
{
|
{
|
||||||
var set = BeatmapSet?.ToBeatmapSet(rulesets);
|
var set = BeatmapSet?.ToBeatmapSet(rulesets);
|
||||||
|
|
||||||
|
18
osu.Game/Online/API/Requests/Responses/APIChatChannel.cs
Normal file
18
osu.Game/Online/API/Requests/Responses/APIChatChannel.cs
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using osu.Game.Online.Chat;
|
||||||
|
|
||||||
|
namespace osu.Game.Online.API.Requests.Responses
|
||||||
|
{
|
||||||
|
public class APIChatChannel
|
||||||
|
{
|
||||||
|
[JsonProperty(@"channel_id")]
|
||||||
|
public int? ChannelID { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty(@"recent_messages")]
|
||||||
|
public List<Message> RecentMessages { get; set; }
|
||||||
|
}
|
||||||
|
}
|
@ -8,7 +8,7 @@ using osu.Game.Scoring;
|
|||||||
|
|
||||||
namespace osu.Game.Online.API.Requests
|
namespace osu.Game.Online.API.Requests
|
||||||
{
|
{
|
||||||
public class SubmitRoomScoreRequest : APIRequest
|
public class SubmitRoomScoreRequest : APIRequest<RoomScore>
|
||||||
{
|
{
|
||||||
private readonly int scoreId;
|
private readonly int scoreId;
|
||||||
private readonly int roomId;
|
private readonly int roomId;
|
||||||
|
75
osu.Game/Online/API/RoomScore.cs
Normal file
75
osu.Game/Online/API/RoomScore.cs
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using Newtonsoft.Json.Converters;
|
||||||
|
using osu.Game.Online.Multiplayer;
|
||||||
|
using osu.Game.Rulesets.Mods;
|
||||||
|
using osu.Game.Rulesets.Scoring;
|
||||||
|
using osu.Game.Scoring;
|
||||||
|
using osu.Game.Users;
|
||||||
|
|
||||||
|
namespace osu.Game.Online.API
|
||||||
|
{
|
||||||
|
public class RoomScore
|
||||||
|
{
|
||||||
|
[JsonProperty("id")]
|
||||||
|
public int ID { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("user")]
|
||||||
|
public User User { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("rank")]
|
||||||
|
[JsonConverter(typeof(StringEnumConverter))]
|
||||||
|
public ScoreRank Rank { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("total_score")]
|
||||||
|
public long TotalScore { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("accuracy")]
|
||||||
|
public double Accuracy { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("max_combo")]
|
||||||
|
public int MaxCombo { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("mods")]
|
||||||
|
public APIMod[] Mods { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("statistics")]
|
||||||
|
public Dictionary<HitResult, int> Statistics = new Dictionary<HitResult, int>();
|
||||||
|
|
||||||
|
[JsonProperty("passed")]
|
||||||
|
public bool Passed { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("ended_at")]
|
||||||
|
public DateTimeOffset EndedAt { get; set; }
|
||||||
|
|
||||||
|
public ScoreInfo CreateScoreInfo(PlaylistItem playlistItem)
|
||||||
|
{
|
||||||
|
var rulesetInstance = playlistItem.Ruleset.Value.CreateInstance();
|
||||||
|
|
||||||
|
var scoreInfo = new ScoreInfo
|
||||||
|
{
|
||||||
|
OnlineScoreID = ID,
|
||||||
|
TotalScore = TotalScore,
|
||||||
|
MaxCombo = MaxCombo,
|
||||||
|
Beatmap = playlistItem.Beatmap.Value,
|
||||||
|
BeatmapInfoID = playlistItem.BeatmapID,
|
||||||
|
Ruleset = playlistItem.Ruleset.Value,
|
||||||
|
RulesetID = playlistItem.RulesetID,
|
||||||
|
Statistics = Statistics,
|
||||||
|
User = User,
|
||||||
|
Accuracy = Accuracy,
|
||||||
|
Date = EndedAt,
|
||||||
|
Hash = string.Empty, // todo: temporary?
|
||||||
|
Rank = Rank,
|
||||||
|
Mods = Mods?.Select(m => m.ToMod(rulesetInstance)).ToArray() ?? Array.Empty<Mod>()
|
||||||
|
};
|
||||||
|
|
||||||
|
return scoreInfo;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -84,7 +84,8 @@ namespace osu.Game.Online.Chat
|
|||||||
public long? LastReadId;
|
public long? LastReadId;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Signalles if the current user joined this channel or not. Defaults to false.
|
/// Signals if the current user joined this channel or not. Defaults to false.
|
||||||
|
/// Note that this does not guarantee a join has completed. Check Id > 0 for confirmation.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Bindable<bool> Joined = new Bindable<bool>();
|
public Bindable<bool> Joined = new Bindable<bool>();
|
||||||
|
|
||||||
|
@ -86,19 +86,13 @@ namespace osu.Game.Online.Chat
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
CurrentChannel.Value = JoinedChannels.FirstOrDefault(c => c.Type == ChannelType.PM && c.Users.Count == 1 && c.Users.Any(u => u.Id == user.Id))
|
CurrentChannel.Value = JoinedChannels.FirstOrDefault(c => c.Type == ChannelType.PM && c.Users.Count == 1 && c.Users.Any(u => u.Id == user.Id))
|
||||||
?? new Channel(user);
|
?? JoinChannel(new Channel(user));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void currentChannelChanged(ValueChangedEvent<Channel> e)
|
private void currentChannelChanged(ValueChangedEvent<Channel> e)
|
||||||
{
|
{
|
||||||
if (!(e.NewValue is ChannelSelectorTabItem.ChannelSelectorTabChannel))
|
if (!(e.NewValue is ChannelSelectorTabItem.ChannelSelectorTabChannel))
|
||||||
JoinChannel(e.NewValue);
|
JoinChannel(e.NewValue);
|
||||||
|
|
||||||
if (e.NewValue?.MessagesLoaded == false)
|
|
||||||
{
|
|
||||||
// let's fetch a small number of messages to bring us up-to-date with the backlog.
|
|
||||||
fetchInitalMessages(e.NewValue);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -114,8 +108,7 @@ namespace osu.Game.Online.Chat
|
|||||||
/// <param name="target">An optional target channel. If null, <see cref="CurrentChannel"/> will be used.</param>
|
/// <param name="target">An optional target channel. If null, <see cref="CurrentChannel"/> will be used.</param>
|
||||||
public void PostMessage(string text, bool isAction = false, Channel target = null)
|
public void PostMessage(string text, bool isAction = false, Channel target = null)
|
||||||
{
|
{
|
||||||
if (target == null)
|
target ??= CurrentChannel.Value;
|
||||||
target = CurrentChannel.Value;
|
|
||||||
|
|
||||||
if (target == null)
|
if (target == null)
|
||||||
return;
|
return;
|
||||||
@ -146,7 +139,7 @@ namespace osu.Game.Online.Chat
|
|||||||
target.AddLocalEcho(message);
|
target.AddLocalEcho(message);
|
||||||
|
|
||||||
// if this is a PM and the first message, we need to do a special request to create the PM channel
|
// if this is a PM and the first message, we need to do a special request to create the PM channel
|
||||||
if (target.Type == ChannelType.PM && !target.Joined.Value)
|
if (target.Type == ChannelType.PM && target.Id == 0)
|
||||||
{
|
{
|
||||||
var createNewPrivateMessageRequest = new CreateNewPrivateMessageRequest(target.Users.First(), message);
|
var createNewPrivateMessageRequest = new CreateNewPrivateMessageRequest(target.Users.First(), message);
|
||||||
|
|
||||||
@ -198,8 +191,7 @@ namespace osu.Game.Online.Chat
|
|||||||
/// <param name="target">An optional target channel. If null, <see cref="CurrentChannel"/> will be used.</param>
|
/// <param name="target">An optional target channel. If null, <see cref="CurrentChannel"/> will be used.</param>
|
||||||
public void PostCommand(string text, Channel target = null)
|
public void PostCommand(string text, Channel target = null)
|
||||||
{
|
{
|
||||||
if (target == null)
|
target ??= CurrentChannel.Value;
|
||||||
target = CurrentChannel.Value;
|
|
||||||
|
|
||||||
if (target == null)
|
if (target == null)
|
||||||
return;
|
return;
|
||||||
@ -240,7 +232,6 @@ namespace osu.Game.Online.Chat
|
|||||||
}
|
}
|
||||||
|
|
||||||
JoinChannel(channel);
|
JoinChannel(channel);
|
||||||
CurrentChannel.Value = channel;
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "help":
|
case "help":
|
||||||
@ -275,7 +266,7 @@ namespace osu.Game.Online.Chat
|
|||||||
|
|
||||||
// join any channels classified as "defaults"
|
// join any channels classified as "defaults"
|
||||||
if (joinDefaults && defaultChannels.Any(c => c.Equals(channel.Name, StringComparison.OrdinalIgnoreCase)))
|
if (joinDefaults && defaultChannels.Any(c => c.Equals(channel.Name, StringComparison.OrdinalIgnoreCase)))
|
||||||
JoinChannel(ch);
|
joinChannel(ch);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
req.Failure += error =>
|
req.Failure += error =>
|
||||||
@ -296,7 +287,7 @@ namespace osu.Game.Online.Chat
|
|||||||
/// <param name="channel">The channel </param>
|
/// <param name="channel">The channel </param>
|
||||||
private void fetchInitalMessages(Channel channel)
|
private void fetchInitalMessages(Channel channel)
|
||||||
{
|
{
|
||||||
if (channel.Id <= 0) return;
|
if (channel.Id <= 0 || channel.MessagesLoaded) return;
|
||||||
|
|
||||||
var fetchInitialMsgReq = new GetMessagesRequest(channel);
|
var fetchInitialMsgReq = new GetMessagesRequest(channel);
|
||||||
fetchInitialMsgReq.Success += messages =>
|
fetchInitialMsgReq.Success += messages =>
|
||||||
@ -351,9 +342,10 @@ namespace osu.Game.Online.Chat
|
|||||||
/// Joins a channel if it has not already been joined.
|
/// Joins a channel if it has not already been joined.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="channel">The channel to join.</param>
|
/// <param name="channel">The channel to join.</param>
|
||||||
/// <param name="alreadyJoined">Whether the channel has already been joined server-side. Will skip a join request.</param>
|
|
||||||
/// <returns>The joined channel. Note that this may not match the parameter channel as it is a backed object.</returns>
|
/// <returns>The joined channel. Note that this may not match the parameter channel as it is a backed object.</returns>
|
||||||
public Channel JoinChannel(Channel channel, bool alreadyJoined = false)
|
public Channel JoinChannel(Channel channel) => joinChannel(channel, true);
|
||||||
|
|
||||||
|
private Channel joinChannel(Channel channel, bool fetchInitialMessages = false)
|
||||||
{
|
{
|
||||||
if (channel == null) return null;
|
if (channel == null) return null;
|
||||||
|
|
||||||
@ -362,24 +354,47 @@ namespace osu.Game.Online.Chat
|
|||||||
// ensure we are joined to the channel
|
// ensure we are joined to the channel
|
||||||
if (!channel.Joined.Value)
|
if (!channel.Joined.Value)
|
||||||
{
|
{
|
||||||
if (alreadyJoined)
|
|
||||||
channel.Joined.Value = true;
|
channel.Joined.Value = true;
|
||||||
else
|
|
||||||
{
|
|
||||||
switch (channel.Type)
|
switch (channel.Type)
|
||||||
{
|
{
|
||||||
case ChannelType.Public:
|
case ChannelType.Multiplayer:
|
||||||
|
// join is implicit. happens when you join a multiplayer game.
|
||||||
|
// this will probably change in the future.
|
||||||
|
joinChannel(channel, fetchInitialMessages);
|
||||||
|
return channel;
|
||||||
|
|
||||||
|
case ChannelType.PM:
|
||||||
|
var createRequest = new CreateChannelRequest(channel);
|
||||||
|
createRequest.Success += resChannel =>
|
||||||
|
{
|
||||||
|
if (resChannel.ChannelID.HasValue)
|
||||||
|
{
|
||||||
|
channel.Id = resChannel.ChannelID.Value;
|
||||||
|
|
||||||
|
handleChannelMessages(resChannel.RecentMessages);
|
||||||
|
channel.MessagesLoaded = true; // this will mark the channel as having received messages even if there were none.
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
api.Queue(createRequest);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
var req = new JoinChannelRequest(channel, api.LocalUser.Value);
|
var req = new JoinChannelRequest(channel, api.LocalUser.Value);
|
||||||
req.Success += () => JoinChannel(channel, true);
|
req.Success += () => joinChannel(channel, fetchInitialMessages);
|
||||||
req.Failure += ex => LeaveChannel(channel);
|
req.Failure += ex => LeaveChannel(channel);
|
||||||
api.Queue(req);
|
api.Queue(req);
|
||||||
return channel;
|
return channel;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (fetchInitialMessages)
|
||||||
|
fetchInitalMessages(channel);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (CurrentChannel.Value == null)
|
CurrentChannel.Value ??= channel;
|
||||||
CurrentChannel.Value = channel;
|
|
||||||
|
|
||||||
return channel;
|
return channel;
|
||||||
}
|
}
|
||||||
@ -420,7 +435,8 @@ namespace osu.Game.Online.Chat
|
|||||||
foreach (var channel in updates.Presence)
|
foreach (var channel in updates.Presence)
|
||||||
{
|
{
|
||||||
// we received this from the server so should mark the channel already joined.
|
// we received this from the server so should mark the channel already joined.
|
||||||
JoinChannel(channel, true);
|
channel.Joined.Value = true;
|
||||||
|
joinChannel(channel);
|
||||||
}
|
}
|
||||||
|
|
||||||
//todo: handle left channels
|
//todo: handle left channels
|
||||||
|
@ -73,8 +73,7 @@ namespace osu.Game.Online.Chat
|
|||||||
[BackgroundDependencyLoader(true)]
|
[BackgroundDependencyLoader(true)]
|
||||||
private void load(ChannelManager manager)
|
private void load(ChannelManager manager)
|
||||||
{
|
{
|
||||||
if (ChannelManager == null)
|
ChannelManager ??= manager;
|
||||||
ChannelManager = manager;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual StandAloneDrawableChannel CreateDrawableChannel(Channel channel) =>
|
protected virtual StandAloneDrawableChannel CreateDrawableChannel(Channel channel) =>
|
||||||
|
@ -7,7 +7,6 @@ using Newtonsoft.Json;
|
|||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Online.API;
|
using osu.Game.Online.API;
|
||||||
using osu.Game.Online.API.Requests.Responses;
|
|
||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
|
|
||||||
@ -37,7 +36,7 @@ namespace osu.Game.Online.Multiplayer
|
|||||||
public readonly BindableList<Mod> RequiredMods = new BindableList<Mod>();
|
public readonly BindableList<Mod> RequiredMods = new BindableList<Mod>();
|
||||||
|
|
||||||
[JsonProperty("beatmap")]
|
[JsonProperty("beatmap")]
|
||||||
private APIBeatmap apiBeatmap { get; set; }
|
private APIPlaylistBeatmap apiBeatmap { get; set; }
|
||||||
|
|
||||||
private APIMod[] allowedModsBacking;
|
private APIMod[] allowedModsBacking;
|
||||||
|
|
||||||
|
@ -164,7 +164,7 @@ namespace osu.Game
|
|||||||
dependencies.Cache(SkinManager = new SkinManager(Storage, contextFactory, Host, Audio, new NamespacedResourceStore<byte[]>(Resources, "Skins/Legacy")));
|
dependencies.Cache(SkinManager = new SkinManager(Storage, contextFactory, Host, Audio, new NamespacedResourceStore<byte[]>(Resources, "Skins/Legacy")));
|
||||||
dependencies.CacheAs<ISkinSource>(SkinManager);
|
dependencies.CacheAs<ISkinSource>(SkinManager);
|
||||||
|
|
||||||
if (API == null) API = new APIAccess(LocalConfig);
|
API ??= new APIAccess(LocalConfig);
|
||||||
|
|
||||||
dependencies.CacheAs(API);
|
dependencies.CacheAs(API);
|
||||||
|
|
||||||
@ -311,11 +311,10 @@ namespace osu.Game
|
|||||||
{
|
{
|
||||||
base.SetHost(host);
|
base.SetHost(host);
|
||||||
|
|
||||||
if (Storage == null) // may be non-null for certain tests
|
// may be non-null for certain tests
|
||||||
Storage = new OsuStorage(host);
|
Storage ??= new OsuStorage(host);
|
||||||
|
|
||||||
if (LocalConfig == null)
|
LocalConfig ??= new OsuConfigManager(Storage);
|
||||||
LocalConfig = new OsuConfigManager(Storage);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly List<ICanAcceptFiles> fileImporters = new List<ICanAcceptFiles>();
|
private readonly List<ICanAcceptFiles> fileImporters = new List<ICanAcceptFiles>();
|
||||||
|
@ -68,8 +68,7 @@ namespace osu.Game.Overlays.Chat.Tabs
|
|||||||
if (!Items.Contains(channel))
|
if (!Items.Contains(channel))
|
||||||
AddItem(channel);
|
AddItem(channel);
|
||||||
|
|
||||||
if (Current.Value == null)
|
Current.Value ??= channel;
|
||||||
Current.Value = channel;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -274,6 +274,9 @@ namespace osu.Game.Overlays.KeyBinding
|
|||||||
|
|
||||||
private void clear()
|
private void clear()
|
||||||
{
|
{
|
||||||
|
if (bindTarget == null)
|
||||||
|
return;
|
||||||
|
|
||||||
bindTarget.UpdateKeyCombination(InputKey.None);
|
bindTarget.UpdateKeyCombination(InputKey.None);
|
||||||
finalise();
|
finalise();
|
||||||
}
|
}
|
||||||
@ -333,7 +336,7 @@ namespace osu.Game.Overlays.KeyBinding
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class ClearButton : TriangleButton
|
public class ClearButton : TriangleButton
|
||||||
{
|
{
|
||||||
public ClearButton()
|
public ClearButton()
|
||||||
{
|
{
|
||||||
|
@ -87,11 +87,11 @@ namespace osu.Game.Rulesets.Edit
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Updates the position of this <see cref="PlacementBlueprint"/> to a new screen-space position.
|
/// Updates the position of this <see cref="PlacementBlueprint"/> to a new screen-space position.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="snapResult">The snap result information.</param>
|
/// <param name="result">The snap result information.</param>
|
||||||
public virtual void UpdatePosition(SnapResult snapResult)
|
public virtual void UpdatePosition(SnapResult result)
|
||||||
{
|
{
|
||||||
if (!PlacementActive)
|
if (!PlacementActive)
|
||||||
HitObject.StartTime = snapResult.Time ?? EditorClock?.CurrentTime ?? Time.Current;
|
HitObject.StartTime = result.Time ?? EditorClock?.CurrentTime ?? Time.Current;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -35,7 +35,9 @@ namespace osu.Game.Rulesets.Mods
|
|||||||
|
|
||||||
private BindableNumber<double> health;
|
private BindableNumber<double> health;
|
||||||
|
|
||||||
public void ReadFromDifficulty(BeatmapDifficulty difficulty) { }
|
public void ReadFromDifficulty(BeatmapDifficulty difficulty)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
public void ApplyToDifficulty(BeatmapDifficulty difficulty)
|
public void ApplyToDifficulty(BeatmapDifficulty difficulty)
|
||||||
{
|
{
|
||||||
|
@ -17,7 +17,9 @@ namespace osu.Game.Rulesets.Mods
|
|||||||
public override string Description => "Everything just got a bit harder...";
|
public override string Description => "Everything just got a bit harder...";
|
||||||
public override Type[] IncompatibleMods => new[] { typeof(ModEasy), typeof(ModDifficultyAdjust) };
|
public override Type[] IncompatibleMods => new[] { typeof(ModEasy), typeof(ModDifficultyAdjust) };
|
||||||
|
|
||||||
public void ReadFromDifficulty(BeatmapDifficulty difficulty) { }
|
public void ReadFromDifficulty(BeatmapDifficulty difficulty)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
public void ApplyToDifficulty(BeatmapDifficulty difficulty)
|
public void ApplyToDifficulty(BeatmapDifficulty difficulty)
|
||||||
{
|
{
|
||||||
|
@ -133,8 +133,7 @@ namespace osu.Game.Rulesets.Objects
|
|||||||
{
|
{
|
||||||
Kiai = controlPointInfo.EffectPointAt(StartTime + control_point_leniency).KiaiMode;
|
Kiai = controlPointInfo.EffectPointAt(StartTime + control_point_leniency).KiaiMode;
|
||||||
|
|
||||||
if (HitWindows == null)
|
HitWindows ??= CreateHitWindows();
|
||||||
HitWindows = CreateHitWindows();
|
|
||||||
HitWindows?.SetDifficulty(difficulty.OverallDifficulty);
|
HitWindows?.SetDifficulty(difficulty.OverallDifficulty);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -11,6 +11,7 @@ namespace osu.Game.Rulesets.Objects
|
|||||||
public static class SliderEventGenerator
|
public static class SliderEventGenerator
|
||||||
{
|
{
|
||||||
[Obsolete("Use the overload with cancellation support instead.")] // can be removed 20201115
|
[Obsolete("Use the overload with cancellation support instead.")] // can be removed 20201115
|
||||||
|
// ReSharper disable once RedundantOverload.Global
|
||||||
public static IEnumerable<SliderEventDescriptor> Generate(double startTime, double spanDuration, double velocity, double tickDistance, double totalDistance, int spanCount,
|
public static IEnumerable<SliderEventDescriptor> Generate(double startTime, double spanDuration, double velocity, double tickDistance, double totalDistance, int spanCount,
|
||||||
double? legacyLastTickOffset)
|
double? legacyLastTickOffset)
|
||||||
{
|
{
|
||||||
|
@ -22,6 +22,7 @@ using osu.Game.Rulesets.Scoring;
|
|||||||
using osu.Game.Scoring;
|
using osu.Game.Scoring;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
using osu.Game.Users;
|
using osu.Game.Users;
|
||||||
|
using JetBrains.Annotations;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets
|
namespace osu.Game.Rulesets
|
||||||
{
|
{
|
||||||
@ -100,7 +101,8 @@ namespace osu.Game.Rulesets
|
|||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ModAutoplay GetAutoplayMod() => GetAllMods().OfType<ModAutoplay>().First();
|
[CanBeNull]
|
||||||
|
public ModAutoplay GetAutoplayMod() => GetAllMods().OfType<ModAutoplay>().FirstOrDefault();
|
||||||
|
|
||||||
public virtual ISkin CreateLegacySkinProvider(ISkinSource source, IBeatmap beatmap) => null;
|
public virtual ISkin CreateLegacySkinProvider(ISkinSource source, IBeatmap beatmap) => null;
|
||||||
|
|
||||||
|
@ -6,6 +6,7 @@ using System.Collections.Generic;
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
using osu.Framework;
|
||||||
using osu.Framework.Logging;
|
using osu.Framework.Logging;
|
||||||
using osu.Framework.Platform;
|
using osu.Framework.Platform;
|
||||||
using osu.Game.Database;
|
using osu.Game.Database;
|
||||||
@ -153,14 +154,14 @@ namespace osu.Game.Rulesets
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
string[] files = Directory.GetFiles(Environment.CurrentDirectory, $"{ruleset_library_prefix}.*.dll");
|
var files = Directory.GetFiles(RuntimeInfo.StartupDirectory, $"{ruleset_library_prefix}.*.dll");
|
||||||
|
|
||||||
foreach (string file in files.Where(f => !Path.GetFileName(f).Contains("Tests")))
|
foreach (string file in files.Where(f => !Path.GetFileName(f).Contains("Tests")))
|
||||||
loadRulesetFromFile(file);
|
loadRulesetFromFile(file);
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
Logger.Error(e, $"Could not load rulesets from directory {Environment.CurrentDirectory}");
|
Logger.Error(e, $"Could not load rulesets from directory {RuntimeInfo.StartupDirectory}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user