71 Commits

Author SHA1 Message Date
fb7ccba4ba bump to 2025.1209.0-lazer 2025-12-09 20:18:13 +03:00
490a6fd724 fix out of range exception in changelog overlay 2025-12-08 17:34:27 +03:00
237e1828f8 add option for playing miss sound on any combo break, make...
exit & restart game options in fail condition mods mutually exclusive
2025-12-07 13:14:38 +03:00
3413f722f7 make volume meter use argon counter (it looks cool) 2025-12-06 23:52:20 +03:00
9f779dac03 forgot to disable christmas intro after testing 2025-12-06 21:57:44 +03:00
0727c53cdc bump to 2025.12.05-lazer 2025-12-06 21:52:53 +03:00
a57ff24191 bump to 2025.1203.0-tachyon, add no intro option, slightly change seasonal bg code 2025-12-06 21:51:48 +03:00
bdb3418b67 fix useless DB versioning in release builds
Idk why, but in my first commit here I just commented out the lines
restricting DB schema suffixes to debug builds only, and before all that
mess there was a "TODO: fix".
I'm only doing this for sake of tools like BeatmapExporter and to not
clog up disk space when newer schema versions arrive.
This should work well with existing installations (hopefully)
2025-12-02 20:15:00 +03:00
936640edeb update some configs for iOS 2025-11-21 19:04:31 +03:00
1187d03333 prepare repo for github ci/cd (mostly) 2025-11-21 18:59:41 +03:00
3bd996ee43 synchronize with github (tag 2025.1119.0-tachyon) 2025-11-19 15:13:25 +03:00
37b9f91d42 make discord rich presence work 2025-11-19 14:03:57 +03:00
1a5a5606dc don't log that we're running an unofficial build 2025-11-19 12:02:27 +03:00
87ff1051e9 set up sentry (glitchtip) logging properly 2025-11-18 23:27:57 +03:00
2e4b0ff197 make score cards for failed scores look better 2025-11-18 15:33:37 +03:00
499f410c94 slightly changed warnings for old windows versions 2025-11-18 14:44:34 +03:00
616c0d8ecd make key counters show proper readable key names 2025-11-18 14:24:05 +03:00
ed889138a0 break countdown now uses argon-style counter 2025-11-18 13:23:44 +03:00
18075bef29 display score retractions in recent activity 2025-11-17 21:28:03 +03:00
bc7780a870 updated the first players cutoff date for user profiles 2025-11-17 20:55:42 +03:00
8930b8fadb synchronize with github (tag 2025.1105.0) 2025-11-17 20:41:55 +03:00
2d457f4305 use online status of beatmap instead of mapset 2025-11-17 20:14:23 +03:00
a784734f94 fix up a couple strings 2025-11-17 20:10:54 +03:00
ecfd4764e7 fixed a blunder 2025-11-16 22:29:25 +03:00
f5ca5083d6 implement pp for legacy song select 2025-11-16 21:21:12 +03:00
1d7c77d8d6 add performance points to selectv2 beatmap info wedge 2025-11-16 20:49:27 +03:00
774e52fbd6 slight result screen score panel redesign 2025-11-16 18:52:04 +03:00
a9d7a9d5d5 score panel is now tinted, plus some other changes 2025-11-16 02:48:41 +03:00
f7069b1009 minor fixes for some mods 2025-11-16 02:47:38 +03:00
c3ce5dc787 add argon-style longest combo counter 2025-11-15 19:24:49 +03:00
98076e2092 add skinnable online status and star rating components 2025-11-15 19:03:14 +03:00
b7d1092f90 Merge branch 'master' of https://gitea.jvnko.boats/jvnkosu/client 2025-11-15 17:02:04 +03:00
08db90c278 some minor hud changes
- argon-style cps counter
- keybinds in key counters (for now, only default)
2025-11-15 16:45:14 +03:00
b7e36164c3 update issue templates 2025-11-14 22:22:43 +01:00
0f5f13858d exit game option for fail condition mods (SD, PF, AC) 2025-11-13 21:41:09 +03:00
89a0c75156 all user-playable mods are now always ranked 2025-11-12 20:53:07 +03:00
ab7e5c94f1 make autoupdates work, at last 2025-11-11 19:11:36 +03:00
8dc9ea4553 add startup disclaimer 2025-11-11 18:14:38 +03:00
dcf553c252 Revert "Remove disclaimer screen completely"
This reverts commit bd0e2b4dde.
2025-11-11 16:45:04 +03:00
43f3a506ea new icon + default logo color 2025-11-11 00:04:18 +03:00
ab51579c27 added quit w/ replay button to pause menu; minor visual changes for ranks 2025-11-10 22:39:10 +03:00
d8e977c05f minor changes to mod scoring, all mods are ranked now
Probably all user-playable mods are ranked by default now,
Mania key mods were reverted to 1.0x score multiplier
2025-11-10 18:52:07 +03:00
c023767df9 bump to 2025.1031.0-tachyon + misc changes for debug builds 2025-11-08 16:26:06 +03:00
d47e26f1ae now reproducible! 2025-09-12 23:44:01 +03:00
b0e5ae1109 some minor very UI changes 2025-09-12 22:39:07 +03:00
47b859ab17 l10n: replace "osu!" with "game" where suitable 2025-09-12 21:59:58 +03:00
5e523eb0b1 bump to 2025.912.0 2025-09-12 18:10:03 +03:00
Dean Herbert
d912f8c3b9 Lazer release 2025-09
For tagging purposes.
2025-09-12 16:30:29 +09:00
65f275106e update license information in various places (and readme) 2025-08-31 22:47:36 +03:00
6e374762fd synchronize with github 2025-08-31 21:26:21 +03:00
80646a166c add settings toggle to not delete imported archives 2025-08-31 19:39:13 +03:00
8f0510d903 made beatmap anonymization code a bit less awful 2025-08-31 15:06:24 +03:00
02e7000ee4 update editor beatmap anonymization code to update not just first diff 2025-08-30 22:56:01 +03:00
081355864e add beatmap editor option to remove online data of a map 2025-08-29 20:19:39 +03:00
6435a835d1 fix background category selection (this time for real) 2025-08-29 18:48:13 +03:00
628181a883 also update colour on load 2025-08-27 01:11:57 +03:00
835329efd3 synchronize with github 2025-08-27 00:59:45 +03:00
5399943118 update logo colour only when changing setting value 2025-08-27 00:58:26 +03:00
d07f82f6f4 refactor custom seasonal background code
some of it may be trauma-inducing, but I don't know how to make it
better
2025-08-27 00:43:26 +03:00
5b186bb740 potentially make logo look less weird (untested)
uh, yeah, I accidentally flipped the colors around in the UpdateColour() method (which I should've probably make private or protected), and it's the reason why the logo overall looked dimmer than it should've

anyhow, this should *probably* look a bit better, don't have any means to test it yet though
2025-08-25 12:46:27 +02:00
6cb99c13c2 added cookie color customization (which shouldn't have been done) 2025-08-24 19:46:36 +03:00
96008e06ab added settings toggle for song select v1 2025-08-24 05:42:59 +03:00
590b0a8028 welcome back select v1 2025-08-24 04:55:25 +03:00
70f7f09a83 synchronize with github (ppy/osu) 2025-08-23 18:11:07 +03:00
490137405f added installer build script, adapted autoupdates (not functional yet) 2025-08-22 22:26:17 +03:00
f3c6f53f70 added version to 'experimental version' banner 2025-08-22 20:19:18 +03:00
8cb5c682b4 make seasonal bg config strings localisable 2025-08-22 19:07:15 +03:00
c3d79295d3 made seasonal background notifications transient 2025-08-22 16:07:28 +03:00
f31d310135 small fixes (vibe-sleeping) 2025-08-22 04:17:25 +03:00
26029de27d Added customs wallpaper in a menu. 2025-08-22 03:10:55 +03:00
c37f72f567 jvnkosu initial bringup
Some checks failed
Continuous Integration / Code Quality (push) Has been cancelled
Continuous Integration / Test (map[fullname:ubuntu-latest prettyname:Linux], MultiThreaded) (push) Has been cancelled
Continuous Integration / Test (map[fullname:ubuntu-latest prettyname:Linux], SingleThread) (push) Has been cancelled
Continuous Integration / Test (map[fullname:windows-latest prettyname:Windows], MultiThreaded) (push) Has been cancelled
Continuous Integration / Test (map[fullname:windows-latest prettyname:Windows], SingleThread) (push) Has been cancelled
Continuous Integration / Build only (Android) (push) Has been cancelled
Continuous Integration / Build only (iOS) (push) Has been cancelled
2025-08-21 22:12:07 +03:00
158 changed files with 2509 additions and 680 deletions

View File

@@ -1,12 +1,6 @@
blank_issues_enabled: false
blank_issues_enabled: true
contact_links:
- name: Help
url: https://github.com/ppy/osu/discussions/categories/q-a
about: osu! not working or performing as you'd expect? Not sure it's a bug? Check the Q&A section!
- name: Suggestions or feature request
url: https://github.com/ppy/osu/discussions/categories/ideas
about: Got something you think should change or be added? Search for or start a new discussion!
- name: osu!stable issues
url: https://github.com/ppy/osu-stable-issues
about: For osu!(stable) - ie. the current "live" game version, check out the dedicated repository. Note that this is for serious bug reports only, not tech support.
url: https://t.me/jvnkosu_chat
about: Your jvnkosu! is not working right? Please contact us using our Telegram chat

View File

@@ -1,8 +1,10 @@
name: Update osu-web mod definitions
name: Update osu-web mod definitions (DO NOT USE YET!!!!!)
on:
push:
tags:
- '*'
workflow_dispatch:
# push:
# tags:
# - '*'
permissions:
contents: read # to fetch code (actions/checkout)

View File

@@ -1,4 +1,10 @@
on: [push, pull_request]
on:
push:
tags:
- '*'
workflow_dispatch:
name: Continuous Integration
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}

View File

@@ -4,6 +4,7 @@ on:
push:
tags:
- '*'
workflow_dispatch:
jobs:
notify_pending_production_deploy:
@@ -12,7 +13,7 @@ jobs:
- name: Submit pending deployment notification
run: |
export TITLE="Pending osu Production Deployment: $GITHUB_REF_NAME"
export URL="https://github.com/ppy/osu/actions/runs/$GITHUB_RUN_ID"
export URL="https://github.com/jvnkosu-dev/client/actions/runs/$GITHUB_RUN_ID"
export DESCRIPTION="Awaiting approval for building NuGet packages for tag $GITHUB_REF_NAME:
[View Workflow Run]($URL)"
export ACTOR_ICON="https://avatars.githubusercontent.com/u/$GITHUB_ACTOR_ID"

View File

@@ -23,7 +23,7 @@ jobs:
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
SENTRY_ORG: ppy
SENTRY_PROJECT: osu
SENTRY_URL: https://sentry.ppy.sh/
SENTRY_URL: https://satellite.jvnko.boats/
with:
environment: production
version: osu@${{ github.ref_name }}
version: jvnkosu@${{ github.ref_name }}

3
.gitignore vendored
View File

@@ -19,6 +19,7 @@ bld/
[Bb]in/
[Oo]bj/
[Ll]og/
[Pp]ub/
# Visual Studio 2015 cache/options directory
.vs/
@@ -343,4 +344,4 @@ inspectcode
FodyWeavers.xsd
.idea/.idea.osu.Desktop/.idea/misc.xml
.idea/.idea.osu.Android/.idea/deploymentTargetDropDown.xml
.idea/.idea.osu.Android/.idea/deploymentTargetDropDown.xml

36
.vscode/launch.json vendored
View File

@@ -7,9 +7,9 @@
"request": "launch",
"program": "dotnet",
"args": [
"${workspaceRoot}/osu.Desktop/bin/Debug/net8.0/osu!.dll"
"${workspaceFolder}/osu.Desktop/bin/Debug/net8.0/osu!.dll"
],
"cwd": "${workspaceRoot}",
"cwd": "${workspaceFolder}",
"preLaunchTask": "Build osu! (Debug)",
"console": "internalConsole"
},
@@ -19,9 +19,9 @@
"request": "launch",
"program": "dotnet",
"args": [
"${workspaceRoot}/osu.Desktop/bin/Release/net8.0/osu!.dll"
"${workspaceFolder}/osu.Desktop/bin/Release/net8.0/osu!.dll"
],
"cwd": "${workspaceRoot}",
"cwd": "${workspaceFolder}",
"preLaunchTask": "Build osu! (Release)",
"console": "internalConsole"
},
@@ -31,9 +31,9 @@
"request": "launch",
"program": "dotnet",
"args": [
"${workspaceRoot}/osu.Game.Tests/bin/Debug/net8.0/osu.Game.Tests.dll"
"${workspaceFolder}/osu.Game.Tests/bin/Debug/net8.0/osu.Game.Tests.dll"
],
"cwd": "${workspaceRoot}",
"cwd": "${workspaceFolder}",
"preLaunchTask": "Build tests (Debug)",
"console": "internalConsole"
},
@@ -43,9 +43,9 @@
"request": "launch",
"program": "dotnet",
"args": [
"${workspaceRoot}/osu.Game.Tests/bin/Release/net8.0/osu.Game.Tests.dll"
"${workspaceFolder}/osu.Game.Tests/bin/Release/net8.0/osu.Game.Tests.dll"
],
"cwd": "${workspaceRoot}",
"cwd": "${workspaceFolder}",
"preLaunchTask": "Build tests (Release)",
"console": "internalConsole"
},
@@ -55,10 +55,10 @@
"request": "launch",
"program": "dotnet",
"args": [
"${workspaceRoot}/osu.Desktop/bin/Debug/net8.0/osu!.dll",
"${workspaceFolder}/osu.Desktop/bin/Debug/net8.0/osu!.dll",
"--tournament"
],
"cwd": "${workspaceRoot}",
"cwd": "${workspaceFolder}",
"preLaunchTask": "Build osu! (Debug)",
"console": "internalConsole"
},
@@ -68,10 +68,10 @@
"request": "launch",
"program": "dotnet",
"args": [
"${workspaceRoot}/osu.Desktop/bin/Release/net8.0/osu!.dll",
"${workspaceFolder}/osu.Desktop/bin/Release/net8.0/osu!.dll",
"--tournament"
],
"cwd": "${workspaceRoot}",
"cwd": "${workspaceFolder}",
"preLaunchTask": "Build osu! (Release)",
"console": "internalConsole"
},
@@ -81,10 +81,10 @@
"request": "launch",
"program": "dotnet",
"args": [
"${workspaceRoot}/osu.Game.Tournament.Tests/bin/Debug/net8.0/osu.Game.Tournament.Tests.dll",
"${workspaceFolder}/osu.Game.Tournament.Tests/bin/Debug/net8.0/osu.Game.Tournament.Tests.dll",
"--tournament"
],
"cwd": "${workspaceRoot}",
"cwd": "${workspaceFolder}",
"preLaunchTask": "Build tournament tests (Debug)",
"console": "internalConsole"
},
@@ -94,10 +94,10 @@
"request": "launch",
"program": "dotnet",
"args": [
"${workspaceRoot}/osu.Game.Tournament.Tests/bin/Debug/net8.0/osu.Game.Tournament.Tests.dll",
"${workspaceFolder}/osu.Game.Tournament.Tests/bin/Debug/net8.0/osu.Game.Tournament.Tests.dll",
"--tournament"
],
"cwd": "${workspaceRoot}",
"cwd": "${workspaceFolder}",
"preLaunchTask": "Build tournament tests (Release)",
"console": "internalConsole"
},
@@ -105,12 +105,12 @@
"name": "Benchmark",
"type": "coreclr",
"request": "launch",
"program": "${workspaceRoot}/osu.Game.Benchmarks/bin/Release/net8.0/osu.Game.Benchmarks.dll",
"program": "${workspaceFolder}/osu.Game.Benchmarks/bin/Release/net8.0/osu.Game.Benchmarks.dll",
"args": [
"--filter",
"*"
],
"cwd": "${workspaceRoot}",
"cwd": "${workspaceFolder}",
"preLaunchTask": "Build benchmarks",
"console": "internalConsole"
}

3
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,3 @@
{
"dotnet.defaultSolution": "osu.Desktop.slnf"
}

View File

@@ -49,7 +49,7 @@
<PackageProjectUrl>https://github.com/ppy/osu</PackageProjectUrl>
<RepositoryUrl>https://github.com/ppy/osu</RepositoryUrl>
<PackageReleaseNotes>Automated release.</PackageReleaseNotes>
<Company>ppy Pty Ltd</Company>
<Company>ppy Pty Ltd, jvnkosu! team</Company>
<Copyright>Copyright (c) 2025 ppy Pty Ltd</Copyright>
<PackageTags>osu game</PackageTags>
</PropertyGroup>

19
MakeInstaller.ps1 Normal file
View File

@@ -0,0 +1,19 @@
#!/usr/bin/env powershell
param (
[string]$Version,
[string]$BuildConfig = "Release"
)
if ($Version -eq "") {
Write-Host "Usage: .\MakeInstaller.ps1 <VERSION_NUMBER> [-BuildConfig <BUILD_CONFIG>]"
Write-Host "Example: .\MakeInstaller.ps1 2025.823.0 -BuildConfig Debug"
exit
}
$tmpPub = ".\pub"
if (-not (Test-Path -Path $tmpPub)) {
New-Item -ItemType Directory -path $tmpPub
}
dotnet publish -c $BuildConfig osu.Desktop --self-contained -r win-x64 -o $tmpPub -verbosity:m /p:Version=$Version
vpk pack --packId jvnkosu.Client --packTitle "jvnkosu!lazer" --packVersion $Version --packDir ./pub --mainExe="osu!.exe"

149
README.md
View File

@@ -1,147 +1,40 @@
<p align="center">
<img width="500" alt="osu! logo" src="assets/lazer.png">
</p>
# jvnkosu! client
# osu!
A free-to-win rhythm game based on osu!(lazer). Click is just a *rhythm* away!
[![Build status](https://github.com/ppy/osu/actions/workflows/ci.yml/badge.svg?branch=master&event=push)](https://github.com/ppy/osu/actions/workflows/ci.yml)
[![GitHub release](https://img.shields.io/github/release/ppy/osu.svg)](https://github.com/ppy/osu/releases/latest)
[![CodeFactor](https://www.codefactor.io/repository/github/ppy/osu/badge)](https://www.codefactor.io/repository/github/ppy/osu)
[![dev chat](https://discordapp.com/api/guilds/188630481301012481/widget.png?style=shield)](https://discord.gg/ppy)
[![Crowdin](https://d322cqt584bo4o.cloudfront.net/osu-web/localized.svg)](https://crowdin.com/project/osu-web)
## Disclaimer
A free-to-win rhythm game. Rhythm is just a *click* away!
*osu!* is a registered trademark of ppy Pty Ltd.
jvnkosu! is not affiliated with, or endorsed by ppy Pty Ltd., but makes use of its open-source components and resources.
This is the future and final iteration of the [osu!](https://osu.ppy.sh) game client which marks the beginning of an open era! Currently known by and released under the release codename "*lazer*". As in sharper than cutting-edge.
## License
Client source code is licensed under the MIT license, see the [LICENCE](LICENCE) file in repository root for more info.
## Status
Game assets are included as a NuGet package and licensed under the CC BY-NC 4.0, which prohibits commercial use. See [ppy/osu-resources](https://github.com/ppy/osu-resources) for more info.
This project is under constant development, but we do our best to keep things in a stable state. Players are encouraged to install from a release alongside their stable *osu!* client. This project will continue to evolve until we eventually reach the point where most users prefer it over the previous "osu!stable" release.
Registered trademarks "osu!" and "ppy" are property of ppy Pty Ltd., and protected by trademark law.
A few resources are available as starting points to getting involved and understanding the project:
## Compiling from source
Building jvnkosu! from source is pretty much possible (and welcome here).
- Detailed release changelogs are available on the [official osu! site](https://osu.ppy.sh/home/changelog/lazer).
- You can learn more about our approach to [project management](https://github.com/ppy/osu/wiki/Project-management).
- Track our current efforts [towards improving the game](https://github.com/orgs/ppy/projects/7/views/6).
First, you must have a desktop platform with [.NET Core SDK 8](https://dotnet.microsoft.com/download) installed. Windows, Linux, macOS should work well. You can check if you have the SDK installed by running `dotnet --version` in your command prompt/terminal.
## Running osu!
If you are just looking to give the game a whirl, you can grab the latest release for your platform:
### Latest release:
| [Windows 10+ (x64)](https://github.com/ppy/osu/releases/latest/download/install.exe) | macOS 12+ ([Intel](https://github.com/ppy/osu/releases/latest/download/osu.app.Intel.zip), [Apple Silicon](https://github.com/ppy/osu/releases/latest/download/osu.app.Apple.Silicon.zip)) | [Linux (x64)](https://github.com/ppy/osu/releases/latest/download/osu.AppImage) | [iOS 13.4+](https://osu.ppy.sh/home/testflight) | [Android 5+](https://github.com/ppy/osu/releases/latest/download/sh.ppy.osulazer.apk) |
|--------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| ------------- | ------------- | ------------- |
You can also generally download a version for your current device from the [osu! site](https://osu.ppy.sh/home/download).
If your platform is unsupported or not listed above, there is still a chance you can run the release or manually build it by following the instructions below.
**For iOS/iPadOS users**: The iOS testflight link fills up very fast (Apple has a hard limit of 10,000 users). We reset it occasionally. Please do not ask about this. Check back regularly for link resets or follow [peppy](https://twitter.com/ppy) on twitter for announcements. Our goal is to get the game on mobile app stores very soon so we don't have to live with this limitation.
## Developing a custom ruleset
osu! is designed to allow user-created gameplay variations, called "rulesets". Building one of these allows a developer to harness the power of the osu! beatmap library, game engine, and general UX for a new style of gameplay. To get started working on a ruleset, we have some templates available [here](https://github.com/ppy/osu/tree/master/Templates).
You can see some examples of custom rulesets by visiting the [custom ruleset directory](https://github.com/ppy/osu/discussions/13096).
## Developing osu!
### Prerequisites
Please make sure you have the following prerequisites:
- A desktop platform with the [.NET 8.0 SDK](https://dotnet.microsoft.com/download) installed.
When working with the codebase, we recommend using an IDE with intelligent code completion and syntax highlighting, such as the latest version of [Visual Studio](https://visualstudio.microsoft.com/vs/), [JetBrains Rider](https://www.jetbrains.com/rider/), or [Visual Studio Code](https://code.visualstudio.com/) with the [EditorConfig](https://marketplace.visualstudio.com/items?itemName=EditorConfig.EditorConfig) and [C# Dev Kit](https://marketplace.visualstudio.com/items?itemName=ms-dotnettools.csdevkit) plugin installed.
### Downloading the source code
Clone the repository:
```shell
git clone https://github.com/ppy/osu
cd osu
Then, download the source code. You may download it as an archive and unzip it, but using [Git](https://git-scm.com/) instead is recommended:
```
git clone https://gitea.jvnko.boats/jvnkosu/client
```
To update the source code to the latest commit, run the following command inside the `osu` directory:
```shell
git pull
To **run** the project, switch to project's directory and run the following:
```
### Building
#### From an IDE
You should load the solution via one of the platform-specific `.slnf` files, rather than the main `.sln`. This will reduce dependencies and hide platforms that you don't care about. Valid `.slnf` files are:
- `osu.Desktop.slnf` (most common)
- `osu.Android.slnf`
- `osu.iOS.slnf`
Run configurations for the recommended IDEs (listed above) are included. You should use the provided Build/Run functionality of your IDE to get things going. When testing or building new components, it's highly encouraged you use the `osu! (Tests)` project/configuration. More information on this is provided [below](#contributing).
To build for mobile platforms, you will likely need to run `sudo dotnet workload restore` if you haven't done so previously. This will install Android/iOS tooling required to complete the build.
#### From CLI
You can also build and run *osu!* from the command-line with a single command:
```shell
dotnet run --project osu.Desktop
```
When running locally to do any kind of performance testing, make sure to add `-c Release` to the build command, as the overhead of running with the default `Debug` configuration can be large (especially when testing with local framework modifications as below).
If the build fails, try to restore NuGet packages with `dotnet restore`.
### Testing with resource/framework modifications
Sometimes it may be necessary to cross-test changes in [osu-resources](https://github.com/ppy/osu-resources) or [osu-framework](https://github.com/ppy/osu-framework). This can be quickly achieved using included commands:
Windows:
```ps
UseLocalFramework.ps1
UseLocalResources.ps1
To **compile**:
```
dotnet build osu.Desktop
```
macOS / Linux:
To reduce performance overhead in custom builds, it's recommended to build with the `-c Release` flag, that will use the release profile and remove possibly unneeded debugging code.
```ps
UseLocalFramework.sh
UseLocalResources.sh
```
Note that these commands assume you have the relevant project(s) checked out in adjacent directories:
```
|- osu // this repository
|- osu-framework
|- osu-resources
```
### Code analysis
Before committing your code, please run a code formatter. This can be achieved by running `dotnet format` in the command line, or using the `Format code` command in your IDE.
We have adopted some cross-platform, compiler integrated analyzers. They can provide warnings when you are editing, building inside IDE or from command line, as-if they are provided by the compiler itself.
JetBrains ReSharper InspectCode is also used for wider rule sets. You can run it from PowerShell with `.\InspectCode.ps1`. Alternatively, you can install ReSharper or use Rider to get inline support in your IDE of choice.
## Contributing
When it comes to contributing to the project, the two main things you can do to help out are reporting issues and submitting pull requests. Please refer to the [contributing guidelines](CONTRIBUTING.md) to understand how to help in the most effective way possible.
If you wish to help with localisation efforts, head over to [crowdin](https://crowdin.com/project/osu-web).
We love to reward quality contributions. If you have made a large contribution, or are a regular contributor, you are welcome to [submit an expense via opencollective](https://opencollective.com/ppy/expenses/new). If you have any questions, feel free to [reach out to peppy](mailto:pe@ppy.sh) before doing so.
## Licence
*osu!*'s code and framework are licensed under the [MIT licence](https://opensource.org/licenses/MIT). Please see [the licence file](LICENCE) for more information. [tl;dr](https://tldrlegal.com/license/mit-license) you can do whatever you want as long as you include the original copyright and license notice in any copy of the software/source.
Please note that this *does not cover* the usage of the "osu!" or "ppy" branding in any software, resources, advertising or promotion, as this is protected by trademark law.
Please also note that game resources are covered by a separate licence. Please see the [ppy/osu-resources](https://github.com/ppy/osu-resources) repository for clarifications.
### See the [original readme](README.original.md) for more info.

147
README.original.md Normal file
View File

@@ -0,0 +1,147 @@
<p align="center">
<img width="500" alt="osu! logo" src="assets/lazer.png">
</p>
# osu!
[![Build status](https://github.com/ppy/osu/actions/workflows/ci.yml/badge.svg?branch=master&event=push)](https://github.com/ppy/osu/actions/workflows/ci.yml)
[![GitHub release](https://img.shields.io/github/release/ppy/osu.svg)](https://github.com/ppy/osu/releases/latest)
[![CodeFactor](https://www.codefactor.io/repository/github/ppy/osu/badge)](https://www.codefactor.io/repository/github/ppy/osu)
[![dev chat](https://discordapp.com/api/guilds/188630481301012481/widget.png?style=shield)](https://discord.gg/ppy)
[![Crowdin](https://d322cqt584bo4o.cloudfront.net/osu-web/localized.svg)](https://crowdin.com/project/osu-web)
A free-to-win rhythm game. Rhythm is just a *click* away!
This is the future and final iteration of the [osu!](https://osu.ppy.sh) game client which marks the beginning of an open era! Currently known by and released under the release codename "*lazer*". As in sharper than cutting-edge.
## Status
This project is under constant development, but we do our best to keep things in a stable state. Players are encouraged to install from a release alongside their stable *osu!* client. This project will continue to evolve until we eventually reach the point where most users prefer it over the previous "osu!stable" release.
A few resources are available as starting points to getting involved and understanding the project:
- Detailed release changelogs are available on the [official osu! site](https://osu.ppy.sh/home/changelog/lazer).
- You can learn more about our approach to [project management](https://github.com/ppy/osu/wiki/Project-management).
- Track our current efforts [towards improving the game](https://github.com/orgs/ppy/projects/7/views/6).
## Running osu!
If you are just looking to give the game a whirl, you can grab the latest release for your platform:
### Latest release:
| [Windows 10+ (x64)](https://github.com/ppy/osu/releases/latest/download/install.exe) | macOS 12+ ([Intel](https://github.com/ppy/osu/releases/latest/download/osu.app.Intel.zip), [Apple Silicon](https://github.com/ppy/osu/releases/latest/download/osu.app.Apple.Silicon.zip)) | [Linux (x64)](https://github.com/ppy/osu/releases/latest/download/osu.AppImage) | [iOS 13.4+](https://osu.ppy.sh/home/testflight) | [Android 5+](https://github.com/ppy/osu/releases/latest/download/sh.ppy.osulazer.apk) |
|--------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| ------------- | ------------- | ------------- |
You can also generally download a version for your current device from the [osu! site](https://osu.ppy.sh/home/download).
If your platform is unsupported or not listed above, there is still a chance you can run the release or manually build it by following the instructions below.
**For iOS/iPadOS users**: The iOS testflight link fills up very fast (Apple has a hard limit of 10,000 users). We reset it occasionally. Please do not ask about this. Check back regularly for link resets or follow [peppy](https://twitter.com/ppy) on twitter for announcements. Our goal is to get the game on mobile app stores very soon so we don't have to live with this limitation.
## Developing a custom ruleset
osu! is designed to allow user-created gameplay variations, called "rulesets". Building one of these allows a developer to harness the power of the osu! beatmap library, game engine, and general UX for a new style of gameplay. To get started working on a ruleset, we have some templates available [here](https://github.com/ppy/osu/tree/master/Templates).
You can see some examples of custom rulesets by visiting the [custom ruleset directory](https://github.com/ppy/osu/discussions/13096).
## Developing osu!
### Prerequisites
Please make sure you have the following prerequisites:
- A desktop platform with the [.NET 8.0 SDK](https://dotnet.microsoft.com/download) installed.
When working with the codebase, we recommend using an IDE with intelligent code completion and syntax highlighting, such as the latest version of [Visual Studio](https://visualstudio.microsoft.com/vs/), [JetBrains Rider](https://www.jetbrains.com/rider/), or [Visual Studio Code](https://code.visualstudio.com/) with the [EditorConfig](https://marketplace.visualstudio.com/items?itemName=EditorConfig.EditorConfig) and [C# Dev Kit](https://marketplace.visualstudio.com/items?itemName=ms-dotnettools.csdevkit) plugin installed.
### Downloading the source code
Clone the repository:
```shell
git clone https://github.com/ppy/osu
cd osu
```
To update the source code to the latest commit, run the following command inside the `osu` directory:
```shell
git pull
```
### Building
#### From an IDE
You should load the solution via one of the platform-specific `.slnf` files, rather than the main `.sln`. This will reduce dependencies and hide platforms that you don't care about. Valid `.slnf` files are:
- `osu.Desktop.slnf` (most common)
- `osu.Android.slnf`
- `osu.iOS.slnf`
Run configurations for the recommended IDEs (listed above) are included. You should use the provided Build/Run functionality of your IDE to get things going. When testing or building new components, it's highly encouraged you use the `osu! (Tests)` project/configuration. More information on this is provided [below](#contributing).
To build for mobile platforms, you will likely need to run `sudo dotnet workload restore` if you haven't done so previously. This will install Android/iOS tooling required to complete the build.
#### From CLI
You can also build and run *osu!* from the command-line with a single command:
```shell
dotnet run --project osu.Desktop
```
When running locally to do any kind of performance testing, make sure to add `-c Release` to the build command, as the overhead of running with the default `Debug` configuration can be large (especially when testing with local framework modifications as below).
If the build fails, try to restore NuGet packages with `dotnet restore`.
### Testing with resource/framework modifications
Sometimes it may be necessary to cross-test changes in [osu-resources](https://github.com/ppy/osu-resources) or [osu-framework](https://github.com/ppy/osu-framework). This can be quickly achieved using included commands:
Windows:
```ps
UseLocalFramework.ps1
UseLocalResources.ps1
```
macOS / Linux:
```ps
UseLocalFramework.sh
UseLocalResources.sh
```
Note that these commands assume you have the relevant project(s) checked out in adjacent directories:
```
|- osu // this repository
|- osu-framework
|- osu-resources
```
### Code analysis
Before committing your code, please run a code formatter. This can be achieved by running `dotnet format` in the command line, or using the `Format code` command in your IDE.
We have adopted some cross-platform, compiler integrated analyzers. They can provide warnings when you are editing, building inside IDE or from command line, as-if they are provided by the compiler itself.
JetBrains ReSharper InspectCode is also used for wider rule sets. You can run it from PowerShell with `.\InspectCode.ps1`. Alternatively, you can install ReSharper or use Rider to get inline support in your IDE of choice.
## Contributing
When it comes to contributing to the project, the two main things you can do to help out are reporting issues and submitting pull requests. Please refer to the [contributing guidelines](CONTRIBUTING.md) to understand how to help in the most effective way possible.
If you wish to help with localisation efforts, head over to [crowdin](https://crowdin.com/project/osu-web).
We love to reward quality contributions. If you have made a large contribution, or are a regular contributor, you are welcome to [submit an expense via opencollective](https://opencollective.com/ppy/expenses/new). If you have any questions, feel free to [reach out to peppy](mailto:pe@ppy.sh) before doing so.
## Licence
*osu!*'s code and framework are licensed under the [MIT licence](https://opensource.org/licenses/MIT). Please see [the licence file](LICENCE) for more information. [tl;dr](https://tldrlegal.com/license/mit-license) you can do whatever you want as long as you include the original copyright and license notice in any copy of the software/source.
Please note that this *does not cover* the usage of the "osu!" or "ppy" branding in any software, resources, advertising or promotion, as this is protected by trademark law.
Please also note that game resources are covered by a separate licence. Please see the [ppy/osu-resources](https://github.com/ppy/osu-resources) repository for clarifications.

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="sh.ppy.osulazer" android:installLocation="auto">
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="boats.jvnko.osu.android" android:installLocation="auto">
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="34" />
<application android:allowBackup="true"
android:supportsRtl="true"

View File

@@ -29,7 +29,7 @@ namespace osu.Desktop
{
internal partial class DiscordRichPresence : Component
{
private const string client_id = "1216669957799018608";
private const string client_id = "1440647613358800918";
private DiscordRpcClient client = null!;

View File

@@ -118,7 +118,7 @@ namespace osu.Desktop
if (IsPackageManaged)
return new NoActionUpdateManager();
return new VelopackUpdateManager();
return new VelopackUpdateManager(); // yay
}
public override bool RestartAppWhenExited()

View File

@@ -21,9 +21,9 @@ namespace osu.Desktop
public static class Program
{
#if DEBUG
private const string base_game_name = @"osu-development";
private const string base_game_name = @"jvnkosu-development";
#else
private const string base_game_name = @"osu";
private const string base_game_name = @"jvnkosu";
#endif
private static LegacyTcpIpcProvider? legacyIpc;
@@ -44,7 +44,7 @@ namespace osu.Desktop
// While .NET 8 only supports Windows 10 and above, running on Windows 7/8.1 may still work. We are limited by realm currently, as they choose to only support 8.1 and higher.
// See https://www.mongodb.com/docs/realm/sdk/dotnet/compatibility/
if (windowsVersion.Major < 6 || (windowsVersion.Major == 6 && windowsVersion.Minor <= 2))
if (windowsVersion.Major < 6)
{
unsafe
{
@@ -53,13 +53,26 @@ namespace osu.Desktop
// We could also better detect compatibility mode if required:
// https://stackoverflow.com/questions/10744651/how-i-can-detect-if-my-application-is-running-under-compatibility-mode#comment58183249_10744730
SDL3.SDL_ShowSimpleMessageBox(SDL_MessageBoxFlags.SDL_MESSAGEBOX_ERROR,
"Your operating system is too old to run osu!"u8,
"This version of osu! requires at least Windows 8.1 to run.\n"u8
+ "Please upgrade your operating system or consider using an older version of osu!.\n\n"u8
+ "If you are running a newer version of windows, please check you don't have \"Compatibility mode\" turned on for osu!"u8, null);
"Your operating system is too old to run this game!"u8,
"This version of the game requires at least Windows 8.1 to run reliably.\n"u8
+ "You may try to run it on Windows 8 or older, but it's not guaranteed to actually work.\n\n"u8
+ "Please upgrade your operating system or consider using an older game version.\n\n"u8
+ "If you are running a newer version of Windows, please check you don't have \"Compatibility mode\" turned on for the game's executable."u8, null);
return;
}
}
if (windowsVersion.Major == 6 && windowsVersion.Minor <= 2)
{
unsafe
{
SDL3.SDL_ShowSimpleMessageBox(SDL_MessageBoxFlags.SDL_MESSAGEBOX_WARNING,
"Your operating system is too old to run this game!"u8,
"While the version of Windows you're using may be able to launch it, it's likely to work unreliably and crash.\n"u8
+ "Please upgrade your operating system or consider using an older game version.\n\n"u8
+ "If you are running a newer version of Windows, please check you don't have \"Compatibility mode\" turned on for the game's executable."u8, null);
}
}
}
// NVIDIA profiles are based on the executable name of a process.
@@ -208,9 +221,10 @@ namespace osu.Desktop
[SupportedOSPlatform("windows")]
private static void configureWindows(VelopackApp app)
{
app.OnFirstRun(_ => WindowsAssociationManager.InstallAssociations());
app.OnAfterUpdateFastCallback(_ => WindowsAssociationManager.UpdateAssociations());
app.OnBeforeUninstallFastCallback(_ => WindowsAssociationManager.UninstallAssociations());
// we do not want associations here, as that breaks official lazer's associations
// app.OnFirstRun(_ => WindowsAssociationManager.InstallAssociations());
// app.OnAfterUpdateFastCallback(_ => WindowsAssociationManager.UpdateAssociations());
// app.OnBeforeUninstallFastCallback(_ => WindowsAssociationManager.UninstallAssociations());
}
}
}

View File

@@ -55,7 +55,7 @@ namespace osu.Desktop.Updater
try
{
IUpdateSource updateSource = new GithubSource(@"https://github.com/ppy/osu", null, ReleaseStream.Value == Game.Configuration.ReleaseStream.Tachyon);
IUpdateSource updateSource = new GiteaSource(@"https://gitea.jvnko.boats/jvnkosu/client", null, ReleaseStream.Value == Game.Configuration.ReleaseStream.Tachyon);
Velopack.UpdateManager updateManager = new Velopack.UpdateManager(updateSource, new UpdateOptions
{
AllowVersionDowngrade = true

Binary file not shown.

Before

Width:  |  Height:  |  Size: 75 KiB

After

Width:  |  Height:  |  Size: 401 KiB

View File

@@ -3,11 +3,11 @@
<TargetFramework>net8.0</TargetFramework>
<OutputType>WinExe</OutputType>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<Description>A free-to-win rhythm game. Rhythm is just a *click* away!</Description>
<Description>A free-to-win rhythm game based on osu!(lazer). Click is just a *rhythm* away!</Description>
<AssemblyName>osu!</AssemblyName>
<AssemblyTitle>osu!(lazer)</AssemblyTitle>
<Title>osu!</Title>
<Product>osu!(lazer)</Product>
<AssemblyTitle>jvnkosu!</AssemblyTitle>
<Title>jvnkosu!</Title>
<Product>jvnkosu!</Product>
<ApplicationIcon>lazer.ico</ApplicationIcon>
<Version>0.0.0</Version>
<FileVersion>0.0.0</FileVersion>

View File

@@ -3,16 +3,19 @@
<metadata>
<id>osulazer</id>
<version>0.0.0</version>
<title>osu!</title>
<authors>ppy Pty Ltd</authors>
<title>jvnkosu!</title>
<authors>ppy Pty Ltd., jvnkosu! team</authors>
<owners>Dean Herbert</owners>
<projectUrl>https://osu.ppy.sh/</projectUrl>
<projectUrl>https://osu.jvnko.boats/</projectUrl>
<iconUrl>https://github.com/ppy/osu/blob/master/assets/lazer-nuget.png?raw=true</iconUrl>
<icon>icon.png</icon>
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<description>A free-to-win rhythm game. Rhythm is just a *click* away!</description>
<description>A free-to-win rhythm game based on osu!(lazer). Click is just a *rhythm* away!</description>
<releaseNotes>testing</releaseNotes>
<copyright>Copyright (c) 2025 ppy Pty Ltd</copyright>
<copyright>
Copyright (c) 2025 ppy Pty Ltd
Copyright (c) 2025 jvnkosu! team
</copyright>
<language>en-AU</language>
</metadata>
<files>

View File

@@ -8,7 +8,7 @@
<PropertyGroup Label="Nuget">
<Title>osu!catch (ruleset)</Title>
<PackageId>ppy.osu.Game.Rulesets.Catch</PackageId>
<PackageId>jvnkosu.Client.Rulesets.Catch</PackageId>
<IsPackable>true</IsPackable>
</PropertyGroup>

View File

@@ -14,8 +14,8 @@ namespace osu.Game.Rulesets.Mania.Mods
public override string Acronym => Name;
public abstract int KeyCount { get; }
public override ModType Type => ModType.Conversion;
public override double ScoreMultiplier => 0.9;
public override bool Ranked => UsesDefaultConfiguration;
public override double ScoreMultiplier => 1;
public override bool Ranked => true;
public void ApplyToBeatmapConverter(IBeatmapConverter beatmapConverter)
{

View File

@@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Mania.Mods
typeof(ManiaModFadeIn)
}).ToArray();
public override bool Ranked => false;
public override bool Ranked => true;
public override bool ValidForFreestyleAsRequiredMod => false;

View File

@@ -13,8 +13,13 @@ namespace osu.Game.Rulesets.Mania.Mods
MinValue = 0,
MaxValue = 10,
// Use larger extended limits for mania to include OD values that occur with EZ or HR enabled
#if !DEBUG
ExtendedMaxValue = 15,
ExtendedMinValue = -15,
#else
ExtendedMinValue = -250,
ExtendedMaxValue = 50,
#endif
ReadCurrentFromDifficulty = diff => diff.OverallDifficulty,
};
}

View File

@@ -11,7 +11,7 @@ namespace osu.Game.Rulesets.Mania.Mods
public class ManiaModHardRock : ModHardRock, IApplicableToHitObject
{
public override double ScoreMultiplier => 1;
public override bool Ranked => false;
public override bool Ranked => true;
public const double HIT_WINDOW_DIFFICULTY_MULTIPLIER = 1.4;

View File

@@ -14,6 +14,5 @@ namespace osu.Game.Rulesets.Mania.Mods
public override string Acronym => "1K";
public override IconUsage? Icon => OsuIcon.ModOneKey;
public override LocalisableString Description => @"Play with one key.";
public override bool Ranked => false;
}
}

View File

@@ -14,6 +14,5 @@ namespace osu.Game.Rulesets.Mania.Mods
public override string Acronym => "10K";
public override IconUsage? Icon => OsuIcon.ModTenKeys;
public override LocalisableString Description => @"Play with ten keys.";
public override bool Ranked => false;
}
}

View File

@@ -14,6 +14,5 @@ namespace osu.Game.Rulesets.Mania.Mods
public override string Acronym => "2K";
public override IconUsage? Icon => OsuIcon.ModTwoKeys;
public override LocalisableString Description => @"Play with two keys.";
public override bool Ranked => false;
}
}

View File

@@ -14,6 +14,5 @@ namespace osu.Game.Rulesets.Mania.Mods
public override string Acronym => "3K";
public override IconUsage? Icon => OsuIcon.ModThreeKeys;
public override LocalisableString Description => @"Play with three keys.";
public override bool Ranked => false;
}
}

View File

@@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Mania.Mods
public class ManiaModMirror : ModMirror, IApplicableToBeatmap
{
public override LocalisableString Description => "Notes are flipped horizontally.";
public override bool Ranked => UsesDefaultConfiguration;
public override bool Ranked => true;
public void ApplyToBeatmap(IBeatmap beatmap)
{

View File

@@ -8,7 +8,7 @@
<PropertyGroup Label="Nuget">
<Title>osu!mania (ruleset)</Title>
<PackageId>ppy.osu.Game.Rulesets.Mania</PackageId>
<PackageId>jvnkosu.Client.Rulesets.Mania</PackageId>
<IsPackable>true</IsPackable>
</PropertyGroup>

View File

@@ -14,7 +14,13 @@ namespace osu.Game.Rulesets.Osu.Mods
{
public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModMagnetised), typeof(OsuModRepel), typeof(OsuModAutopilot), typeof(OsuModSpunOut), typeof(OsuModAlternate), typeof(OsuModSingleTap) }).ToArray();
#if !DEBUG
private string username = "Autoplay";
#else
private string username = "Chicony";
#endif
public override ModReplayData CreateReplayData(IBeatmap beatmap, IReadOnlyList<Mod> mods)
=> new ModReplayData(new OsuAutoGenerator(beatmap, mods).Generate(), new ModCreatedUser { Username = "Autoplay" });
=> new ModReplayData(new OsuAutoGenerator(beatmap, mods).Generate(), new ModCreatedUser { Username = username });
}
}

View File

@@ -16,13 +16,19 @@ namespace osu.Game.Rulesets.Osu.Mods
{
public partial class OsuModDifficultyAdjust : ModDifficultyAdjust
{
[SettingSource("Circle Size", "Override a beatmap's set CS.", FIRST_SETTING_ORDER - 1, SettingControlType = typeof(DifficultyAdjustSettingsControl))]
public DifficultyBindable CircleSize { get; } = new DifficultyBindable
{
Precision = 0.1f,
MinValue = 0,
MaxValue = 10,
#if !DEBUG
ExtendedMaxValue = 11,
#else
ExtendedMinValue = -250,
ExtendedMaxValue = 13,
#endif
ReadCurrentFromDifficulty = diff => diff.CircleSize,
};
@@ -32,8 +38,13 @@ namespace osu.Game.Rulesets.Osu.Mods
Precision = 0.1f,
MinValue = 0,
MaxValue = 10,
#if !DEBUG
ExtendedMinValue = -10,
ExtendedMaxValue = 11,
#else
ExtendedMinValue = -250,
ExtendedMaxValue = 13,
#endif
ReadCurrentFromDifficulty = diff => diff.ApproachRate,
};

View File

@@ -0,0 +1,16 @@
// 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.Audio;
using osu.Framework.Bindables;
using osu.Game.Configuration;
using osu.Game.Overlays.Settings;
using osu.Game.Rulesets.Mods;
namespace osu.Game.Rulesets.Osu.Mods
{
public class OsuModRateAdjustConcrete : ModRateAdjustConcrete
{
}
}

View File

@@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Osu.Mods
public override LocalisableString Description => @"Spinners will be automatically completed.";
public override double ScoreMultiplier => 0.9;
public override Type[] IncompatibleMods => new[] { typeof(ModAutoplay), typeof(OsuModAutopilot), typeof(OsuModTargetPractice) };
public override bool Ranked => UsesDefaultConfiguration;
public override bool Ranked => true;
public void ApplyToDrawableHitObject(DrawableHitObject hitObject)
{

View File

@@ -10,6 +10,6 @@ namespace osu.Game.Rulesets.Osu.Mods
public class OsuModTouchDevice : ModTouchDevice
{
public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModAutopilot), typeof(OsuModBloom) }).ToArray();
public override bool Ranked => UsesDefaultConfiguration;
public override bool Ranked => true;
}
}

View File

@@ -230,6 +230,14 @@ namespace osu.Game.Rulesets.Osu
new ModScoreV2(),
};
case ModType.Special:
#if DEBUG
return new Mod[]
{
new OsuModRateAdjustConcrete(),
};
#endif
default:
return Array.Empty<Mod>();
}

View File

@@ -8,7 +8,7 @@
<PropertyGroup Label="Nuget">
<Title>osu! (ruleset)</Title>
<PackageId>ppy.osu.Game.Rulesets.Osu</PackageId>
<PackageId>jvnkosu.Client.Rulesets.Osu</PackageId>
<IsPackable>true</IsPackable>
</PropertyGroup>

View File

@@ -8,7 +8,7 @@
<PropertyGroup Label="Nuget">
<Title>osu!taiko (ruleset)</Title>
<PackageId>ppy.osu.Game.Rulesets.Taiko</PackageId>
<PackageId>jvnkosu.Client.Rulesets.Taiko</PackageId>
<IsPackable>true</IsPackable>
</PropertyGroup>

View File

@@ -16,6 +16,7 @@ using osu.Framework.Platform;
using osu.Framework.Testing;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Formats;
using osu.Game.Configuration;
using osu.Game.Database;
using osu.Game.IO;
using osu.Game.IO.Archives;
@@ -206,7 +207,7 @@ namespace osu.Game.Tests.Online
{
}
protected override BeatmapImporter CreateBeatmapImporter(Storage storage, RealmAccess realm)
protected override BeatmapImporter CreateBeatmapImporter(Storage storage, RealmAccess realm, OsuConfigManager? config = null)
{
return new TestBeatmapImporter(this, storage, realm);
}
@@ -216,7 +217,7 @@ namespace osu.Game.Tests.Online
private readonly TestBeatmapManager testBeatmapManager;
public TestBeatmapImporter(TestBeatmapManager testBeatmapManager, Storage storage, RealmAccess databaseAccess)
: base(storage, databaseAccess)
: base(storage, databaseAccess, null)
{
this.testBeatmapManager = testBeatmapManager;
}
@@ -256,4 +257,4 @@ namespace osu.Game.Tests.Online
protected override string Target => string.Empty;
}
}
}
}

View File

@@ -1,209 +1,209 @@
// 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.
// // 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.
#nullable disable
// #nullable disable
using System;
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Rendering;
using osu.Framework.Graphics.Textures;
using osu.Game.Configuration;
using osu.Game.Graphics.Backgrounds;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests;
using osu.Game.Online.API.Requests.Responses;
// using System;
// using System.Collections.Generic;
// using System.Linq;
// using NUnit.Framework;
// using osu.Framework.Allocation;
// using osu.Framework.Graphics;
// using osu.Framework.Graphics.Containers;
// using osu.Framework.Graphics.Rendering;
// using osu.Framework.Graphics.Textures;
// using osu.Game.Configuration;
// using osu.Game.Graphics.Backgrounds;
// using osu.Game.Online.API;
// using osu.Game.Online.API.Requests;
// using osu.Game.Online.API.Requests.Responses;
namespace osu.Game.Tests.Visual.Background
{
public partial class TestSceneSeasonalBackgroundLoader : ScreenTestScene
{
[Resolved]
private OsuConfigManager config { get; set; }
// namespace osu.Game.Tests.Visual.Background
// {
// public partial class TestSceneSeasonalBackgroundLoader : ScreenTestScene
// {
// [Resolved]
// private OsuConfigManager config { get; set; }
[Resolved]
private SessionStatics statics { get; set; }
// [Resolved]
// private SessionStatics statics { get; set; }
private DummyAPIAccess dummyAPI => (DummyAPIAccess)API;
// private DummyAPIAccess dummyAPI => (DummyAPIAccess)API;
private LookupLoggingTextureStore textureStore;
private SeasonalBackgroundLoader backgroundLoader;
private Container backgroundContainer;
// private LookupLoggingTextureStore textureStore;
// private SeasonalBackgroundLoader backgroundLoader;
// private Container backgroundContainer;
// in real usages these would be online URLs, but correct execution of this test
// shouldn't be coupled to existence of online assets.
private static readonly List<string> seasonal_background_urls = new List<string>
{
"Backgrounds/bg2",
"Backgrounds/bg4",
"Backgrounds/bg3"
};
// // in real usages these would be online URLs, but correct execution of this test
// // shouldn't be coupled to existence of online assets.
// private static readonly List<string> seasonal_background_urls = new List<string>
// {
// "Backgrounds/bg2",
// "Backgrounds/bg4",
// "Backgrounds/bg3"
// };
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
{
var dependencies = new DependencyContainer(base.CreateChildDependencies(parent));
// protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
// {
// var dependencies = new DependencyContainer(base.CreateChildDependencies(parent));
textureStore = new LookupLoggingTextureStore(dependencies.Get<IRenderer>());
dependencies.CacheAs(typeof(LargeTextureStore), textureStore);
// textureStore = new LookupLoggingTextureStore(dependencies.Get<IRenderer>());
// dependencies.CacheAs(typeof(LargeTextureStore), textureStore);
return dependencies;
}
// return dependencies;
// }
[BackgroundDependencyLoader]
private void load(LargeTextureStore wrappedStore)
{
textureStore.AddStore(wrappedStore);
// [BackgroundDependencyLoader]
// private void load(LargeTextureStore wrappedStore)
// {
// textureStore.AddStore(wrappedStore);
Child = new DependencyProvidingContainer
{
CachedDependencies = new (Type, object)[]
{
(typeof(LargeTextureStore), textureStore)
},
Child = backgroundContainer = new Container
{
RelativeSizeAxes = Axes.Both
}
};
}
// Child = new DependencyProvidingContainer
// {
// CachedDependencies = new (Type, object)[]
// {
// (typeof(LargeTextureStore), textureStore)
// },
// Child = backgroundContainer = new Container
// {
// RelativeSizeAxes = Axes.Both
// }
// };
// }
[SetUp]
public void SetUp() => Schedule(() =>
{
// reset API response in statics to avoid test crosstalk.
statics.SetValue<APISeasonalBackgrounds>(Static.SeasonalBackgrounds, null);
textureStore.PerformedLookups.Clear();
dummyAPI.SetState(APIState.Online);
// [SetUp]
// public void SetUp() => Schedule(() =>
// {
// // reset API response in statics to avoid test crosstalk.
// statics.SetValue<APISeasonalBackgrounds>(Static.SeasonalBackgrounds, null);
// textureStore.PerformedLookups.Clear();
// dummyAPI.SetState(APIState.Online);
backgroundContainer.Clear();
});
// backgroundContainer.Clear();
// });
[TestCase(-5)]
[TestCase(5)]
public void TestAlwaysSeasonal(int daysOffset)
{
registerBackgroundsResponse(DateTimeOffset.Now.AddDays(daysOffset));
setSeasonalBackgroundMode(SeasonalBackgroundMode.Always);
// [TestCase(-5)]
// [TestCase(5)]
// public void TestAlwaysSeasonal(int daysOffset)
// {
// registerBackgroundsResponse(DateTimeOffset.Now.AddDays(daysOffset));
// setSeasonalBackgroundMode(SeasonalBackgroundMode.Always);
createLoader();
// createLoader();
for (int i = 0; i < 4; ++i)
loadNextBackground();
// for (int i = 0; i < 4; ++i)
// loadNextBackground();
AddAssert("all backgrounds cycled", () => new HashSet<string>(textureStore.PerformedLookups).SetEquals(seasonal_background_urls));
}
// AddAssert("all backgrounds cycled", () => new HashSet<string>(textureStore.PerformedLookups).SetEquals(seasonal_background_urls));
// }
[TestCase(-5)]
[TestCase(5)]
public void TestNeverSeasonal(int daysOffset)
{
registerBackgroundsResponse(DateTimeOffset.Now.AddDays(daysOffset));
setSeasonalBackgroundMode(SeasonalBackgroundMode.Never);
// [TestCase(-5)]
// [TestCase(5)]
// public void TestNeverSeasonal(int daysOffset)
// {
// registerBackgroundsResponse(DateTimeOffset.Now.AddDays(daysOffset));
// setSeasonalBackgroundMode(SeasonalBackgroundMode.Never);
createLoader();
// createLoader();
assertNoBackgrounds();
}
// assertNoBackgrounds();
// }
[Test]
public void TestSometimesInSeason()
{
registerBackgroundsResponse(DateTimeOffset.Now.AddDays(5));
setSeasonalBackgroundMode(SeasonalBackgroundMode.Sometimes);
// [Test]
// public void TestSometimesInSeason()
// {
// registerBackgroundsResponse(DateTimeOffset.Now.AddDays(5));
// setSeasonalBackgroundMode(SeasonalBackgroundMode.Sometimes);
createLoader();
// createLoader();
assertAnyBackground();
}
// assertAnyBackground();
// }
[Test]
public void TestSometimesOutOfSeason()
{
registerBackgroundsResponse(DateTimeOffset.Now.AddDays(-10));
setSeasonalBackgroundMode(SeasonalBackgroundMode.Sometimes);
// [Test]
// public void TestSometimesOutOfSeason()
// {
// registerBackgroundsResponse(DateTimeOffset.Now.AddDays(-10));
// setSeasonalBackgroundMode(SeasonalBackgroundMode.Sometimes);
createLoader();
// createLoader();
assertNoBackgrounds();
}
// assertNoBackgrounds();
// }
private void registerBackgroundsResponse(DateTimeOffset endDate)
=> AddStep("setup request handler", () =>
{
dummyAPI.HandleRequest = request =>
{
if (dummyAPI.State.Value != APIState.Online || !(request is GetSeasonalBackgroundsRequest backgroundsRequest))
return false;
// private void registerBackgroundsResponse(DateTimeOffset endDate)
// => AddStep("setup request handler", () =>
// {
// dummyAPI.HandleRequest = request =>
// {
// if (dummyAPI.State.Value != APIState.Online || !(request is GetSeasonalBackgroundsRequest backgroundsRequest))
// return false;
backgroundsRequest.TriggerSuccess(new APISeasonalBackgrounds
{
Backgrounds = seasonal_background_urls.Select(url => new APISeasonalBackground { Url = url }).ToList(),
EndDate = endDate
});
// backgroundsRequest.TriggerSuccess(new APISeasonalBackgrounds
// {
// Backgrounds = seasonal_background_urls.Select(url => new APISeasonalBackground { Url = url }).ToList(),
// EndDate = endDate
// });
return true;
};
});
// return true;
// };
// });
private void setSeasonalBackgroundMode(SeasonalBackgroundMode mode)
=> AddStep($"set seasonal mode to {mode}", () => config.SetValue(OsuSetting.SeasonalBackgroundMode, mode));
// private void setSeasonalBackgroundMode(SeasonalBackgroundMode mode)
// => AddStep($"set seasonal mode to {mode}", () => config.SetValue(OsuSetting.SeasonalBackgroundMode, mode));
private void createLoader()
=> AddStep("create loader", () =>
{
if (backgroundLoader != null)
Remove(backgroundLoader, true);
// private void createLoader()
// => AddStep("create loader", () =>
// {
// if (backgroundLoader != null)
// Remove(backgroundLoader, true);
Add(backgroundLoader = new SeasonalBackgroundLoader());
});
// Add(backgroundLoader = new SeasonalBackgroundLoader());
// });
private void loadNextBackground()
{
SeasonalBackground previousBackground = null;
SeasonalBackground background = null;
// private void loadNextBackground()
// {
// SeasonalBackground previousBackground = null;
// SeasonalBackground background = null;
AddStep("create next background", () =>
{
previousBackground = (SeasonalBackground)backgroundContainer.SingleOrDefault();
background = backgroundLoader.LoadNextBackground();
if (background != null)
LoadComponentAsync(background, bg => backgroundContainer.Child = bg);
});
// AddStep("create next background", () =>
// {
// previousBackground = (SeasonalBackground)backgroundContainer.SingleOrDefault();
// background = backgroundLoader.LoadNextBackground();
// if (background != null)
// LoadComponentAsync(background, bg => backgroundContainer.Child = bg);
// });
AddUntilStep("background loaded", () => background.IsLoaded);
AddAssert("background is different", () => !background.Equals(previousBackground));
}
// AddUntilStep("background loaded", () => background.IsLoaded);
// AddAssert("background is different", () => !background.Equals(previousBackground));
// }
private void assertAnyBackground()
{
loadNextBackground();
AddAssert("background looked up", () => textureStore.PerformedLookups.Any());
}
// private void assertAnyBackground()
// {
// loadNextBackground();
// AddAssert("background looked up", () => textureStore.PerformedLookups.Any());
// }
private void assertNoBackgrounds()
{
AddAssert("no background available", () => backgroundLoader.LoadNextBackground() == null);
AddAssert("no lookups performed", () => !textureStore.PerformedLookups.Any());
}
// private void assertNoBackgrounds()
// {
// AddAssert("no background available", () => backgroundLoader.LoadNextBackground() == null);
// AddAssert("no lookups performed", () => !textureStore.PerformedLookups.Any());
// }
private class LookupLoggingTextureStore : LargeTextureStore
{
public List<string> PerformedLookups { get; } = new List<string>();
// private class LookupLoggingTextureStore : LargeTextureStore
// {
// public List<string> PerformedLookups { get; } = new List<string>();
public LookupLoggingTextureStore(IRenderer renderer)
: base(renderer)
{
}
// public LookupLoggingTextureStore(IRenderer renderer)
// : base(renderer)
// {
// }
public override Texture Get(string name, WrapMode wrapModeS, WrapMode wrapModeT)
{
PerformedLookups.Add(name);
return base.Get(name, wrapModeS, wrapModeT);
}
}
}
}
// public override Texture Get(string name, WrapMode wrapModeS, WrapMode wrapModeT)
// {
// PerformedLookups.Add(name);
// return base.Get(name, wrapModeS, wrapModeT);
// }
// }
// }
// }

View File

@@ -0,0 +1,29 @@
// 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.Allocation;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Screens.Menu;
namespace osu.Game.Tests.Visual.Menus
{
public partial class TestSceneDisclaimer : ScreenTestScene
{
[BackgroundDependencyLoader]
private void load()
{
AddStep("load disclaimer", () => LoadScreen(new Disclaimer()));
AddStep("toggle support", () =>
{
((DummyAPIAccess)API).LocalUser.Value = new APIUser
{
Username = API.LocalUser.Value.Username,
Id = API.LocalUser.Value.Id + 1,
IsSupporter = !API.LocalUser.Value.IsSupporter,
};
});
}
}
}

View File

@@ -13,6 +13,7 @@ using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Logging;
using osu.Framework.Platform;
using osu.Game.Beatmaps.Formats;
using osu.Game.Configuration;
using osu.Game.Collections;
using osu.Game.Database;
using osu.Game.Extensions;
@@ -36,8 +37,8 @@ namespace osu.Game.Beatmaps
public ProcessBeatmapDelegate? ProcessBeatmap { private get; set; }
public BeatmapImporter(Storage storage, RealmAccess realm)
: base(storage, realm)
public BeatmapImporter(Storage storage, RealmAccess realm, OsuConfigManager? config = null)
: base(storage, realm, config)
{
}

View File

@@ -17,6 +17,7 @@ using osu.Framework.IO.Stores;
using osu.Framework.Platform;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Beatmaps.Formats;
using osu.Game.Configuration;
using osu.Game.Database;
using osu.Game.Extensions;
using osu.Game.IO.Archives;
@@ -59,7 +60,7 @@ namespace osu.Game.Beatmaps
}
public BeatmapManager(Storage storage, RealmAccess realm, IAPIProvider? api, AudioManager audioManager, IResourceStore<byte[]> gameResources, GameHost? host = null,
WorkingBeatmap? defaultBeatmap = null, BeatmapDifficultyCache? difficultyCache = null, bool performOnlineLookups = false)
WorkingBeatmap? defaultBeatmap = null, BeatmapDifficultyCache? difficultyCache = null, bool performOnlineLookups = false, OsuConfigManager? config = null)
: base(storage, realm)
{
if (performOnlineLookups)
@@ -75,7 +76,7 @@ namespace osu.Game.Beatmaps
BeatmapTrackStore = audioManager.GetTrackStore(userResources);
beatmapImporter = CreateBeatmapImporter(storage, realm);
beatmapImporter = CreateBeatmapImporter(storage, realm, config);
beatmapImporter.ProcessBeatmap = (beatmapSet, scope) => ProcessBeatmap?.Invoke(beatmapSet, scope);
beatmapImporter.PostNotification = obj => PostNotification?.Invoke(obj);
@@ -98,7 +99,7 @@ namespace osu.Game.Beatmaps
return new WorkingBeatmapCache(BeatmapTrackStore, audioManager, resources, storage, defaultBeatmap, host);
}
protected virtual BeatmapImporter CreateBeatmapImporter(Storage storage, RealmAccess realm) => new BeatmapImporter(storage, realm);
protected virtual BeatmapImporter CreateBeatmapImporter(Storage storage, RealmAccess realm, OsuConfigManager? config = null) => new BeatmapImporter(storage, realm, config);
/// <summary>
/// Create a new beatmap set, backed by a <see cref="BeatmapSetInfo"/> model,

View File

@@ -19,7 +19,7 @@ namespace osu.Game.Beatmaps
LocallyModified = -4,
[LocalisableDescription(typeof(SongSelectStrings), nameof(SongSelectStrings.StatusUnknown))]
[Description("Unknown")]
[Description("Offline")]
None = -3,
[LocalisableDescription(typeof(BeatmapsetsStrings), nameof(BeatmapsetsStrings.ShowStatusGraveyard))]

View File

@@ -33,7 +33,8 @@ namespace osu.Game.Beatmaps.Drawables.Cards
Status = beatmapSet.Status,
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
TextSize = 13f
TextSize = 13f,
ShowUnknownStatus = true
},
new DifficultySpectrumDisplay
{

View File

@@ -8,6 +8,7 @@ namespace osu.Game.Configuration
Circles,
Welcome,
Triangles,
None,
Random
}
}

View File

@@ -8,6 +8,7 @@ using osu.Framework.Configuration;
using osu.Framework.Configuration.Tracking;
using osu.Framework.Extensions;
using osu.Framework.Extensions.LocalisationExtensions;
using osu.Framework.Graphics;
using osu.Framework.Localisation;
using osu.Framework.Platform;
using osu.Game.Beatmaps.Drawables.Cards;
@@ -41,6 +42,8 @@ namespace osu.Game.Configuration
SetDefault(OsuSetting.Ruleset, string.Empty);
SetDefault(OsuSetting.Skin, SkinInfo.ARGON_SKIN.ToString());
SetDefault(OsuSetting.MenuCookieColor, Colour4.FromHex(@"8400FF"));
SetDefault(OsuSetting.BeatmapDetailTab, BeatmapDetailTab.Local);
SetDefault(OsuSetting.BeatmapLeaderboardSortMode, LeaderboardSortMode.Score);
SetDefault(OsuSetting.BeatmapDetailModsFilter, false);
@@ -59,12 +62,13 @@ namespace osu.Game.Configuration
SetDefault(OsuSetting.ChatDisplayHeight, ChatOverlay.DEFAULT_HEIGHT, 0.2f, 1f, 0.01f);
SetDefault(OsuSetting.BeatmapListingCardSize, BeatmapCardSize.Normal);
SetDefault(OsuSetting.BeatmapListingFeaturedArtistFilter, true);
SetDefault(OsuSetting.BeatmapListingFeaturedArtistFilter, false);
SetDefault(OsuSetting.ProfileCoverExpanded, true);
SetDefault(OsuSetting.ToolbarClockDisplayMode, ToolbarClockDisplayMode.Full);
SetDefault(OsuSetting.ForceLegacySongSelect, false);
SetDefault(OsuSetting.SongSelectBackgroundBlur, false);
// Online settings
@@ -92,7 +96,7 @@ namespace osu.Game.Configuration
SetDefault(OsuSetting.ExternalLinkWarning, true);
SetDefault(OsuSetting.PreferNoVideo, false);
SetDefault(OsuSetting.BackgroundCategory, "Default");
SetDefault(OsuSetting.ShowOnlineExplicitContent, false);
SetDefault(OsuSetting.NotifyOnUsernameMentioned, true);
@@ -154,6 +158,7 @@ namespace osu.Game.Configuration
SetDefault(OsuSetting.ReplayPlaybackControlsExpanded, true);
SetDefault(OsuSetting.GameplayLeaderboard, true);
SetDefault(OsuSetting.AlwaysPlayFirstComboBreak, true);
SetDefault(OsuSetting.AlwaysPlayComboBreak, false);
SetDefault(OsuSetting.FloatingComments, false);
@@ -193,10 +198,13 @@ namespace osu.Game.Configuration
SetDefault(OsuSetting.IntroSequence, IntroSequence.Triangles);
SetDefault(OsuSetting.MenuBackgroundSource, BackgroundSource.Skin);
SetDefault(OsuSetting.SeasonalBackgroundMode, SeasonalBackgroundMode.Sometimes);
SetDefault(OsuSetting.SeasonalBackgroundMode, SeasonalBackgroundMode.Never);
SetDefault(OsuSetting.UseSeasonalBackgroundsV2, true);
SetDefault(OsuSetting.DiscordRichPresence, DiscordRichPresenceMode.Full);
SetDefault(OsuSetting.DeleteImportedArchives, true);
SetDefault(OsuSetting.EditorDim, 0.25f, 0f, 0.75f, 0.25f);
SetDefault(OsuSetting.EditorWaveformOpacity, 0.25f, 0f, 1f, 0.25f);
SetDefault(OsuSetting.EditorShowHitMarkers, true);
@@ -362,6 +370,7 @@ namespace osu.Game.Configuration
GameplayLeaderboard,
PositionalHitsoundsLevel,
AlwaysPlayFirstComboBreak,
AlwaysPlayComboBreak,
FloatingComments,
HUDVisibilityMode,
@@ -405,6 +414,7 @@ namespace osu.Game.Configuration
ChatDisplayHeight,
BeatmapListingCardSize,
ToolbarClockDisplayMode,
ForceLegacySongSelect,
SongSelectBackgroundBlur,
Version,
ShowFirstRunSetup,
@@ -412,6 +422,7 @@ namespace osu.Game.Configuration
Skin,
ScreenshotFormat,
ScreenshotCaptureMenuCursor,
MenuCookieColor,
BeatmapSkins,
BeatmapColours,
BeatmapHitsounds,
@@ -436,10 +447,13 @@ namespace osu.Game.Configuration
MenuBackgroundSource,
GameplayDisableWinKey,
SeasonalBackgroundMode,
UseSeasonalBackgroundsV2, // TODO: add migrations
BackgroundCategory,
EditorWaveformOpacity,
EditorShowHitMarkers,
EditorAutoSeekOnPlacement,
DiscordRichPresence,
DeleteImportedArchives,
ShowOnlineExplicitContent,
LastProcessedMetadataId,

View File

@@ -14,12 +14,6 @@ namespace osu.Game.Configuration
[LocalisableDescription(typeof(UserInterfaceStrings), nameof(UserInterfaceStrings.AlwaysSeasonalBackground))]
Always,
/// <summary>
/// Seasonal backgrounds are shown only during their corresponding season.
/// </summary>
[LocalisableDescription(typeof(UserInterfaceStrings), nameof(UserInterfaceStrings.SometimesSeasonalBackground))]
Sometimes,
/// <summary>
/// Seasonal backgrounds are never shown.
/// </summary>

View File

@@ -206,11 +206,25 @@ namespace osu.Game.Database
if (!Filename.EndsWith(realm_extension, StringComparison.Ordinal))
Filename += realm_extension;
// since I'm an idiot, I will have to suffer
#if DEBUG
if (!DebugUtils.IsNUnitRunning)
applyFilenameSchemaSuffix(ref Filename);
#endif
#if !DEBUG
// in the lazer. straight up "migrating it". and by "it", haha, well. let's just say. My realm .
string altFilename = filename;
applyFilenameSchemaSuffix(ref altFilename); // it also migrates older versions automagically! (sorry, I only used that word for irony...)
if (storage.Exists(altFilename) && !storage.Exists(Filename))
{
using (var previous = storage.GetStream(altFilename))
using (var current = storage.CreateFileSafely(Filename))
{
Logger.Log($@"Migrating production build DB: {altFilename} -> {Filename}");
previous.CopyTo(current);
}
}
#endif
// `prepareFirstRealmAccess()` triggers the first `getRealmInstance` call, which will implicitly run realm migrations and bring the schema up-to-date.
using (var realm = prepareFirstRealmAccess())
cleanupPendingDeletions(realm);

View File

@@ -8,11 +8,14 @@ using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Humanizer;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Logging;
using osu.Framework.Platform;
using osu.Framework.Threading;
using osu.Game.Configuration;
using osu.Game.Extensions;
using osu.Game.IO.Archives;
using osu.Game.Models;
@@ -77,11 +80,19 @@ namespace osu.Game.Database
/// </summary>
public Action<Notification>? PostNotification { get; set; }
protected RealmArchiveModelImporter(Storage storage, RealmAccess realm)
private readonly OsuConfigManager? config;
private Bindable<bool>? deleteImportedArchives;
protected RealmArchiveModelImporter(Storage storage, RealmAccess realm, OsuConfigManager? config = null)
{
Realm = realm;
Files = new RealmFileStore(realm, storage);
deleteImportedArchives = config?.GetBindable<bool>(OsuSetting.DeleteImportedArchives);
this.config = config;
}
public Task Import(params string[] paths) => Import(paths.Select(p => new ImportTask(p)).ToArray());
@@ -252,9 +263,10 @@ namespace osu.Game.Database
// e.g. reconstructing/repairing database with items from default storage.
// Also, not always a single file, i.e. for LegacyFilesystemReader
// TODO: Add a check to prevent files from storage to be deleted.
bool allowDelete = deleteImportedArchives?.Value ?? true;
try
{
if (import != null && ShouldDeleteArchive(task.Path))
if (import != null && ShouldDeleteArchive(task.Path) && allowDelete)
task.DeleteFile();
}
catch (Exception e)

View File

@@ -4,6 +4,7 @@
#nullable disable
using System;
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
@@ -19,46 +20,105 @@ namespace osu.Game.Graphics.Backgrounds
{
public partial class SeasonalBackgroundLoader : Component
{
public event Action<Exception> OnLoadFailure;
public event Action BackgroundChanged;
/// <summary>
/// Fired when background should be changed due to receiving backgrounds from API
/// or when the user setting is changed (as it might require unloading the seasonal background).
/// Fired when categories have been successfully refreshed from the server.
/// </summary>
public event Action SeasonalBackgroundChanged;
public event Action OnCategoriesRefreshed;
public readonly Bindable<IEnumerable<string>> AvailableCategories = new Bindable<IEnumerable<string>>(new List<string>());
[Resolved]
private IAPIProvider api { get; set; }
private Bindable<SeasonalBackgroundMode> seasonalBackgroundMode;
private Bindable<APISeasonalBackgrounds> seasonalBackgrounds;
private readonly IBindable<APIState> apiState = new Bindable<APIState>();
private Bindable<bool> useSeasonalBackgrounds;
private Bindable<string> selectedCategory;
private Bindable<APISeasonalBackgrounds> currentBackgrounds;
private int current;
private int currentBackgroundIndex;
private bool shouldShowCustomBackgrounds => useSeasonalBackgrounds.Value;
private bool shouldFetchCustomBackgrounds = false;
[BackgroundDependencyLoader]
private void load(OsuConfigManager config, SessionStatics sessionStatics)
{
seasonalBackgroundMode = config.GetBindable<SeasonalBackgroundMode>(OsuSetting.SeasonalBackgroundMode);
seasonalBackgroundMode.BindValueChanged(_ => SeasonalBackgroundChanged?.Invoke());
useSeasonalBackgrounds = config.GetBindable<bool>(OsuSetting.UseSeasonalBackgroundsV2);
useSeasonalBackgrounds.BindValueChanged(_ => BackgroundChanged?.Invoke());
seasonalBackgrounds = sessionStatics.GetBindable<APISeasonalBackgrounds>(Static.SeasonalBackgrounds);
seasonalBackgrounds.BindValueChanged(_ =>
{
if (shouldShowSeasonal)
SeasonalBackgroundChanged?.Invoke();
});
selectedCategory = config.GetBindable<string>(OsuSetting.BackgroundCategory);
selectedCategory.BindValueChanged(_ => fetchBackgroundsForSelectedCategory());
fetchSeasonalBackgrounds();
currentBackgrounds = sessionStatics.GetBindable<APISeasonalBackgrounds>(Static.SeasonalBackgrounds);
if (shouldShowCustomBackgrounds)
fetchCategories(true);
apiState.BindTo(api.State);
apiState.BindValueChanged(d => shouldFetchCustomBackgrounds = d.NewValue == APIState.Online, true);
}
private void fetchSeasonalBackgrounds()
/// <summary>
/// Public method to trigger a refresh of categories from the UI.
/// </summary>
public void RefreshCategories(bool ignoreSuccess = false)
{
if (seasonalBackgrounds.Value != null)
return;
fetchCategories(ignoreSuccess);
}
private void fetchCategories(bool ignoreSuccess = false)
{
if (!shouldShowCustomBackgrounds) return;
var request = new GetBackgroundCategoriesRequest();
var request = new GetSeasonalBackgroundsRequest();
request.Success += response =>
{
seasonalBackgrounds.Value = response;
current = RNG.Next(0, response.Backgrounds?.Count ?? 0);
var serverCategories = response.Categories ?? Enumerable.Empty<string>();
AvailableCategories.Value = serverCategories.Distinct(StringComparer.OrdinalIgnoreCase).ToList();
if (!AvailableCategories.Value.Any())
{
selectedCategory.Value = "";
return; // we don't have any categories!!!
}
if (!AvailableCategories.Value.Contains(selectedCategory.Value))
selectedCategory.Value = AvailableCategories.Value.Contains("Default")
? "Default"
: AvailableCategories.Value.ElementAt(0);
fetchBackgroundsForSelectedCategory();
if (!ignoreSuccess)
OnCategoriesRefreshed?.Invoke();
};
request.Failure += exception =>
{
AvailableCategories.Value = Array.Empty<string>();
OnLoadFailure?.Invoke(exception);
};
api.PerformAsync(request);
}
private void fetchBackgroundsForSelectedCategory()
{
if (!shouldShowCustomBackgrounds) return;
string categoryToFetch = selectedCategory.Value == "Default" ? null : selectedCategory.Value;
var request = new GetSeasonalBackgroundsRequest(categoryToFetch);
request.Success += response =>
{
currentBackgrounds.Value = response;
currentBackgroundIndex = RNG.Next(0, response.Backgrounds?.Count ?? 0);
BackgroundChanged?.Invoke();
};
api.PerformAsync(request);
@@ -66,32 +126,15 @@ namespace osu.Game.Graphics.Backgrounds
public SeasonalBackground LoadNextBackground()
{
if (!shouldShowSeasonal)
return null;
if (!shouldShowCustomBackgrounds || !shouldFetchCustomBackgrounds || currentBackgrounds.Value?.Backgrounds?.Any() != true)
return (SeasonalBackground)(new Background($@"Menu/menu-background-{RNG.Next(1, 9)}"));
var backgrounds = seasonalBackgrounds.Value.Backgrounds;
current = (current + 1) % backgrounds.Count;
string url = backgrounds[current].Url;
var backgrounds = currentBackgrounds.Value.Backgrounds;
currentBackgroundIndex = (currentBackgroundIndex + 1) % backgrounds.Count;
string url = backgrounds[currentBackgroundIndex].Url;
return new SeasonalBackground(url);
}
private bool shouldShowSeasonal
{
get
{
if (seasonalBackgroundMode.Value == SeasonalBackgroundMode.Never)
return false;
if (seasonalBackgroundMode.Value == SeasonalBackgroundMode.Sometimes && !isInSeason)
return false;
return seasonalBackgrounds.Value?.Backgrounds?.Any() == true;
}
}
private bool isInSeason => seasonalBackgrounds.Value != null && DateTimeOffset.Now < seasonalBackgrounds.Value.EndDate;
}
[LongRunningLoad]

View File

@@ -129,7 +129,7 @@ namespace osu.Game.Graphics
switch (status)
{
case BeatmapOnlineStatus.None:
return Color4.RosyBrown;
return Color4.AliceBlue;
case BeatmapOnlineStatus.LocallyModified:
return Color4.OrangeRed;
@@ -183,6 +183,9 @@ namespace osu.Game.Graphics
case ModType.System:
return Yellow;
case ModType.Special:
return Orange2;
default:
throw new ArgumentOutOfRangeException(nameof(modType), modType, "Unknown mod type");
}

View File

@@ -140,9 +140,9 @@ namespace osu.Game.Localisation
public static LocalisableString LoadInBrowserAfterSubmission => new TranslatableString(getKey(@"load_in_browser_after_submission"), @"Load in browser after submission");
/// <summary>
/// "Note: In order to make it possible for users of all osu! versions to enjoy your beatmap, it will be exported in a backwards-compatible format. While we have made efforts to ensure that this process keeps the beatmap playable in its intended form, some data related to features that previous versions of osu! do not support may be lost."
/// "Note: In order to make it possible for users of all game versions to enjoy your beatmap, it will be exported in a backwards-compatible format. While we have made efforts to ensure that this process keeps the beatmap playable in its intended form, some data related to features that previous versions of osu! do not support may be lost."
/// </summary>
public static LocalisableString LegacyExportDisclaimer => new TranslatableString(getKey(@"legacy_export_disclaimer"), @"Note: In order to make it possible for users of all osu! versions to enjoy your beatmap, it will be exported in a backwards-compatible format. While we have made efforts to ensure that this process keeps the beatmap playable in its intended form, some data related to features that previous versions of osu! do not support may be lost.");
public static LocalisableString LegacyExportDisclaimer => new TranslatableString(getKey(@"legacy_export_disclaimer"), @"Note: In order to make it possible for users of all game versions to enjoy your beatmap, it will be exported in a backwards-compatible format. While we have made efforts to ensure that this process keeps the beatmap playable in its intended form, some data related to features that previous versions of osu! do not support may be lost.");
/// <summary>
/// "Empty beatmaps cannot be submitted."

View File

@@ -78,6 +78,23 @@ Your experience will not be perfect, and may even feel subpar compared to games
Please bear with us as we continue to improve the game for you!");
/// <summary>
/// "Welcome to jvnkosu!"
/// </summary>
public static LocalisableString GreetingNotification => new TranslatableString(getKey(@"greeting_notification"), @"Welcome to jvnkosu!");
/// <summary>
/// "Failed to load backgrounds!
/// Please check your internet connection"
/// </summary>
public static LocalisableString SeasonalBackgroundsFail => new TranslatableString(getKey(@"seasonal_backgrounds_fail"),
@"Failed to load backgrounds!
Please check your internet connection"); // TODO: implement l10n in osu-resources
/// <summary>
/// "Successfully refreshed background categories!"
/// </summary>
public static LocalisableString SeasonalBackgroundsRefreshed => new TranslatableString(getKey(@"seasonal_backgrounds_refreshed"), @"Successfully refreshed background categories!");
private static string getKey(string key) => $@"{prefix}:{key}";
}
}

View File

@@ -65,14 +65,14 @@ namespace osu.Game.Localisation
public static LocalisableString LightweightLinkingNotSupported => new TranslatableString(getKey(@"lightweight_linking_not_supported"), @"Lightweight linking of files is not supported on your operating system yet, so a copy of all files will be made during import.");
/// <summary>
/// "A second copy of all files will be made during import. To avoid this, please make sure the lazer data folder is on the same drive as your previous osu! install (and the file system is NTFS)."
/// "A second copy of all files will be made during import. To avoid this, please make sure this game's data folder is on the same drive as your previous osu! install (and the file system is NTFS)."
/// </summary>
public static LocalisableString SecondCopyWillBeMadeWindows => new TranslatableString(getKey(@"second_copy_will_be_made_windows"), @"A second copy of all files will be made during import. To avoid this, please make sure the lazer data folder is on the same drive as your previous osu! install (and the file system is NTFS).");
public static LocalisableString SecondCopyWillBeMadeWindows => new TranslatableString(getKey(@"second_copy_will_be_made_windows"), @"A second copy of all files will be made during import. To avoid this, please make sure this game's data folder is on the same drive as your previous osu! install (and the file system is NTFS).");
/// <summary>
/// "A second copy of all files will be made during import. To avoid this, please make sure the lazer data folder is on the same drive as your previous osu! install (and the file system supports hard links)."
/// "A second copy of all files will be made during import. To avoid this, please make sure this game's data folder is on the same drive as your previous osu! install (and the file system supports hard links)."
/// </summary>
public static LocalisableString SecondCopyWillBeMadeOtherPlatforms => new TranslatableString(getKey(@"second_copy_will_be_made_other_platforms"), @"A second copy of all files will be made during import. To avoid this, please make sure the lazer data folder is on the same drive as your previous osu! install (and the file system supports hard links).");
public static LocalisableString SecondCopyWillBeMadeOtherPlatforms => new TranslatableString(getKey(@"second_copy_will_be_made_other_platforms"), @"A second copy of all files will be made during import. To avoid this, please make sure this game's data folder is on the same drive as your previous osu! install (and the file system supports hard links).");
private static string getKey(string key) => $@"{prefix}:{key}";
}

View File

@@ -15,9 +15,9 @@ namespace osu.Game.Localisation
public static LocalisableString Header => new TranslatableString(getKey(@"header"), @"Obtaining Beatmaps");
/// <summary>
/// "&quot;Beatmaps&quot; are what we call sets of playable levels. osu! doesn&#39;t come with any beatmaps pre-loaded. This step will help you get started on your beatmap collection."
/// "&quot;Beatmaps&quot; are what we call sets of playable levels. Game doesn&#39;t come with any beatmaps pre-loaded. This step will help you get started on your beatmap collection."
/// </summary>
public static LocalisableString Description => new TranslatableString(getKey(@"description"), @"""Beatmaps"" are what we call sets of playable levels. osu! doesn't come with any beatmaps pre-loaded. This step will help you get started on your beatmap collection.");
public static LocalisableString Description => new TranslatableString(getKey(@"description"), @"""Beatmaps"" are what we call sets of playable levels. Game doesn't come with any beatmaps pre-loaded. This step will help you get started on your beatmap collection.");
/// <summary>
/// "If you are a new player, we recommend playing through the tutorial to get accustomed to the gameplay."
@@ -25,9 +25,9 @@ namespace osu.Game.Localisation
public static LocalisableString TutorialDescription => new TranslatableString(getKey(@"tutorial_description"), @"If you are a new player, we recommend playing through the tutorial to get accustomed to the gameplay.");
/// <summary>
/// "Get the osu! tutorial"
/// "Get the tutorial"
/// </summary>
public static LocalisableString TutorialButton => new TranslatableString(getKey(@"tutorial_button"), @"Get the osu! tutorial");
public static LocalisableString TutorialButton => new TranslatableString(getKey(@"tutorial_button"), @"Get the tutorial");
/// <summary>
/// "To get you started, we have some recommended beatmaps."

View File

@@ -38,16 +38,16 @@ namespace osu.Game.Localisation
/// <summary>
/// "Welcome to the first-run setup guide!
///
/// osu! is a very configurable game, and diving straight into the settings can sometimes be overwhelming. This guide will help you get the important choices out of the way to ensure a great first experience!"
/// This game is very configurable, and diving straight into the settings can sometimes be overwhelming. This guide will help you get the important choices out of the way to ensure a great first experience!"
/// </summary>
public static LocalisableString WelcomeDescription => new TranslatableString(getKey(@"welcome_description"), @"Welcome to the first-run setup guide!
osu! is a very configurable game, and diving straight into the settings can sometimes be overwhelming. This guide will help you get the important choices out of the way to ensure a great first experience!");
This game is very configurable, and diving straight into the settings can sometimes be overwhelming. This guide will help you get the important choices out of the way to ensure a great first experience!");
/// <summary>
/// "The size of the osu! user interface can be adjusted to your liking."
/// "The size of the game's user interface can be adjusted to your liking."
/// </summary>
public static LocalisableString UIScaleDescription => new TranslatableString(getKey(@"ui_scale_description"), @"The size of the osu! user interface can be adjusted to your liking.");
public static LocalisableString UIScaleDescription => new TranslatableString(getKey(@"ui_scale_description"), @"The size of the game's user interface can be adjusted to your liking.");
/// <summary>
/// "Behaviour"

View File

@@ -60,9 +60,9 @@ namespace osu.Game.Localisation
public static LocalisableString CheckingForUpdates => new TranslatableString(getKey(@"checking_for_updates"), @"Checking for updates");
/// <summary>
/// "Open osu! folder"
/// "Open game folder"
/// </summary>
public static LocalisableString OpenOsuFolder => new TranslatableString(getKey(@"open_osu_folder"), @"Open osu! folder");
public static LocalisableString OpenOsuFolder => new TranslatableString(getKey(@"open_osu_folder"), @"Open game folder");
/// <summary>
/// "Export logs"

View File

@@ -155,9 +155,9 @@ namespace osu.Game.Localisation
public static LocalisableString ChangeRendererConfirmation => new TranslatableString(getKey(@"change_renderer_configuration"), @"In order to change the renderer, the game will close. Please open it again.");
/// <summary>
/// "Minimise osu! when switching to another app"
/// "Minimise game when switching to another app"
/// </summary>
public static LocalisableString MinimiseOnFocusLoss => new TranslatableString(getKey(@"minimise_on_focus_loss"), @"Minimise osu! when switching to another app");
public static LocalisableString MinimiseOnFocusLoss => new TranslatableString(getKey(@"minimise_on_focus_loss"), @"Minimise game when switching to another app");
/// <summary>
/// "Shrink game to avoid cameras and notches"

View File

@@ -15,9 +15,9 @@ namespace osu.Game.Localisation
public static LocalisableString CheckingForFullscreenCapabilities => new TranslatableString(getKey(@"checking_for_fullscreen_capabilities"), @"Checking for fullscreen capabilities...");
/// <summary>
/// "osu! is running in exclusive fullscreen, guaranteeing low latency!"
/// "Game is running in exclusive fullscreen, guaranteeing low latency!"
/// </summary>
public static LocalisableString OsuIsRunningExclusiveFullscreen => new TranslatableString(getKey(@"osu_is_running_exclusive_fullscreen"), @"osu! is running in exclusive fullscreen, guaranteeing low latency!");
public static LocalisableString OsuIsRunningExclusiveFullscreen => new TranslatableString(getKey(@"osu_is_running_exclusive_fullscreen"), @"Game is running in exclusive fullscreen, guaranteeing low latency!");
/// <summary>
/// "Unable to run in exclusive fullscreen. You may experience some input latency."

View File

@@ -40,9 +40,9 @@ namespace osu.Game.Localisation
public static LocalisableString PleaseSignInToViewOnlineLeaderboards => new TranslatableString(getKey(@"please_sign_in_to_view_online_leaderboards"), @"Please sign in to view online leaderboards!");
/// <summary>
/// "Please invest in an osu!supporter tag to view this leaderboard!"
/// "Please invest in a supporter tag to view this leaderboard!"
/// </summary>
public static LocalisableString PleaseInvestInAnOsuSupporterTagToViewThisLeaderboard => new TranslatableString(getKey(@"please_invest_in_an_osu_supporter_tag_to_view_this_leaderboard"), @"Please invest in an osu!supporter tag to view this leaderboard!");
public static LocalisableString PleaseInvestInAnOsuSupporterTagToViewThisLeaderboard => new TranslatableString(getKey(@"please_invest_in_an_osu_supporter_tag_to_view_this_leaderboard"), @"Please invest in a supporter tag to view this leaderboard!");
/// <summary>
/// "You are not on a team. Maybe you should join one!"

View File

@@ -45,9 +45,9 @@ namespace osu.Game.Localisation
public static LocalisableString TargetDirectoryAlreadyInstalledOsu => new TranslatableString(getKey(@"target_directory_already_installed_osu"), @"The target directory already seems to have an osu! install. Use that data instead?");
/// <summary>
/// "To complete this operation, osu! will close. Please open it again to use the new data location."
/// "To complete this operation, the game will close. Please open it again to use the new data location."
/// </summary>
public static LocalisableString RestartAndReOpenRequiredForCompletion => new TranslatableString(getKey(@"restart_and_re_open_required_for_completion"), @"To complete this operation, osu! will close. Please open it again to use the new data location.");
public static LocalisableString RestartAndReOpenRequiredForCompletion => new TranslatableString(getKey(@"restart_and_re_open_required_for_completion"), @"To complete this operation, the game will close. Please open it again to use the new data location.");
/// <summary>
/// "Delete ALL beatmaps"

View File

@@ -45,11 +45,11 @@ namespace osu.Game.Localisation
public static LocalisableString NoAutoplayMod => new TranslatableString(getKey(@"no_autoplay_mod"), @"The current ruleset doesn't have an autoplay mod available!");
/// <summary>
/// "osu! doesn&#39;t seem to be able to play audio correctly.
/// "Game doesn&#39;t seem to be able to play audio correctly.
///
/// Please try changing your audio device to a working setting."
/// </summary>
public static LocalisableString AudioPlaybackIssue => new TranslatableString(getKey(@"audio_playback_issue"), @"osu! doesn't seem to be able to play audio correctly.
public static LocalisableString AudioPlaybackIssue => new TranslatableString(getKey(@"audio_playback_issue"), @"Game doesn't seem to be able to play audio correctly.
Please try changing your audio device to a working setting.");
@@ -104,10 +104,10 @@ Please try changing your audio device to a working setting.");
public static LocalisableString MismatchingBeatmapForReplay => new TranslatableString(getKey(@"mismatching_beatmap_for_replay"), @"Your local copy of the beatmap for this replay appears to be different than expected. You may need to update or re-download it.");
/// <summary>
/// "You are now running osu! {0}.
/// "You are now running game version {0}.
/// Click to see what&#39;s new!"
/// </summary>
public static LocalisableString GameVersionAfterUpdate(string version) => new TranslatableString(getKey(@"game_version_after_update"), @"You are now running osu! {0}.
public static LocalisableString GameVersionAfterUpdate(string version) => new TranslatableString(getKey(@"game_version_after_update"), @"You are now running game version {0}.
Click to see what's new!", version);
/// <summary>

View File

@@ -10,9 +10,9 @@ namespace osu.Game.Localisation
private const string prefix = @"osu.Game.Resources.Localisation.OnlinePlay";
/// <summary>
/// "Playlist durations longer than 2 weeks require an active osu!supporter tag."
/// "Playlist durations longer than 2 weeks require an active supporter tag."
/// </summary>
public static LocalisableString SupporterOnlyDurationNotice => new TranslatableString(getKey(@"supporter_only_duration_notice"), @"Playlist durations longer than 2 weeks require an active osu!supporter tag.");
public static LocalisableString SupporterOnlyDurationNotice => new TranslatableString(getKey(@"supporter_only_duration_notice"), @"Playlist durations longer than 2 weeks require an active supporter tag.");
/// <summary>
/// "Can&#39;t invite this user as you have blocked them or they have blocked you."

View File

@@ -15,9 +15,9 @@ namespace osu.Game.Localisation
public static LocalisableString HeaderTitle => new TranslatableString(getKey(@"header_title"), @"settings");
/// <summary>
/// "change the way osu! behaves"
/// "change the way your game behaves"
/// </summary>
public static LocalisableString HeaderDescription => new TranslatableString(getKey(@"header_description"), @"change the way osu! behaves");
public static LocalisableString HeaderDescription => new TranslatableString(getKey(@"header_description"), @"change the way your game behaves");
private static string getKey(string key) => $"{prefix}:{key}";
}

View File

@@ -40,9 +40,9 @@ namespace osu.Game.Localisation
public static LocalisableString LocallyModifiedTooltip => new TranslatableString(getKey(@"locally_modified_tooltip"), @"Has been locally modified");
/// <summary>
/// "Unknown"
/// "Offline"
/// </summary>
public static LocalisableString StatusUnknown => new TranslatableString(getKey(@"status_unknown"), @"Unknown");
public static LocalisableString StatusUnknown => new TranslatableString(getKey(@"status_unknown"), @"Offline");
/// <summary>
/// "Total Plays"

View File

@@ -10,19 +10,19 @@ namespace osu.Game.Localisation
private const string prefix = @"osu.Game.Resources.Localisation.StorageErrorDialog";
/// <summary>
/// "osu! storage error"
/// "Game storage error"
/// </summary>
public static LocalisableString StorageError => new TranslatableString(getKey(@"storage_error"), @"osu! storage error");
public static LocalisableString StorageError => new TranslatableString(getKey(@"storage_error"), @"Game storage error");
/// <summary>
/// "The specified osu! data location (&quot;{0}&quot;) is not accessible. If it is on external storage, please reconnect the device and try again."
/// "The specified game data location (&quot;{0}&quot;) is not accessible. If it is on external storage, please reconnect the device and try again."
/// </summary>
public static LocalisableString LocationIsNotAccessible(string? loc) => new TranslatableString(getKey(@"location_is_not_accessible"), @"The specified osu! data location (""{0}"") is not accessible. If it is on external storage, please reconnect the device and try again.", loc);
public static LocalisableString LocationIsNotAccessible(string? loc) => new TranslatableString(getKey(@"location_is_not_accessible"), @"The specified game data location (""{0}"") is not accessible. If it is on external storage, please reconnect the device and try again.", loc);
/// <summary>
/// "The specified osu! data location (&quot;{0}&quot;) is empty. If you have moved the files, please close osu! and move them back."
/// "The specified game data location (&quot;{0}&quot;) is empty. If you have moved the files, please close the game and move them back."
/// </summary>
public static LocalisableString LocationIsEmpty(string? loc2) => new TranslatableString(getKey(@"location_is_empty"), @"The specified osu! data location (""{0}"") is empty. If you have moved the files, please close osu! and move them back.", loc2);
public static LocalisableString LocationIsEmpty(string? loc2) => new TranslatableString(getKey(@"location_is_empty"), @"The specified game data location (""{0}"") is empty. If you have moved the files, please close the game and move them back.", loc2);
/// <summary>
/// "Try again"

View File

@@ -50,9 +50,9 @@ namespace osu.Game.Localisation
public static LocalisableString InterfaceVoices => new TranslatableString(getKey(@"interface_voices"), @"Interface voices");
/// <summary>
/// "osu! music theme"
/// "Music theme"
/// </summary>
public static LocalisableString OsuMusicTheme => new TranslatableString(getKey(@"osu_music_theme"), @"osu! music theme");
public static LocalisableString OsuMusicTheme => new TranslatableString(getKey(@"osu_music_theme"), @"Music theme");
/// <summary>
/// "Intro sequence"
@@ -64,15 +64,30 @@ namespace osu.Game.Localisation
/// </summary>
public static LocalisableString BackgroundSource => new TranslatableString(getKey(@"background_source"), @"Background source");
/// <summary>
/// "Use custom backgrounds from server"
/// </summary>
public static LocalisableString UseSeasonalBackgrounds => new TranslatableString(getKey(@"use_seasonal_backgrounds"), @"Use custom backgrounds from server");
/// <summary>
/// "Seasonal backgrounds"
/// </summary>
public static LocalisableString SeasonalBackgrounds => new TranslatableString(getKey(@"seasonal_backgrounds"), @"Seasonal backgrounds");
/// <summary>
/// "Changes to this setting will only apply with an active osu!supporter tag."
/// "Background categories"
/// </summary>
public static LocalisableString NotSupporterNote => new TranslatableString(getKey(@"not_supporter_note"), @"Changes to this setting will only apply with an active osu!supporter tag.");
public static LocalisableString SeasonalBackgroundsCategories => new TranslatableString(getKey(@"seasonal_backgrounds_categories"), @"Background categories");
/// <summary>
/// "Refresh categories"
/// </summary>
public static LocalisableString SeasonalBackgroundsRefresh => new TranslatableString(getKey(@"seasonal_backgrounds_refresh"), @"Refresh categories");
/// <summary>
/// "Changes to this setting will only apply with an active supporter tag."
/// </summary>
public static LocalisableString NotSupporterNote => new TranslatableString(getKey(@"not_supporter_note"), @"Changes to this setting will only apply with an active supporter tag.");
/// <summary>
/// "Song Select"
@@ -169,6 +184,16 @@ namespace osu.Game.Localisation
/// </summary>
public static LocalisableString SelectedMods => new TranslatableString(getKey(@"selected_mods"), @"Selected Mods");
/// <summary>
/// "Use legacy song select (SelectV1)"
/// </summary>
public static LocalisableString ForceLegacySongSelect => new TranslatableString(getKey(@"force_select_v1"), @"Use legacy song select (SelectV1)");
/// <summary>
/// "Main menu logo colour"
/// </summary>
public static LocalisableString LogoColour => new TranslatableString(getKey(@"menu_logo_colour"), @"Main menu logo colour");
private static string getKey(string key) => $@"{prefix}:{key}";
}
}

View File

@@ -0,0 +1,12 @@
// 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.Online.API.Requests.Responses;
namespace osu.Game.Online.API.Requests
{
public class GetBackgroundCategoriesRequest : APIRequest<APIBackgroundCategories>
{
protected override string Target => @"seasonal-backgrounds-categories";
}
}

View File

@@ -8,7 +8,7 @@ namespace osu.Game.Online.API.Requests
public class GetMenuContentRequest : OsuJsonWebRequest<APIMenuContent>
{
public GetMenuContentRequest()
: base(@"https://assets.ppy.sh/menu-content.json")
: base(@"https://osu.jvnko.boats/uploads/menu-content.json") // TODO: backend
{
}
}

View File

@@ -1,12 +1,33 @@
// 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.
#nullable enable
using System.Net;
using osu.Game.Online.API.Requests.Responses;
namespace osu.Game.Online.API.Requests
{
public class GetSeasonalBackgroundsRequest : APIRequest<APISeasonalBackgrounds>
{
protected override string Target => @"seasonal-backgrounds";
private readonly string? category;
public GetSeasonalBackgroundsRequest()
{
}
public GetSeasonalBackgroundsRequest(string? category = null)
{
this.category = category;
}
protected override string Target => getPath();
private string getPath()
{
if (string.IsNullOrEmpty(category))
return @"seasonal-backgrounds";
return $@"seasonal-backgrounds?category={WebUtility.UrlEncode(category)}";
}
}
}

View File

@@ -33,6 +33,7 @@ namespace osu.Game.Online.API.Requests
Medal,
Rank,
RankLost,
RankRetracted,
UserSupportAgain,
UserSupportFirst,
UserSupportGift,

View File

@@ -0,0 +1,14 @@
// 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.Responses
{
public class APIBackgroundCategories
{
[JsonProperty("categories")]
public List<string>? Categories { get; set; }
}
}

View File

@@ -7,13 +7,13 @@ namespace osu.Game.Online
{
public DevelopmentEndpointConfiguration()
{
WebsiteUrl = APIUrl = @"https://dev.ppy.sh";
APIClientSecret = @"3LP2mhUrV89xxzD1YKNndXHEhWWCRLPNKioZ9ymT";
APIClientID = "5";
SpectatorUrl = $@"{APIUrl}/signalr/spectator";
MultiplayerUrl = $@"{APIUrl}/signalr/multiplayer";
MetadataUrl = $@"{APIUrl}/signalr/metadata";
BeatmapSubmissionServiceUrl = $@"{APIUrl}/beatmap-submission";
WebsiteUrl = APIUrl = @"https://osu.jvnko.boats";
APIClientSecret = @"ijBg9O6aULCYGnvEELYD3IdW7fqrYiFaoMdkzQNA";
APIClientID = "1";
SpectatorUrl = $@"https://osu-spec.jvnko.boats/spectator";
MultiplayerUrl = $@"https://osu-spec.jvnko.boats/multiplayer";
MetadataUrl = $@"https://osu-spec.jvnko.boats/metadata";
BeatmapSubmissionServiceUrl = $@"https://osu-bss.jvnko.boats";
}
}
}

View File

@@ -51,9 +51,9 @@ namespace osu.Game.Online.Leaderboards
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Spacing = new Vector2(-3, 0),
Padding = new MarginPadding { Top = 5 },
Padding = new MarginPadding { Top = -5 },
Colour = GetRankLetterColour(rank),
Font = OsuFont.Numeric.With(size: 25),
Font = OsuFont.TorusAlternate.With(size: 40, weight: FontWeight.Bold),
Text = GetRankLetter(rank),
ShadowColour = Color4.Black.Opacity(0.3f),
ShadowOffset = new Vector2(0, 0.08f),
@@ -72,12 +72,14 @@ namespace osu.Game.Online.Leaderboards
switch (rank)
{
case ScoreRank.SH:
return @"S";
return @"S+";
case ScoreRank.X:
case ScoreRank.XH:
return @"SS";
case ScoreRank.XH:
return @"SS+";
default:
return rank.ToString();
}

View File

@@ -7,13 +7,13 @@ namespace osu.Game.Online
{
public ProductionEndpointConfiguration()
{
WebsiteUrl = APIUrl = @"https://osu.ppy.sh";
APIClientSecret = @"FGc9GAtyHzeQDshWP5Ah7dega8hJACAJpQtw6OXk";
APIClientID = "5";
SpectatorUrl = "https://spectator.ppy.sh/spectator";
MultiplayerUrl = "https://spectator.ppy.sh/multiplayer";
MetadataUrl = "https://spectator.ppy.sh/metadata";
BeatmapSubmissionServiceUrl = "https://bss.ppy.sh";
WebsiteUrl = APIUrl = @"https://osu.jvnko.boats";
APIClientSecret = @"ijBg9O6aULCYGnvEELYD3IdW7fqrYiFaoMdkzQNA";
APIClientID = "1";
SpectatorUrl = $@"https://osu-spec.jvnko.boats/spectator";
MultiplayerUrl = $@"https://osu-spec.jvnko.boats/multiplayer";
MetadataUrl = $@"https://osu-spec.jvnko.boats/metadata";
BeatmapSubmissionServiceUrl = $@"https://osu-bss.jvnko.boats";
}
}
}

View File

@@ -11,7 +11,7 @@ namespace osu.Game.Online
{
protected override string GetLookupUrl(string url)
{
if (!Uri.TryCreate(url, UriKind.Absolute, out Uri? uri) || !uri.Host.EndsWith(@".ppy.sh", StringComparison.OrdinalIgnoreCase))
if (!Uri.TryCreate(url, UriKind.Absolute, out Uri? uri) || !uri.Host.EndsWith(@".jvnko.boats", StringComparison.OrdinalIgnoreCase))
{
Logger.Log($@"Blocking resource lookup from external website: {url}", LoggingTarget.Network, LogLevel.Important);
return string.Empty;

View File

@@ -17,6 +17,7 @@ using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Bindables;
using osu.Framework.Configuration;
using osu.Framework.Development;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Extensions.TypeExtensions;
using osu.Framework.Graphics;
@@ -38,6 +39,7 @@ using osu.Game.Configuration;
using osu.Game.Database;
using osu.Game.Extensions;
using osu.Game.Graphics;
using osu.Game.Graphics.Backgrounds;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.UserInterface;
using osu.Game.Input;
@@ -94,9 +96,9 @@ namespace osu.Game
{
#if DEBUG
// Different port allows running release and debug builds alongside each other.
public const string IPC_PIPE_NAME = "osu-lazer-debug";
public const string IPC_PIPE_NAME = "jvnkosu-debug";
#else
public const string IPC_PIPE_NAME = "osu-lazer";
public const string IPC_PIPE_NAME = "jvnkosu";
#endif
/// <summary>
@@ -171,6 +173,9 @@ namespace osu.Game
[Cached]
private readonly ScreenshotManager screenshotManager = new ScreenshotManager();
[Cached]
private SeasonalBackgroundLoader seasonalBackgroundLoader;
protected SentryLogger SentryLogger;
public virtual StableStorage GetStorageForStableInstall() => null;
@@ -262,6 +267,8 @@ namespace osu.Game
tabletLogNotifyOnError = true;
}, true);
});
initializeSeasonalBackgrounds();
}
#region IOverlayManager
@@ -1179,7 +1186,7 @@ namespace osu.Game
Margin = new MarginPadding(5),
}, topMostOverlayContent.Add);
if (!IsDeployedBuild)
if (!IsDeployedBuild && DebugUtils.IsDebugBuild)
loadComponentSingleFile(devBuildBanner = new DevBuildBanner(), ScreenContainer.Add);
loadComponentSingleFile(osuLogo, _ =>
@@ -1225,6 +1232,8 @@ namespace osu.Game
loadComponentSingleFile(screenshotManager, Add);
loadComponentSingleFile(seasonalBackgroundLoader, Add);
// dependency on notification overlay, dependent by settings overlay
loadComponentSingleFile(CreateUpdateManager(), Add, true);
@@ -1455,6 +1464,40 @@ namespace osu.Game
}
}
private void initializeSeasonalBackgrounds()
{
seasonalBackgroundLoader = new SeasonalBackgroundLoader();
seasonalBackgroundLoader.OnCategoriesRefreshed += handleCategoriesRefreshed;
seasonalBackgroundLoader.OnLoadFailure += handleBackgroundLoadFailure;
}
private void handleCategoriesRefreshed()
{
Schedule(() =>
{
Notifications?.Post(new SimpleNotification
{
Text = ButtonSystemStrings.SeasonalBackgroundsRefreshed,
Icon = FontAwesome.Solid.CheckCircle,
Transient = true
});
});
}
private void handleBackgroundLoadFailure(Exception exception)
{
Schedule(() =>
{
Notifications?.Post(new SimpleErrorNotification
{
Text = ButtonSystemStrings.SeasonalBackgroundsFail,
Icon = FontAwesome.Solid.ExclamationTriangle,
Transient = true
});
});
}
private Task asyncLoadStream;
/// <summary>
@@ -1687,6 +1730,13 @@ namespace osu.Game
{
case IntroScreen intro:
introScreen = intro;
// SimpleNotification notification = new SimpleNotification
// {
// Text = ButtonSystemStrings.GreetingNotification,
// Transient = true,
// PopInSampleName = "",
// };
// Notifications?.Post(notification);
devBuildBanner?.Show();
break;

View File

@@ -76,12 +76,12 @@ namespace osu.Game
public partial class OsuGameBase : Framework.Game, ICanAcceptFiles, IBeatSyncProvider
{
#if DEBUG
public const string GAME_NAME = "osu! (development)";
public const string GAME_NAME = "jvnkosu! (development)";
#else
public const string GAME_NAME = "osu!";
public const string GAME_NAME = "jvnkosu!";
#endif
public const string OSU_PROTOCOL = "osu://";
public const string OSU_PROTOCOL = "jnvkosu://";
/// <summary>
/// The filename of the main client database.
@@ -298,7 +298,7 @@ namespace osu.Game
Audio.Samples.PlaybackConcurrency = SAMPLE_CONCURRENCY;
dependencies.Cache(SkinManager = new SkinManager(Storage, realm, Host, Resources, Audio, Scheduler));
dependencies.Cache(SkinManager = new SkinManager(Storage, realm, Host, Resources, Audio, Scheduler, LocalConfig));
dependencies.CacheAs<ISkinSource>(SkinManager);
EndpointConfiguration endpoints = CreateEndpoints();
@@ -322,7 +322,7 @@ namespace osu.Game
// ordering is important here to ensure foreign keys rules are not broken in ModelStore.Cleanup()
dependencies.Cache(ScoreManager = new ScoreManager(RulesetStore, () => BeatmapManager, Storage, realm, API, LocalConfig));
dependencies.Cache(BeatmapManager = new BeatmapManager(Storage, realm, API, Audio, Resources, Host, defaultBeatmap, difficultyCache, performOnlineLookups: true));
dependencies.Cache(BeatmapManager = new BeatmapManager(Storage, realm, API, Audio, Resources, Host, defaultBeatmap, difficultyCache, performOnlineLookups: true, config: LocalConfig));
dependencies.CacheAs<IWorkingBeatmapCache>(BeatmapManager);
dependencies.Cache(BeatmapDownloader = new BeatmapModelDownloader(BeatmapManager, API));
@@ -718,6 +718,7 @@ namespace osu.Game
if (Interlocked.Decrement(ref allowableExceptions) < 0)
{
Logger.Log("Too many unhandled exceptions, crashing out.");
RulesetStore?.TryDisableCustomRulesetsCausing(ex);
return false;
}

View File

@@ -191,7 +191,8 @@ namespace osu.Game.Overlays.BeatmapSet
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
TextSize = 14,
TextPadding = new MarginPadding { Horizontal = 35, Vertical = 10 }
TextPadding = new MarginPadding { Horizontal = 35, Vertical = 10 },
ShowUnknownStatus = true
},
storyboardIconPill = new StoryboardIconPill
{

View File

@@ -84,8 +84,12 @@ namespace osu.Game.Overlays
performAfterFetch(() =>
{
string versionPart = version.Split('-')[0];
string updateStream = version.Split('-')[1];
string[] versionIdentifier = version.Split('-');
string versionPart = versionIdentifier[0];
string updateStream = versionIdentifier.Length >= 2
? versionIdentifier[1]
: "lazer"; // let's assume it's lazer by default, seems like the most sane option
var build = builds.Find(b => b.Version == versionPart && b.UpdateStream.Name == updateStream)
?? Streams.Find(s => s.Name == updateStream)?.LatestBuild;

View File

@@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
using osu.Framework.Development;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
@@ -9,6 +10,7 @@ using osu.Framework.Graphics.Textures;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Overlays
{
@@ -26,21 +28,42 @@ namespace osu.Game.Overlays
AddRange(new Drawable[]
{
new OsuSpriteText
new FillFlowContainer
{
Anchor = Anchor.BottomCentre,
Origin = Anchor.BottomCentre,
Font = OsuFont.Numeric.With(weight: FontWeight.Bold, size: 12),
Colour = colours.YellowDark,
Text = @"DEVELOPER BUILD",
},
new Sprite
{
Anchor = Anchor.BottomCentre,
Origin = Anchor.BottomCentre,
Texture = textures.Get(@"Menu/dev-build-footer"),
Scale = new Vector2(0.4f, 1),
Y = 2,
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Vertical,
Children = new Drawable[]
{
new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal,
Spacing = new Vector2(5),
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
Children = new Drawable[]
{
new OsuSpriteText
{
Font = OsuFont.GetFont(weight: FontWeight.Bold),
Text = game.Name
},
new OsuSpriteText
{
Colour = DebugUtils.IsDebugBuild ? colours.Red : Color4.White,
Text = game.Version
},
}
},
new Sprite
{
// Anchor = Anchor.BottomCentre,
// Origin = Anchor.BottomCentre,
Texture = textures.Get(@"Menu/dev-build-footer"),
Scale = new Vector2(0.4f, 1),
Y = 2,
}
},
},
});
}

View File

@@ -329,6 +329,7 @@ namespace osu.Game.Overlays.Mods
yield return createModColumnContent(ModType.Automation);
yield return createModColumnContent(ModType.Conversion);
yield return createModColumnContent(ModType.Fun);
yield return createModColumnContent(ModType.Special);
}
private ColumnDimContainer createModColumnContent(ModType modType)

View File

@@ -78,12 +78,13 @@ namespace osu.Game.Overlays.Profile.Header
private void updateDisplay(APIUser? user)
{
var cutoffDate = new DateTime(2025, 8, 25);
topLinkContainer.Clear();
bottomLinkContainer.Clear();
if (user == null) return;
if (user.JoinDate.ToUniversalTime().Year < 2008)
if (user.JoinDate.ToUniversalTime().Date < cutoffDate)
topLinkContainer.AddText(UsersStrings.ShowFirstMembers);
else
{

View File

@@ -189,6 +189,13 @@ namespace osu.Game.Overlays.Profile.Sections.Recent
addText($" ({getRulesetName()})");
break;
case RecentActivityType.RankRetracted:
addUserLink();
addText("'s score on ");
addBeatmapLink();
addText($" has been retracted ({getRulesetName()})");
break;
case RecentActivityType.UserSupportAgain:
addUserLink();
addText(" has once again chosen to support osu! - thanks for your generosity!");

View File

@@ -75,6 +75,11 @@ namespace osu.Game.Overlays.Profile.Sections.Recent
icon.Colour = Color4.White;
break;
case RecentActivityType.RankRetracted:
icon.Icon = FontAwesome.Solid.Ban;
icon.Colour = colours.Red1;
break;
case RecentActivityType.UserSupportAgain:
icon.Icon = FontAwesome.Solid.Heart;
icon.Colour = colours.Pink;

View File

@@ -13,6 +13,9 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay
{
protected override LocalisableString Header => GameplaySettingsStrings.AudioHeader;
private SettingsCheckbox alwaysPlayFirst = null!;
private SettingsCheckbox alwaysPlay = null!;
[BackgroundDependencyLoader]
private void load(OsuConfigManager config, OsuConfigManager osuConfig)
{
@@ -26,13 +29,23 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay
KeyboardStep = 0.01f,
DisplayAsPercentage = true
},
new SettingsCheckbox
alwaysPlayFirst = new SettingsCheckbox
{
ClassicDefault = false,
LabelText = GameplaySettingsStrings.AlwaysPlayFirstComboBreak,
Current = config.GetBindable<bool>(OsuSetting.AlwaysPlayFirstComboBreak)
},
alwaysPlay = new SettingsCheckbox
{
ClassicDefault = false,
LabelText = "Always play combo break sound",
Current = config.GetBindable<bool>(OsuSetting.AlwaysPlayComboBreak)
}
};
alwaysPlay.Current.BindValueChanged(d =>
{
alwaysPlayFirst.Current.Disabled = d.NewValue;
}, true);
}
}
}

View File

@@ -54,7 +54,7 @@ namespace osu.Game.Overlays.Settings.Sections.General
Text = GeneralSettingsStrings.ReportIssue,
TooltipText = GeneralSettingsStrings.ReportIssueTooltip,
BackgroundColour = colours.YellowDarker,
Action = () => game?.OpenUrlExternally(@"https://osu.ppy.sh/community/forums/topics/create?forum_id=5", LinkWarnMode.NeverWarn)
Action = () => game?.OpenUrlExternally(@"https://osu.jvnko.boats/community/forums/topics/create?forum_id=5", LinkWarnMode.NeverWarn)
},
});

View File

@@ -8,6 +8,7 @@ using osu.Framework.Graphics;
using osu.Framework.Localisation;
using osu.Framework.Platform;
using osu.Framework.Screens;
using osu.Game.Configuration;
using osu.Game.Localisation;
using osu.Game.Screens;
using osu.Game.Screens.Import;
@@ -22,13 +23,19 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
private ISystemFileSelector? selector;
[BackgroundDependencyLoader]
private void load(OsuGameBase game, GameHost host, IPerformFromScreenRunner? performer)
private void load(OsuGameBase game, GameHost host, IPerformFromScreenRunner? performer, OsuConfigManager config)
{
if ((selector = host.CreateSystemFileSelector(game.HandledExtensions.ToArray())) != null)
selector.Selected += f => Task.Run(() => game.Import(f.FullName));
AddRange(new Drawable[]
{
new SettingsCheckbox
{
LabelText = "Delete archives on import",
Current = config.GetBindable<bool>(OsuSetting.DeleteImportedArchives),
ClassicDefault = true
},
new SettingsButton
{
Text = DebugSettingsStrings.ImportFiles,
@@ -44,7 +51,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
{
Text = DebugSettingsStrings.RunLatencyCertifier,
Action = () => performer?.PerformFromScreen(menu => menu.Push(new LatencyCertifierScreen()))
}
},
});
}
}

View File

@@ -8,6 +8,7 @@ using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Localisation;
using osu.Game.Configuration;
using osu.Game.Graphics.Backgrounds;
using osu.Game.Localisation;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests.Responses;
@@ -18,21 +19,55 @@ namespace osu.Game.Overlays.Settings.Sections.UserInterface
{
protected override LocalisableString Header => UserInterfaceStrings.MainMenuHeader;
[Resolved]
private SeasonalBackgroundLoader backgroundLoader { get; set; }
private IBindable<APIUser> user;
private SettingsEnumDropdown<BackgroundSource> backgroundSourceDropdown;
private Bindable<bool> useSeasonalBackgrounds;
[BackgroundDependencyLoader]
private void load(OsuConfigManager config, IAPIProvider api)
{
user = api.LocalUser.GetBoundCopy();
useSeasonalBackgrounds = config.GetBindable<bool>(OsuSetting.UseSeasonalBackgroundsV2);
var backgroundToggle = new SettingsCheckbox
{
LabelText = UserInterfaceStrings.UseSeasonalBackgrounds,
Current = config.GetBindable<bool>(OsuSetting.UseSeasonalBackgroundsV2),
ClassicDefault = true
};
var categoryDropdown = new SettingsDropdown<string>
{
LabelText = UserInterfaceStrings.SeasonalBackgroundsCategories,
Current = config.GetBindable<string>(OsuSetting.BackgroundCategory)
};
var refreshButton = new SettingsButton
{
Text = UserInterfaceStrings.SeasonalBackgroundsRefresh,
Action = () => backgroundLoader.RefreshCategories()
};
// TODO: the category dropdown disappear if no backgrounds (e.g. when first enabling the setting)
refreshButton.CanBeShown.BindTo(useSeasonalBackgrounds);
categoryDropdown.CanBeShown.BindTo(useSeasonalBackgrounds);
useSeasonalBackgrounds.BindValueChanged(
_ => backgroundLoader.RefreshCategories(true)
);
backgroundLoader.AvailableCategories.BindValueChanged(categories => categoryDropdown.Items = categories.NewValue, true);
Children = new Drawable[]
{
new SettingsCheckbox
{
LabelText = UserInterfaceStrings.ShowMenuTips,
Current = config.GetBindable<bool>(OsuSetting.MenuTips)
Current = config.GetBindable<bool>(OsuSetting.MenuTips),
},
new SettingsCheckbox
{
@@ -56,11 +91,15 @@ namespace osu.Game.Overlays.Settings.Sections.UserInterface
LabelText = UserInterfaceStrings.BackgroundSource,
Current = config.GetBindable<BackgroundSource>(OsuSetting.MenuBackgroundSource),
},
new SettingsEnumDropdown<SeasonalBackgroundMode>
backgroundToggle,
categoryDropdown,
refreshButton,
new SettingsColour
{
LabelText = UserInterfaceStrings.SeasonalBackgrounds,
Current = config.GetBindable<SeasonalBackgroundMode>(OsuSetting.SeasonalBackgroundMode),
}
LabelText = UserInterfaceStrings.LogoColour,
Current = config.GetBindable<Colour4>(OsuSetting.MenuCookieColor),
ClassicDefault = Colour4.FromHex(@"ff66ba"),
},
};
}

View File

@@ -19,6 +19,12 @@ namespace osu.Game.Overlays.Settings.Sections.UserInterface
{
Children = new Drawable[]
{
new SettingsCheckbox
{
LabelText = UserInterfaceStrings.ForceLegacySongSelect,
Current = config.GetBindable<bool>(OsuSetting.ForceLegacySongSelect),
ClassicDefault = false
},
new SettingsCheckbox
{
LabelText = UserInterfaceStrings.ShowConvertedBeatmaps,

View File

@@ -25,6 +25,7 @@ using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
using osu.Game.Input.Bindings;
using osu.Game.Screens.Play.HUD;
using osuTK;
using osuTK.Graphics;
@@ -44,7 +45,7 @@ namespace osu.Game.Overlays.Volume
private readonly Color4 meterColour;
private readonly string name;
private OsuSpriteText text;
private ArgonCounterTextComponent text { get; set; }
private BufferedContainer maxGlow;
private Container selectedGlowContainer;
@@ -199,12 +200,7 @@ namespace osu.Game.Overlays.Volume
Radius = 10,
}
},
maxGlow = (text = new OsuSpriteText
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Font = OsuFont.Numeric.With(size: 0.16f * CircleSize)
}).WithEffect(new GlowEffect
maxGlow = (text = new ArgonCounterTextComponent(Anchor.Centre)).WithEffect(new GlowEffect
{
Colour = Color4.Transparent,
PadExtent = true,
@@ -239,6 +235,8 @@ namespace osu.Game.Overlays.Volume
Bindable.BindValueChanged(volume => { this.TransformTo(nameof(DisplayVolume), volume.NewValue, 400, Easing.OutQuint); }, true);
text.Scale = new Vector2(0.9f);
text.WireframeOpacity.BindTo(new BindableFloat(0.5f));
bgProgress.Progress = 0.75f;
}
@@ -258,9 +256,11 @@ namespace osu.Game.Overlays.Volume
displayVolumeInt = intValue;
text.WireframeTemplate = new string('#', intValue.ToString().Length);
if (displayVolume >= 0.995f)
{
text.Text = "MAX";
text.Text = "100";
maxGlow.EffectColour = meterColour.Opacity(2f);
}
else

View File

@@ -0,0 +1,22 @@
// 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.
namespace osu.Game.Rulesets.Mods
{
/// <summary>
/// Represents a mod which can override a fail and quit the game instead.
/// </summary>
public interface IApplicableFailExit : IApplicableMod
{
/// <summary>
/// Whether we should allow failing at the current point in time.
/// </summary>
/// <returns>Whether the fail should be allowed to proceed. Return false to block.</returns>
bool PerformFail();
/// <summary>
/// Whether we want to exit the game on fail. Only used if <see cref="PerformFail"/> returns true.
/// </summary>
bool ExitOnFail { get; }
}
}

View File

@@ -110,7 +110,7 @@ namespace osu.Game.Rulesets.Mods
public virtual bool RequiresConfiguration => false;
[JsonIgnore]
public virtual bool Ranked => false;
public virtual bool Ranked => true;
/// <summary>
/// The mods this mod cannot be enabled with.
@@ -127,7 +127,9 @@ namespace osu.Game.Rulesets.Mods
/// The settings are returned in ascending key order as per <see cref="SettingsMap"/>.
/// The ordering is intentionally enforced manually, as ordering of <see cref="Dictionary{TKey,TValue}.Values"/> is unspecified.
/// </remarks>
internal IEnumerable<IBindable> SettingsBindables => SettingsMap.OrderBy(pair => pair.Key).Select(pair => pair.Value);
internal IEnumerable<IBindable> SettingsBindables => SettingsMap.OrderBy(pair => pair.Key)
.Select(pair => pair.Value)
.Where(x => !x.GetType().GetCustomAttributes(typeof(JsonIgnoreAttribute)).Any());
/// <summary>
/// Provides mapping of names to <see cref="IBindable"/>s of all settings within this mod.

View File

@@ -23,6 +23,8 @@ namespace osu.Game.Rulesets.Mods
public sealed override bool UserPlayable => false;
public sealed override bool ValidForMultiplayer => false;
public sealed override bool ValidForMultiplayerAsFreeMod => false;
public override bool Ranked => false;
public override Type[] IncompatibleMods => new[] { typeof(ModCinema), typeof(ModRelax), typeof(ModAdaptiveSpeed), typeof(ModTouchDevice) };

View File

@@ -31,6 +31,7 @@ namespace osu.Game.Rulesets.Mods
public override LocalisableString Description => "Watch the video without visual distractions.";
public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(ModAutoplay), typeof(ModNoFail), typeof(ModFailCondition) }).ToArray();
public override bool Ranked => false;
public void ApplyToHUD(HUDOverlay overlay)
{

View File

@@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Mods
/// - Hit windows differ (https://github.com/ppy/osu/issues/11311).
/// - Sliders always gives combo for slider end, even on miss (https://github.com/ppy/osu/issues/11769).
/// </summary>
public sealed override bool Ranked => false;
public sealed override bool Ranked => true;
public sealed override bool ValidForFreestyleAsRequiredMod => false;
}

View File

@@ -18,13 +18,18 @@ namespace osu.Game.Rulesets.Mods
public override IconUsage? Icon => OsuIcon.ModDaycore;
public override ModType Type => ModType.DifficultyReduction;
public override LocalisableString Description => "Whoaaaaa...";
public override bool Ranked => UsesDefaultConfiguration;
public override bool Ranked => true;
[SettingSource("Speed decrease", "The actual decrease to apply", SettingControlType = typeof(MultiplierSettingsSlider))]
public override BindableNumber<double> SpeedChange { get; } = new BindableDouble(0.75)
{
#if !DEBUG
MinValue = 0.5,
MaxValue = 0.99,
#else
MinValue = 0.1,
MaxValue = 0.99,
#endif
Precision = 0.01,
};

View File

@@ -36,14 +36,17 @@ namespace osu.Game.Rulesets.Mods
protected const int FIRST_SETTING_ORDER = 1;
protected const int LAST_SETTING_ORDER = 2;
[SettingSource("HP Drain", "Override a beatmap's set HP.", FIRST_SETTING_ORDER, SettingControlType = typeof(DifficultyAdjustSettingsControl))]
public DifficultyBindable DrainRate { get; } = new DifficultyBindable
{
Precision = 0.1f,
MinValue = 0,
MaxValue = 10,
#if !DEBUG
ExtendedMaxValue = 11,
#else
ExtendedMaxValue = 13,
#endif
ReadCurrentFromDifficulty = diff => diff.DrainRate,
};
@@ -53,7 +56,12 @@ namespace osu.Game.Rulesets.Mods
Precision = 0.1f,
MinValue = 0,
MaxValue = 10,
#if !DEBUG
ExtendedMaxValue = 11,
#else
ExtendedMinValue = -50,
ExtendedMaxValue = 20,
#endif
ReadCurrentFromDifficulty = diff => diff.OverallDifficulty,
};

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