1
0
mirror of https://github.com/ppy/osu.git synced 2025-02-15 04:12:57 +08:00

Merge branch 'master' into adjustable-map-settings

This commit is contained in:
Dean Herbert 2019-12-25 13:16:47 +09:00 committed by GitHub
commit e019ea408a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
52 changed files with 594 additions and 174 deletions

View File

@ -1,6 +1,6 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="CatchRuleset (Tests)" type="DotNetProject" factoryName=".NET Project" folderName="Ruleset">
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Game.Rulesets.Catch.Tests/bin/Debug/netcoreapp3.0/osu.Game.Rulesets.Catch.Tests.dll" />
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Game.Rulesets.Catch.Tests/bin/Debug/netcoreapp3.1/osu.Game.Rulesets.Catch.Tests.dll" />
<option name="PROGRAM_PARAMETERS" value="" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/osu.Game.Rulesets.Catch.Tests" />
<option name="PASS_PARENT_ENVS" value="1" />
@ -12,7 +12,7 @@
<option name="PROJECT_ARGUMENTS_TRACKING" value="1" />
<option name="PROJECT_WORKING_DIRECTORY_TRACKING" value="1" />
<option name="PROJECT_KIND" value="DotNetCore" />
<option name="PROJECT_TFM" value=".NETCoreApp,Version=v3.0" />
<option name="PROJECT_TFM" value=".NETCoreApp,Version=v3.1" />
<browser url="http://localhost:5000" />
<method v="2">
<option name="Build" enabled="true" />

View File

@ -1,6 +1,6 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="ManiaRuleset (Tests)" type="DotNetProject" factoryName=".NET Project" folderName="Ruleset">
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Game.Rulesets.Mania.Tests/bin/Debug/netcoreapp3.0/osu.Game.Rulesets.Mania.Tests.dll" />
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Game.Rulesets.Mania.Tests/bin/Debug/netcoreapp3.1/osu.Game.Rulesets.Mania.Tests.dll" />
<option name="PROGRAM_PARAMETERS" value="" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/osu.Game.Rulesets.Mania.Tests" />
<option name="PASS_PARENT_ENVS" value="1" />
@ -12,7 +12,7 @@
<option name="PROJECT_ARGUMENTS_TRACKING" value="1" />
<option name="PROJECT_WORKING_DIRECTORY_TRACKING" value="1" />
<option name="PROJECT_KIND" value="DotNetCore" />
<option name="PROJECT_TFM" value=".NETCoreApp,Version=v3.0" />
<option name="PROJECT_TFM" value=".NETCoreApp,Version=v3.1" />
<browser url="http://localhost:5000" />
<method v="2">
<option name="Build" enabled="true" />

View File

@ -1,6 +1,6 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="OsuRuleset (Tests)" type="DotNetProject" factoryName=".NET Project" folderName="Ruleset">
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Game.Rulesets.Osu.Tests/bin/Debug/netcoreapp3.0/osu.Game.Rulesets.Osu.Tests.dll" />
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Game.Rulesets.Osu.Tests/bin/Debug/netcoreapp3.1/osu.Game.Rulesets.Osu.Tests.dll" />
<option name="PROGRAM_PARAMETERS" value="" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/osu.Game.Rulesets.Osu.Tests" />
<option name="PASS_PARENT_ENVS" value="1" />
@ -12,7 +12,7 @@
<option name="PROJECT_ARGUMENTS_TRACKING" value="1" />
<option name="PROJECT_WORKING_DIRECTORY_TRACKING" value="1" />
<option name="PROJECT_KIND" value="DotNetCore" />
<option name="PROJECT_TFM" value=".NETCoreApp,Version=v3.0" />
<option name="PROJECT_TFM" value=".NETCoreApp,Version=v3.1" />
<browser url="http://localhost:5000" />
<method v="2">
<option name="Build" enabled="true" />

View File

@ -1,6 +1,6 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="TaikoRuleset (Tests)" type="DotNetProject" factoryName=".NET Project" folderName="Ruleset">
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Game.Rulesets.Taiko.Tests/bin/Debug/netcoreapp3.0/osu.Game.Rulesets.Taiko.Tests.dll" />
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Game.Rulesets.Taiko.Tests/bin/Debug/netcoreapp3.1/osu.Game.Rulesets.Taiko.Tests.dll" />
<option name="PROGRAM_PARAMETERS" value="" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/osu.Game.Rulesets.Taiko.Tests" />
<option name="PASS_PARENT_ENVS" value="1" />
@ -12,7 +12,7 @@
<option name="PROJECT_ARGUMENTS_TRACKING" value="1" />
<option name="PROJECT_WORKING_DIRECTORY_TRACKING" value="1" />
<option name="PROJECT_KIND" value="DotNetCore" />
<option name="PROJECT_TFM" value=".NETCoreApp,Version=v3.0" />
<option name="PROJECT_TFM" value=".NETCoreApp,Version=v3.1" />
<browser url="http://localhost:5000" />
<method v="2">
<option name="Build" enabled="true" />

View File

@ -1,6 +1,6 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Tournament" type="DotNetProject" factoryName=".NET Project" folderName="Tournament">
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Desktop/bin/Debug/netcoreapp3.0/osu!.dll" />
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Desktop/bin/Debug/netcoreapp3.1/osu!.dll" />
<option name="PROGRAM_PARAMETERS" value="--tournament" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/osu.Desktop" />
<option name="PASS_PARENT_ENVS" value="1" />
@ -12,7 +12,7 @@
<option name="PROJECT_ARGUMENTS_TRACKING" value="1" />
<option name="PROJECT_WORKING_DIRECTORY_TRACKING" value="1" />
<option name="PROJECT_KIND" value="DotNetCore" />
<option name="PROJECT_TFM" value=".NETCoreApp,Version=v3.0" />
<option name="PROJECT_TFM" value=".NETCoreApp,Version=v3.1" />
<method v="2">
<option name="Build" enabled="true" />
</method>

View File

@ -1,6 +1,6 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Tournament (Tests)" type="DotNetProject" factoryName=".NET Project" folderName="Tournament">
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Game.Tournament.Tests/bin/Debug/netcoreapp2.1/osu.Game.Tournament.Tests.dll" />
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Game.Tournament.Tests/bin/Debug/netcoreapp3.1/osu.Game.Tournament.Tests.dll" />
<option name="PROGRAM_PARAMETERS" value="" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/osu.Game.Tournament.Tests" />
<option name="PASS_PARENT_ENVS" value="1" />
@ -12,7 +12,7 @@
<option name="PROJECT_ARGUMENTS_TRACKING" value="1" />
<option name="PROJECT_WORKING_DIRECTORY_TRACKING" value="1" />
<option name="PROJECT_KIND" value="DotNetCore" />
<option name="PROJECT_TFM" value=".NETCoreApp,Version=v2.1" />
<option name="PROJECT_TFM" value=".NETCoreApp,Version=v3.1" />
<browser url="http://localhost:5000" />
<method v="2">
<option name="Build" enabled="true" />

View File

@ -1,6 +1,6 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="osu!" type="DotNetProject" factoryName=".NET Project" folderName="osu!">
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Desktop/bin/Debug/netcoreapp3.0/osu!.dll" />
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Desktop/bin/Debug/netcoreapp3.1/osu!.dll" />
<option name="PROGRAM_PARAMETERS" value="" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/osu.Desktop" />
<option name="PASS_PARENT_ENVS" value="1" />
@ -12,7 +12,7 @@
<option name="PROJECT_ARGUMENTS_TRACKING" value="1" />
<option name="PROJECT_WORKING_DIRECTORY_TRACKING" value="1" />
<option name="PROJECT_KIND" value="DotNetCore" />
<option name="PROJECT_TFM" value=".NETCoreApp,Version=v3.0" />
<option name="PROJECT_TFM" value=".NETCoreApp,Version=v3.1" />
<method v="2">
<option name="Build" enabled="true" />
</method>

View File

@ -1,6 +1,6 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="osu! (Tests)" type="DotNetProject" factoryName=".NET Project" folderName="osu!">
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Game.Tests/bin/Debug/netcoreapp3.0/osu.Game.Tests.dll" />
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Game.Tests/bin/Debug/netcoreapp3.1/osu.Game.Tests.dll" />
<option name="PROGRAM_PARAMETERS" value="" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/osu.Game.Tests" />
<option name="PASS_PARENT_ENVS" value="1" />
@ -12,7 +12,7 @@
<option name="PROJECT_ARGUMENTS_TRACKING" value="1" />
<option name="PROJECT_WORKING_DIRECTORY_TRACKING" value="1" />
<option name="PROJECT_KIND" value="DotNetCore" />
<option name="PROJECT_TFM" value=".NETCoreApp,Version=v3.0" />
<option name="PROJECT_TFM" value=".NETCoreApp,Version=v3.1" />
<method v="2">
<option name="Build" enabled="true" />
</method>

32
.vscode/launch.json vendored
View File

@ -6,13 +6,13 @@
"request": "launch",
"program": "dotnet",
"args": [
"${workspaceRoot}/osu.Desktop/bin/Debug/netcoreapp3.0/osu!.dll"
"${workspaceRoot}/osu.Desktop/bin/Debug/netcoreapp3.1/osu!.dll"
],
"cwd": "${workspaceRoot}",
"preLaunchTask": "Build osu! (Debug)",
"linux": {
"env": {
"LD_LIBRARY_PATH": "${workspaceRoot}/osu.Desktop/bin/Debug/netcoreapp3.0:${env:LD_LIBRARY_PATH}"
"LD_LIBRARY_PATH": "${workspaceRoot}/osu.Desktop/bin/Debug/netcoreapp3.1:${env:LD_LIBRARY_PATH}"
}
},
"console": "internalConsole"
@ -23,13 +23,13 @@
"request": "launch",
"program": "dotnet",
"args": [
"${workspaceRoot}/osu.Desktop/bin/Release/netcoreapp3.0/osu!.dll"
"${workspaceRoot}/osu.Desktop/bin/Release/netcoreapp3.1/osu!.dll"
],
"cwd": "${workspaceRoot}",
"preLaunchTask": "Build osu! (Release)",
"linux": {
"env": {
"LD_LIBRARY_PATH": "${workspaceRoot}/osu.Desktop/bin/Release/netcoreapp3.0:${env:LD_LIBRARY_PATH}"
"LD_LIBRARY_PATH": "${workspaceRoot}/osu.Desktop/bin/Release/netcoreapp3.1:${env:LD_LIBRARY_PATH}"
}
},
"console": "internalConsole"
@ -40,13 +40,13 @@
"request": "launch",
"program": "dotnet",
"args": [
"${workspaceRoot}/osu.Game.Tests/bin/Debug/netcoreapp3.0/osu.Game.Tests.dll"
"${workspaceRoot}/osu.Game.Tests/bin/Debug/netcoreapp3.1/osu.Game.Tests.dll"
],
"cwd": "${workspaceRoot}",
"preLaunchTask": "Build tests (Debug)",
"linux": {
"env": {
"LD_LIBRARY_PATH": "${workspaceRoot}/osu.Game.Tests/bin/Debug/netcoreapp3.0:${env:LD_LIBRARY_PATH}"
"LD_LIBRARY_PATH": "${workspaceRoot}/osu.Game.Tests/bin/Debug/netcoreapp3.1:${env:LD_LIBRARY_PATH}"
}
},
"console": "internalConsole"
@ -56,13 +56,13 @@
"request": "launch",
"program": "dotnet",
"args": [
"${workspaceRoot}/osu.Game.Tests/bin/Release/netcoreapp3.0/osu.Game.Tests.dll"
"${workspaceRoot}/osu.Game.Tests/bin/Release/netcoreapp3.1/osu.Game.Tests.dll"
],
"cwd": "${workspaceRoot}",
"preLaunchTask": "Build tests (Release)",
"linux": {
"env": {
"LD_LIBRARY_PATH": "${workspaceRoot}/osu.Game.Tests/bin/Release/netcoreapp3.0:${env:LD_LIBRARY_PATH}"
"LD_LIBRARY_PATH": "${workspaceRoot}/osu.Game.Tests/bin/Release/netcoreapp3.1:${env:LD_LIBRARY_PATH}"
}
},
"console": "internalConsole"
@ -73,14 +73,14 @@
"request": "launch",
"program": "dotnet",
"args": [
"${workspaceRoot}/osu.Desktop/bin/Debug/netcoreapp3.0/osu!.dll",
"${workspaceRoot}/osu.Desktop/bin/Debug/netcoreapp3.1/osu!.dll",
"--tournament"
],
"cwd": "${workspaceRoot}",
"preLaunchTask": "Build osu! (Debug)",
"linux": {
"env": {
"LD_LIBRARY_PATH": "${workspaceRoot}/osu.Desktop/bin/Debug/netcoreapp3.0:${env:LD_LIBRARY_PATH}"
"LD_LIBRARY_PATH": "${workspaceRoot}/osu.Desktop/bin/Debug/netcoreapp3.1:${env:LD_LIBRARY_PATH}"
}
},
"console": "internalConsole"
@ -91,14 +91,14 @@
"request": "launch",
"program": "dotnet",
"args": [
"${workspaceRoot}/osu.Desktop/bin/Release/netcoreapp3.0/osu!.dll",
"${workspaceRoot}/osu.Desktop/bin/Release/netcoreapp3.1/osu!.dll",
"--tournament"
],
"cwd": "${workspaceRoot}",
"preLaunchTask": "Build osu! (Release)",
"linux": {
"env": {
"LD_LIBRARY_PATH": "${workspaceRoot}/osu.Desktop/bin/Release/netcoreapp3.0:${env:LD_LIBRARY_PATH}"
"LD_LIBRARY_PATH": "${workspaceRoot}/osu.Desktop/bin/Release/netcoreapp3.1:${env:LD_LIBRARY_PATH}"
}
},
"console": "internalConsole"
@ -109,14 +109,14 @@
"request": "launch",
"program": "dotnet",
"args": [
"${workspaceRoot}/osu.Game.Tournament.Tests/bin/Debug/netcoreapp3.0/osu.Game.Tournament.Tests.dll",
"${workspaceRoot}/osu.Game.Tournament.Tests/bin/Debug/netcoreapp3.1/osu.Game.Tournament.Tests.dll",
"--tournament"
],
"cwd": "${workspaceRoot}",
"preLaunchTask": "Build tournament tests (Debug)",
"linux": {
"env": {
"LD_LIBRARY_PATH": "${workspaceRoot}/osu.Game.Tournament.Tests/bin/Debug/netcoreapp3.0:${env:LD_LIBRARY_PATH}"
"LD_LIBRARY_PATH": "${workspaceRoot}/osu.Game.Tournament.Tests/bin/Debug/netcoreapp3.1:${env:LD_LIBRARY_PATH}"
}
},
"console": "internalConsole"
@ -127,14 +127,14 @@
"request": "launch",
"program": "dotnet",
"args": [
"${workspaceRoot}/osu.Game.Tournament.Tests/bin/Debug/netcoreapp3.0/osu.Game.Tournament.Tests.dll",
"${workspaceRoot}/osu.Game.Tournament.Tests/bin/Debug/netcoreapp3.1/osu.Game.Tournament.Tests.dll",
"--tournament"
],
"cwd": "${workspaceRoot}",
"preLaunchTask": "Build tournament tests (Release)",
"linux": {
"env": {
"LD_LIBRARY_PATH": "${workspaceRoot}/osu.Game.Tournament.Tests/bin/Debug/netcoreapp3.0:${env:LD_LIBRARY_PATH}"
"LD_LIBRARY_PATH": "${workspaceRoot}/osu.Game.Tournament.Tests/bin/Debug/netcoreapp3.1:${env:LD_LIBRARY_PATH}"
}
},
"console": "internalConsole"

2
.vscode/tasks.json vendored
View File

@ -95,7 +95,7 @@
"problemMatcher": "$msCompile"
},
{
"label": "Restore (netcoreapp3.0)",
"label": "Restore (netcoreapp3.1)",
"type": "shell",
"command": "dotnet",
"args": [

View File

@ -21,9 +21,9 @@ Detailed changelogs are published on the [official osu! site](https://osu.ppy.sh
## Requirements
- A desktop platform with the [.NET Core SDK 3.0](https://www.microsoft.com/net/learn/get-started) or higher installed.
- A desktop platform with the [.NET Core 3.1 SDK](https://dotnet.microsoft.com/download) or higher installed.
- When running on Linux, please have a system-wide FFmpeg installation available to support video decoding.
- When running on Windows 7 or 8.1, **[additional prerequisites](https://docs.microsoft.com/en-us/dotnet/core/install/dependencies?tabs=netcore30&pivots=os-windows)** may be required to correctly run .NET Core applications if your operating system is not up-to-date with the latest service packs.
- When running on Windows 7 or 8.1, **[additional prerequisites](https://docs.microsoft.com/en-us/dotnet/core/install/dependencies?tabs=netcore31&pivots=os-windows)** may be required to correctly run .NET Core applications if your operating system is not up-to-date with the latest service packs.
- When developing with mobile, [Xamarin](https://docs.microsoft.com/en-us/xamarin/) is required, which is shipped together with Visual Studio or [Visual Studio for Mac](https://visualstudio.microsoft.com/vs/mac/).
- When working with the codebase, we recommend using an IDE with intelligent code completion and syntax highlighting, such as [Visual Studio 2019+](https://visualstudio.microsoft.com/vs/), [JetBrains Rider](https://www.jetbrains.com/rider/) or [Visual Studio Code](https://code.visualstudio.com/).
@ -35,7 +35,7 @@ If you are not interested in developing the game, you can still consume our [bin
**Latest build:**
| [Windows (x64)](https://github.com/ppy/osu/releases/latest/download/install.exe) | [macOS 10.12+](https://github.com/ppy/osu/releases/latest/download/osu.app.zip) | [iOS(iOS 10+)](https://testflight.apple.com/join/2tLcjWlF) | [Android (5+)](https://github.com/ppy/osu/releases/latest/download/sh.ppy.osulazer.apk)
| [Windows (x64)](https://github.com/ppy/osu/releases/latest/download/install.exe) | [macOS 10.12+](https://github.com/ppy/osu/releases/latest/download/osu.app.zip) | [iOS(iOS 10+)](https://osu.ppy.sh/home/testflight) | [Android (5+)](https://github.com/ppy/osu/releases/latest/download/sh.ppy.osulazer.apk)
| ------------- | ------------- | ------------- | ------------- |
- **Linux** users are recommended to self-compile until we have official deployment in place.

View File

@ -54,6 +54,6 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="ppy.osu.Game.Resources" Version="2019.1215.0" />
<PackageReference Include="ppy.osu.Framework.Android" Version="2019.1215.0" />
<PackageReference Include="ppy.osu.Framework.Android" Version="2019.1224.0" />
</ItemGroup>
</Project>

View File

@ -1,6 +1,8 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Text;
using DiscordRPC;
using DiscordRPC.Message;
using osu.Framework.Allocation;
@ -43,8 +45,11 @@ namespace osu.Desktop
};
client.OnReady += onReady;
client.OnError += (_, e) => Logger.Log($"An error occurred with Discord RPC Client: {e.Code} {e.Message}", LoggingTarget.Network, LogLevel.Error);
client.OnConnectionFailed += (_, e) => Logger.Log($"An connection occurred with Discord RPC Client: {e.Type}", LoggingTarget.Network, LogLevel.Error);
// safety measure for now, until we performance test / improve backoff for failed connections.
client.OnConnectionFailed += (_, __) => client.Deinitialize();
client.OnError += (_, e) => Logger.Log($"An error occurred with Discord RPC Client: {e.Code} {e.Message}", LoggingTarget.Network);
(user = provider.LocalUser.GetBoundCopy()).BindValueChanged(u =>
{
@ -78,8 +83,8 @@ namespace osu.Desktop
if (status.Value is UserStatusOnline && activity.Value != null)
{
presence.State = activity.Value.Status;
presence.Details = getDetails(activity.Value);
presence.State = truncate(activity.Value.Status);
presence.Details = truncate(getDetails(activity.Value));
}
else
{
@ -97,6 +102,27 @@ namespace osu.Desktop
client.SetPresence(presence);
}
private static readonly int ellipsis_length = Encoding.UTF8.GetByteCount(new[] { '…' });
private string truncate(string str)
{
if (Encoding.UTF8.GetByteCount(str) <= 128)
return str;
ReadOnlyMemory<char> strMem = str.AsMemory();
do
{
strMem = strMem[..^1];
} while (Encoding.UTF8.GetByteCount(strMem.Span) + ellipsis_length > 128);
return string.Create(strMem.Length + 1, strMem, (span, mem) =>
{
mem.Span.CopyTo(span);
span[^1] = '…';
});
}
private string getDetails(UserActivity activity)
{
switch (activity)

View File

@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup Label="Project">
<TargetFramework>netcoreapp3.0</TargetFramework>
<TargetFramework>netcoreapp3.1</TargetFramework>
<OutputType>WinExe</OutputType>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<Description>click the circles. to the beat.</Description>

View File

@ -5,7 +5,7 @@ using UIKit;
namespace osu.Game.Rulesets.Catch.Tests.iOS
{
public class Application
public static class Application
{
public static void Main(string[] args)
{

View File

@ -7,7 +7,7 @@
"request": "launch",
"program": "dotnet",
"args": [
"${workspaceRoot}/bin/Debug/netcoreapp3.0/osu.Game.Rulesets.Catch.Tests.dll"
"${workspaceRoot}/bin/Debug/netcoreapp3.1/osu.Game.Rulesets.Catch.Tests.dll"
],
"cwd": "${workspaceRoot}",
"preLaunchTask": "Build (Debug)",
@ -20,7 +20,7 @@
"request": "launch",
"program": "dotnet",
"args": [
"${workspaceRoot}/bin/Release/netcoreapp3.0/osu.Game.Rulesets.Catch.Tests.dll"
"${workspaceRoot}/bin/Release/netcoreapp3.1/osu.Game.Rulesets.Catch.Tests.dll"
],
"cwd": "${workspaceRoot}",
"preLaunchTask": "Build (Release)",

View File

@ -9,7 +9,7 @@
</ItemGroup>
<PropertyGroup Label="Project">
<OutputType>WinExe</OutputType>
<TargetFramework>netcoreapp3.0</TargetFramework>
<TargetFramework>netcoreapp3.1</TargetFramework>
</PropertyGroup>
<ItemGroup Label="Project References">
<ProjectReference Include="..\osu.Game.Rulesets.Catch\osu.Game.Rulesets.Catch.csproj" />

View File

@ -5,7 +5,7 @@ using UIKit;
namespace osu.Game.Rulesets.Mania.Tests.iOS
{
public class Application
public static class Application
{
public static void Main(string[] args)
{

View File

@ -7,7 +7,7 @@
"request": "launch",
"program": "dotnet",
"args": [
"${workspaceRoot}/bin/Debug/netcoreapp3.0/osu.Game.Rulesets.Mania.Tests.dll"
"${workspaceRoot}/bin/Debug/netcoreapp3.1/osu.Game.Rulesets.Mania.Tests.dll"
],
"cwd": "${workspaceRoot}",
"preLaunchTask": "Build (Debug)",
@ -20,7 +20,7 @@
"request": "launch",
"program": "dotnet",
"args": [
"${workspaceRoot}/bin/Release/netcoreapp3.0/osu.Game.Rulesets.Mania.Tests.dll"
"${workspaceRoot}/bin/Release/netcoreapp3.1/osu.Game.Rulesets.Mania.Tests.dll"
],
"cwd": "${workspaceRoot}",
"preLaunchTask": "Build (Release)",

View File

@ -9,7 +9,7 @@
</ItemGroup>
<PropertyGroup Label="Project">
<OutputType>WinExe</OutputType>
<TargetFramework>netcoreapp3.0</TargetFramework>
<TargetFramework>netcoreapp3.1</TargetFramework>
</PropertyGroup>
<ItemGroup Label="Project References">
<ProjectReference Include="..\osu.Game.Rulesets.Mania\osu.Game.Rulesets.Mania.csproj" />

View File

@ -5,7 +5,7 @@ using UIKit;
namespace osu.Game.Rulesets.Osu.Tests.iOS
{
public class Application
public static class Application
{
public static void Main(string[] args)
{

View File

@ -7,7 +7,7 @@
"request": "launch",
"program": "dotnet",
"args": [
"${workspaceRoot}/bin/Debug/netcoreapp3.0/osu.Game.Rulesets.Osu.Tests.dll"
"${workspaceRoot}/bin/Debug/netcoreapp3.1/osu.Game.Rulesets.Osu.Tests.dll"
],
"cwd": "${workspaceRoot}",
"preLaunchTask": "Build (Debug)",
@ -20,7 +20,7 @@
"request": "launch",
"program": "dotnet",
"args": [
"${workspaceRoot}/bin/Release/netcoreapp3.0/osu.Game.Rulesets.Osu.Tests.dll"
"${workspaceRoot}/bin/Release/netcoreapp3.1/osu.Game.Rulesets.Osu.Tests.dll"
],
"cwd": "${workspaceRoot}",
"preLaunchTask": "Build (Release)",

View File

@ -9,7 +9,7 @@
</ItemGroup>
<PropertyGroup Label="Project">
<OutputType>WinExe</OutputType>
<TargetFramework>netcoreapp3.0</TargetFramework>
<TargetFramework>netcoreapp3.1</TargetFramework>
</PropertyGroup>
<ItemGroup Label="Project References">
<ProjectReference Include="..\osu.Game.Rulesets.Osu\osu.Game.Rulesets.Osu.csproj" />

View File

@ -5,7 +5,7 @@ using UIKit;
namespace osu.Game.Rulesets.Taiko.Tests.iOS
{
public class Application
public static class Application
{
public static void Main(string[] args)
{

View File

@ -7,7 +7,7 @@
"request": "launch",
"program": "dotnet",
"args": [
"${workspaceRoot}/bin/Debug/netcoreapp3.0/osu.Game.Rulesets.Taiko.Tests.dll"
"${workspaceRoot}/bin/Debug/netcoreapp3.1/osu.Game.Rulesets.Taiko.Tests.dll"
],
"cwd": "${workspaceRoot}",
"preLaunchTask": "Build (Debug)",
@ -20,7 +20,7 @@
"request": "launch",
"program": "dotnet",
"args": [
"${workspaceRoot}/bin/Release/netcoreapp3.0/osu.Game.Rulesets.Taiko.Tests.dll"
"${workspaceRoot}/bin/Release/netcoreapp3.1/osu.Game.Rulesets.Taiko.Tests.dll"
],
"cwd": "${workspaceRoot}",
"preLaunchTask": "Build (Release)",

View File

@ -9,7 +9,7 @@
</ItemGroup>
<PropertyGroup Label="Project">
<OutputType>WinExe</OutputType>
<TargetFramework>netcoreapp3.0</TargetFramework>
<TargetFramework>netcoreapp3.1</TargetFramework>
</PropertyGroup>
<ItemGroup Label="Project References">
<ProjectReference Include="..\osu.Game.Rulesets.Taiko\osu.Game.Rulesets.Taiko.csproj" />

View File

@ -5,7 +5,7 @@ using UIKit;
namespace osu.Game.Tests.iOS
{
public class Application
public static class Application
{
public static void Main(string[] args)
{

View File

@ -71,7 +71,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
Assert.AreEqual(new Vector2(320, 240), sprite.InitialPosition);
Assert.IsTrue(sprite.IsDrawable);
Assert.AreEqual(Anchor.Centre, sprite.Origin);
Assert.AreEqual("SB/black.jpg", sprite.Path);
Assert.AreEqual("SB/lyric/ja-21.png", sprite.Path);
var animation = background.Elements.OfType<StoryboardAnimation>().First();
Assert.NotNull(animation);

View File

@ -0,0 +1,75 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using JetBrains.Annotations;
using NUnit.Framework;
using osu.Framework.Testing;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.UI;
using osu.Game.Tests.Visual;
namespace osu.Game.Tests.Gameplay
{
[HeadlessTest]
public class TestSceneHitObjectContainer : OsuTestScene
{
private HitObjectContainer container;
[SetUp]
public void Setup() => Schedule(() =>
{
Child = container = new HitObjectContainer();
});
[Test]
public void TestLateHitObjectIsAddedEarlierInList()
{
DrawableHitObject hitObject = null;
AddStep("setup", () => container.Add(new TestDrawableHitObject(new HitObject { StartTime = 500 })));
AddStep("add late hitobject", () => container.Add(hitObject = new TestDrawableHitObject(new HitObject { StartTime = 1000 })));
AddAssert("hitobject index is 0", () => container.IndexOf(hitObject) == 0);
}
[Test]
public void TestEarlyHitObjectIsAddedLaterInList()
{
DrawableHitObject hitObject = null;
AddStep("setup", () => container.Add(new TestDrawableHitObject(new HitObject { StartTime = 500 })));
AddStep("add early hitobject", () => container.Add(hitObject = new TestDrawableHitObject(new HitObject())));
AddAssert("hitobject index is 0", () => container.IndexOf(hitObject) == 1);
}
[Test]
public void TestHitObjectsResortedAfterStartTimeChange()
{
DrawableHitObject firstObject = null;
DrawableHitObject secondObject = null;
AddStep("setup", () =>
{
container.Add(firstObject = new TestDrawableHitObject(new HitObject()));
container.Add(secondObject = new TestDrawableHitObject(new HitObject { StartTime = 1000 }));
});
AddStep("move first object after second", () => firstObject.HitObject.StartTime = 2000);
AddAssert("first object index is 1", () => container.IndexOf(firstObject) == 0);
AddAssert("second object index is 0", () => container.IndexOf(secondObject) == 1);
}
private class TestDrawableHitObject : DrawableHitObject
{
public TestDrawableHitObject([NotNull] HitObject hitObject)
: base(hitObject)
{
}
}
}
}

View File

@ -68,8 +68,11 @@ namespace osu.Game.Tests.Visual.Editor
{
private readonly Drawable marker;
private readonly IBindable<WorkingBeatmap> beatmap = new Bindable<WorkingBeatmap>();
private IAdjustableClock adjustableClock;
[Resolved]
private IBindable<WorkingBeatmap> beatmap { get; set; }
[Resolved]
private IAdjustableClock adjustableClock { get; set; }
public AudioVisualiser()
{
@ -91,13 +94,6 @@ namespace osu.Game.Tests.Visual.Editor
};
}
[BackgroundDependencyLoader]
private void load(IAdjustableClock adjustableClock, IBindable<WorkingBeatmap> beatmap)
{
this.adjustableClock = adjustableClock;
this.beatmap.BindTo(beatmap);
}
protected override void Update()
{
base.Update();

View File

@ -0,0 +1,92 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using osu.Game.Graphics.Sprites;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Screens.Select;
namespace osu.Game.Tests.Visual.UserInterface
{
public class TestSceneFooterButtonMods : OsuTestScene
{
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(FooterButtonMods)
};
private readonly TestFooterButtonMods footerButtonMods;
public TestSceneFooterButtonMods()
{
Add(footerButtonMods = new TestFooterButtonMods());
}
[Test]
public void TestIncrementMultiplier()
{
var hiddenMod = new Mod[] { new OsuModHidden() };
AddStep(@"Add Hidden", () => changeMods(hiddenMod));
AddAssert(@"Check Hidden multiplier", () => assertModsMultiplier(hiddenMod));
var hardRockMod = new Mod[] { new OsuModHardRock() };
AddStep(@"Add HardRock", () => changeMods(hardRockMod));
AddAssert(@"Check HardRock multiplier", () => assertModsMultiplier(hardRockMod));
var doubleTimeMod = new Mod[] { new OsuModDoubleTime() };
AddStep(@"Add DoubleTime", () => changeMods(doubleTimeMod));
AddAssert(@"Check DoubleTime multiplier", () => assertModsMultiplier(doubleTimeMod));
var mutlipleIncrementMods = new Mod[] { new OsuModDoubleTime(), new OsuModHidden(), new OsuModHardRock() };
AddStep(@"Add multiple Mods", () => changeMods(mutlipleIncrementMods));
AddAssert(@"Check multiple mod multiplier", () => assertModsMultiplier(mutlipleIncrementMods));
}
[Test]
public void TestDecrementMultiplier()
{
var easyMod = new Mod[] { new OsuModEasy() };
AddStep(@"Add Easy", () => changeMods(easyMod));
AddAssert(@"Check Easy multiplier", () => assertModsMultiplier(easyMod));
var noFailMod = new Mod[] { new OsuModNoFail() };
AddStep(@"Add NoFail", () => changeMods(noFailMod));
AddAssert(@"Check NoFail multiplier", () => assertModsMultiplier(noFailMod));
var multipleDecrementMods = new Mod[] { new OsuModEasy(), new OsuModNoFail() };
AddStep(@"Add Multiple Mods", () => changeMods(multipleDecrementMods));
AddAssert(@"Check multiple mod multiplier", () => assertModsMultiplier(multipleDecrementMods));
}
[Test]
public void TestClearMultiplier()
{
var multipleMods = new Mod[] { new OsuModDoubleTime(), new OsuModFlashlight() };
AddStep(@"Add mods", () => changeMods(multipleMods));
AddStep(@"Clear selected mod", () => changeMods(Array.Empty<Mod>()));
AddAssert(@"Check empty multiplier", () => assertModsMultiplier(Array.Empty<Mod>()));
}
private void changeMods(IReadOnlyList<Mod> mods)
{
footerButtonMods.Current.Value = mods;
}
private bool assertModsMultiplier(IEnumerable<Mod> mods)
{
var multiplier = mods.Aggregate(1.0, (current, mod) => current * mod.ScoreMultiplier);
var expectedValue = multiplier.Equals(1.0) ? string.Empty : $"{multiplier:N2}x";
return expectedValue == footerButtonMods.MultiplierText.Text;
}
private class TestFooterButtonMods : FooterButtonMods
{
public new OsuSpriteText MultiplierText => base.MultiplierText;
}
}
}

View File

@ -20,26 +20,24 @@ namespace osu.Game.Tests
/// </summary>
public class WaveformTestBeatmap : WorkingBeatmap
{
private readonly ZipArchiveReader reader;
private readonly Stream stream;
private readonly ITrackStore trackStore;
public WaveformTestBeatmap(AudioManager audioManager)
: base(new BeatmapInfo(), audioManager)
{
stream = TestResources.GetTestBeatmapStream();
reader = new ZipArchiveReader(stream);
trackStore = audioManager.GetTrackStore(reader);
trackStore = audioManager.GetTrackStore(getZipReader());
}
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
stream?.Dispose();
reader?.Dispose();
trackStore?.Dispose();
}
private Stream getStream() => TestResources.GetTestBeatmapStream();
private ZipArchiveReader getZipReader() => new ZipArchiveReader(getStream());
protected override IBeatmap GetBeatmap() => createTestBeatmap();
protected override Texture GetBackground() => null;
@ -50,15 +48,23 @@ namespace osu.Game.Tests
protected override Track GetTrack() => trackStore.Get(firstAudioFile);
private string firstAudioFile => reader.Filenames.First(f => f.EndsWith(".mp3"));
private Stream getBeatmapStream() => reader.GetStream(reader.Filenames.First(f => f.EndsWith(".osu")));
private string firstAudioFile
{
get
{
using (var reader = getZipReader())
return reader.Filenames.First(f => f.EndsWith(".mp3"));
}
}
private Beatmap createTestBeatmap()
{
using (var beatmapStream = getBeatmapStream())
using (var beatmapReader = new LineBufferedReader(beatmapStream))
return Decoder.GetDecoder<Beatmap>(beatmapReader).Decode(beatmapReader);
using (var reader = getZipReader())
{
using (var beatmapStream = reader.GetStream(reader.Filenames.First(f => f.EndsWith(".osu"))))
using (var beatmapReader = new LineBufferedReader(beatmapStream))
return Decoder.GetDecoder<Beatmap>(beatmapReader).Decode(beatmapReader);
}
}
}
}

View File

@ -10,7 +10,7 @@
</ItemGroup>
<PropertyGroup Label="Project">
<OutputType>WinExe</OutputType>
<TargetFramework>netcoreapp3.0</TargetFramework>
<TargetFramework>netcoreapp3.1</TargetFramework>
</PropertyGroup>
<ItemGroup Label="Project References">
<ProjectReference Include="..\osu.Game.Rulesets.Osu\osu.Game.Rulesets.Osu.csproj" />

View File

@ -11,7 +11,7 @@
</ItemGroup>
<PropertyGroup Label="Project">
<OutputType>WinExe</OutputType>
<TargetFramework>netcoreapp3.0</TargetFramework>
<TargetFramework>netcoreapp3.1</TargetFramework>
</PropertyGroup>
<ItemGroup Label="Project References">
<ProjectReference Include="..\osu.Game.Rulesets.Catch\osu.Game.Rulesets.Catch.csproj" />

View File

@ -5,7 +5,6 @@ using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using osuTK;
using osuTK.Graphics;
using osu.Framework.Extensions;
@ -42,10 +41,6 @@ namespace osu.Game.Beatmaps.Formats
{
this.storyboard = storyboard;
base.ParseStreamInto(stream, storyboard);
// OrderBy is used to guarantee that the parsing order of elements with equal start times is maintained (stably-sorted)
foreach (StoryboardLayer layer in storyboard.Layers)
layer.Elements = layer.Elements.OrderBy(h => h.StartTime).ToList();
}
protected override void ParseLine(Storyboard storyboard, Section section, string line)

View File

@ -12,10 +12,12 @@ using osu.Framework.Input.Events;
namespace osu.Game.Graphics.UserInterface
{
public class OsuTextBox : TextBox
public class OsuTextBox : BasicTextBox
{
protected override float LeftRightPadding => 10;
protected override float CaretWidth => 3;
protected override SpriteText CreatePlaceholder() => new OsuSpriteText
{
Font = OsuFont.GetFont(italics: true),
@ -41,6 +43,8 @@ namespace osu.Game.Graphics.UserInterface
BackgroundCommit = BorderColour = colour.Yellow;
}
protected override Color4 SelectionColour => new Color4(249, 90, 255, 255);
protected override void OnFocus(FocusEvent e)
{
BorderThickness = 3;

View File

@ -113,6 +113,7 @@ namespace osu.Game.Overlays.Settings
bindable = value;
bindable.ValueChanged += _ => UpdateState();
bindable.DisabledChanged += _ => UpdateState();
bindable.DefaultChanged += _ => UpdateState();
UpdateState();
}
}

View File

@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Bindables;
@ -15,6 +16,8 @@ using osu.Game.Online.API.Requests;
using osu.Game.Overlays.SearchableList;
using osu.Game.Overlays.Social;
using osu.Game.Users;
using System.Threading;
using osu.Framework.Allocation;
using osu.Framework.Threading;
namespace osu.Game.Overlays
@ -31,17 +34,20 @@ namespace osu.Game.Overlays
protected override SearchableListHeader<SocialTab> CreateHeader() => new Header();
protected override SearchableListFilterControl<SocialSortCriteria, SortDirection> CreateFilterControl() => new FilterControl();
private IEnumerable<User> users;
private User[] users = Array.Empty<User>();
public IEnumerable<User> Users
public User[] Users
{
get => users;
set
{
if (ReferenceEquals(users, value))
if (users == value)
return;
users = value?.ToList();
users = value ?? Array.Empty<User>();
if (LoadState >= LoadState.Ready)
recreatePanels();
}
}
@ -67,11 +73,10 @@ namespace osu.Game.Overlays
};
Header.Tabs.Current.ValueChanged += _ => queueUpdate();
Filter.Tabs.Current.ValueChanged += _ => onFilterUpdate();
Filter.Tabs.Current.ValueChanged += _ => queueUpdate();
Filter.DisplayStyleControl.DisplayStyle.ValueChanged += style => recreatePanels(style.NewValue);
Filter.DisplayStyleControl.Dropdown.Current.ValueChanged += _ => queueUpdate();
Filter.DisplayStyleControl.DisplayStyle.ValueChanged += _ => recreatePanels();
Filter.DisplayStyleControl.Dropdown.Current.ValueChanged += _ => recreatePanels();
currentQuery.BindTo(Filter.Search.Current);
currentQuery.ValueChanged += query =>
@ -85,6 +90,12 @@ namespace osu.Game.Overlays
};
}
[BackgroundDependencyLoader]
private void load()
{
recreatePanels();
}
private APIRequest getUsersRequest;
private readonly Bindable<string> currentQuery = new Bindable<string>();
@ -93,6 +104,8 @@ namespace osu.Game.Overlays
private void queueUpdate() => Scheduler.AddOnce(updateSearch);
private CancellationTokenSource loadCancellation;
private void updateSearch()
{
queryChangedDebounce?.Cancel();
@ -102,7 +115,6 @@ namespace osu.Game.Overlays
Users = null;
clearPanels();
loading.Hide();
getUsersRequest?.Cancel();
if (API?.IsLoggedIn != true)
@ -112,26 +124,43 @@ namespace osu.Game.Overlays
{
case SocialTab.Friends:
var friendRequest = new GetFriendsRequest(); // TODO filter arguments?
friendRequest.Success += updateUsers;
friendRequest.Success += users => Users = users.ToArray();
API.Queue(getUsersRequest = friendRequest);
break;
default:
var userRequest = new GetUsersRequest(); // TODO filter arguments!
userRequest.Success += res => updateUsers(res.Users.Select(r => r.User));
userRequest.Success += res => Users = res.Users.Select(r => r.User).ToArray();
API.Queue(getUsersRequest = userRequest);
break;
}
loading.Show();
}
private void recreatePanels(PanelDisplayStyle displayStyle)
private void recreatePanels()
{
clearPanels();
if (Users == null)
{
loading.Hide();
return;
}
IEnumerable<User> sortedUsers = Users;
switch (Filter.Tabs.Current.Value)
{
case SocialSortCriteria.Location:
sortedUsers = sortedUsers.OrderBy(u => u.Country.FullName);
break;
case SocialSortCriteria.Name:
sortedUsers = sortedUsers.OrderBy(u => u.Username);
break;
}
if (Filter.DisplayStyleControl.Dropdown.Current.Value == SortDirection.Descending)
sortedUsers = sortedUsers.Reverse();
var newPanels = new FillFlowContainer<SocialPanel>
{
@ -139,11 +168,11 @@ namespace osu.Game.Overlays
AutoSizeAxes = Axes.Y,
Spacing = new Vector2(10f),
Margin = new MarginPadding { Top = 10 },
ChildrenEnumerable = Users.Select(u =>
ChildrenEnumerable = sortedUsers.Select(u =>
{
SocialPanel panel;
switch (displayStyle)
switch (Filter.DisplayStyleControl.DisplayStyle.Value)
{
case PanelDisplayStyle.Grid:
panel = new SocialGridPanel(u)
@ -169,19 +198,28 @@ namespace osu.Game.Overlays
if (panels != null)
ScrollFlow.Remove(panels);
loading.Hide();
ScrollFlow.Add(panels = newPanels);
});
}, (loadCancellation = new CancellationTokenSource()).Token);
}
private void updateUsers(IEnumerable<User> newUsers)
private void onFilterUpdate()
{
Users = newUsers;
loading.Hide();
recreatePanels(Filter.DisplayStyleControl.DisplayStyle.Value);
if (Filter.Tabs.Current.Value == SocialSortCriteria.Rank)
{
queueUpdate();
return;
}
recreatePanels();
}
private void clearPanels()
{
loading.Show();
loadCancellation?.Cancel();
if (panels != null)
{
panels.Expire();

View File

@ -3,6 +3,7 @@
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Objects.Drawables;
@ -14,13 +15,45 @@ namespace osu.Game.Rulesets.UI
public IEnumerable<DrawableHitObject> Objects => InternalChildren.Cast<DrawableHitObject>().OrderBy(h => h.HitObject.StartTime);
public IEnumerable<DrawableHitObject> AliveObjects => AliveInternalChildren.Cast<DrawableHitObject>().OrderBy(h => h.HitObject.StartTime);
private readonly Dictionary<DrawableHitObject, (IBindable<double> bindable, double timeAtAdd)> startTimeMap = new Dictionary<DrawableHitObject, (IBindable<double>, double)>();
public HitObjectContainer()
{
RelativeSizeAxes = Axes.Both;
}
public virtual void Add(DrawableHitObject hitObject) => AddInternal(hitObject);
public virtual bool Remove(DrawableHitObject hitObject) => RemoveInternal(hitObject);
public virtual void Add(DrawableHitObject hitObject)
{
// Added first for the comparer to remain ordered during AddInternal
startTimeMap[hitObject] = (hitObject.HitObject.StartTimeBindable.GetBoundCopy(), hitObject.HitObject.StartTime);
startTimeMap[hitObject].bindable.BindValueChanged(_ => onStartTimeChanged(hitObject));
AddInternal(hitObject);
}
public virtual bool Remove(DrawableHitObject hitObject)
{
if (!RemoveInternal(hitObject))
return false;
// Removed last for the comparer to remain ordered during RemoveInternal
startTimeMap[hitObject].bindable.UnbindAll();
startTimeMap.Remove(hitObject);
return true;
}
public int IndexOf(DrawableHitObject hitObject) => IndexOfInternal(hitObject);
private void onStartTimeChanged(DrawableHitObject hitObject)
{
if (!RemoveInternal(hitObject))
return;
// Update the stored time, preserving the existing bindable
startTimeMap[hitObject] = (startTimeMap[hitObject].bindable, hitObject.HitObject.StartTime);
AddInternal(hitObject);
}
protected override int Compare(Drawable x, Drawable y)
{
@ -28,7 +61,7 @@ namespace osu.Game.Rulesets.UI
return base.Compare(x, y);
// Put earlier hitobjects towards the end of the list, so they handle input first
int i = yObj.HitObject.StartTime.CompareTo(xObj.HitObject.StartTime);
int i = startTimeMap[yObj].timeAtAdd.CompareTo(startTimeMap[xObj].timeAtAdd);
return i == 0 ? CompareReverseChildID(x, y) : i;
}

View File

@ -89,7 +89,7 @@ namespace osu.Game.Scoring
if (mods == null)
return null;
return modsJson = JsonConvert.SerializeObject(mods);
return modsJson = JsonConvert.SerializeObject(mods.Select(m => new DeserializedMod { Acronym = m.Acronym }));
}
set
{

View File

@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Linq;
using osuTK;
using osuTK.Graphics;
using osu.Framework.Allocation;
@ -132,6 +133,8 @@ namespace osu.Game.Screens.Menu
private void confirmAndExit()
{
if (exitConfirmed) return;
exitConfirmed = true;
this.Exit();
}
@ -244,10 +247,18 @@ namespace osu.Game.Screens.Menu
public override bool OnExiting(IScreen next)
{
if (!exitConfirmed && dialogOverlay != null && !(dialogOverlay.CurrentDialog is ConfirmExitDialog))
if (!exitConfirmed && dialogOverlay != null)
{
dialogOverlay.Push(new ConfirmExitDialog(confirmAndExit, () => exitConfirmOverlay.Abort()));
return true;
if (dialogOverlay.CurrentDialog is ConfirmExitDialog exitDialog)
{
exitConfirmed = true;
exitDialog.Buttons.First().Click();
}
else
{
dialogOverlay.Push(new ConfirmExitDialog(confirmAndExit, () => exitConfirmOverlay.Abort()));
return true;
}
}
buttons.State = ButtonSystemState.Exit;

View File

@ -43,6 +43,11 @@ namespace osu.Game.Screens.Play
private void load(OsuConfigManager config)
{
config.BindWith(OsuSetting.KeyOverlay, configVisibility);
}
protected override void LoadComplete()
{
base.LoadComplete();
Visible.BindValueChanged(_ => updateVisibility());
configVisibility.BindValueChanged(_ => updateVisibility(), true);

View File

@ -562,7 +562,7 @@ namespace osu.Game.Screens.Play
// GameplayClockContainer performs seeks / start / stop operations on the beatmap's track.
// as we are no longer the current screen, we cannot guarantee the track is still usable.
GameplayClockContainer.StopUsingBeatmapClock();
GameplayClockContainer?.StopUsingBeatmapClock();
fadeOut();
return base.OnExiting(next);

View File

@ -7,11 +7,14 @@ using osu.Framework.Graphics.Containers;
using osu.Game.Screens.Play.HUD;
using osu.Game.Rulesets.Mods;
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics.UserInterface;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osuTK;
using osuTK.Graphics;
using osuTK.Input;
namespace osu.Game.Screens.Select
@ -24,19 +27,35 @@ namespace osu.Game.Screens.Select
set => modDisplay.Current = value;
}
protected readonly OsuSpriteText MultiplierText;
private readonly FooterModDisplay modDisplay;
private Color4 lowMultiplierColour;
private Color4 highMultiplierColour;
public FooterButtonMods()
{
Add(new Container
Add(new FillFlowContainer
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
Direction = FillDirection.Horizontal,
Shear = -SHEAR,
Child = modDisplay = new FooterModDisplay
Children = new Drawable[]
{
DisplayUnrankedText = false,
Scale = new Vector2(0.8f)
modDisplay = new FooterModDisplay
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
DisplayUnrankedText = false,
Scale = new Vector2(0.8f)
},
MultiplierText = new OsuSpriteText
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Font = OsuFont.GetFont(weight: FontWeight.Bold),
Margin = new MarginPadding { Right = 10 }
}
},
AutoSizeAxes = Axes.Both,
Margin = new MarginPadding { Left = 70 }
@ -48,10 +67,33 @@ namespace osu.Game.Screens.Select
{
SelectedColour = colours.Yellow;
DeselectedColour = SelectedColour.Opacity(0.5f);
lowMultiplierColour = colours.Red;
highMultiplierColour = colours.Green;
Text = @"mods";
Hotkey = Key.F1;
}
protected override void LoadComplete()
{
base.LoadComplete();
Current.BindValueChanged(_ => updateMultiplierText(), true);
}
private void updateMultiplierText()
{
double multiplier = Current.Value?.Aggregate(1.0, (current, mod) => current * mod.ScoreMultiplier) ?? 1;
MultiplierText.Text = multiplier.Equals(1.0) ? string.Empty : $"{multiplier:N2}x";
if (multiplier > 1.0)
MultiplierText.FadeColour(highMultiplierColour, 200);
else if (multiplier < 1.0)
MultiplierText.FadeColour(lowMultiplierColour, 200);
else
MultiplierText.FadeColour(Color4.White, 200);
}
private class FooterModDisplay : ModDisplay
{
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => Parent?.Parent?.ReceivePositionalInputAt(screenSpacePos) ?? false;

View File

@ -1,7 +1,6 @@
// 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.Caching;
using osu.Framework.Graphics;
using System;
using System.Collections.Generic;
@ -12,27 +11,35 @@ namespace osu.Game.Storyboards
public class CommandTimeline<T> : ICommandTimeline
{
private readonly List<TypedCommand> commands = new List<TypedCommand>();
public IEnumerable<TypedCommand> Commands => commands.OrderBy(c => c.StartTime);
public bool HasCommands => commands.Count > 0;
private readonly Cached<double> startTimeBacking = new Cached<double>();
public double StartTime => startTimeBacking.IsValid ? startTimeBacking : startTimeBacking.Value = HasCommands ? commands.Min(c => c.StartTime) : double.MinValue;
public double StartTime { get; private set; } = double.MaxValue;
public double EndTime { get; private set; } = double.MinValue;
private readonly Cached<double> endTimeBacking = new Cached<double>();
public double EndTime => endTimeBacking.IsValid ? endTimeBacking : endTimeBacking.Value = HasCommands ? commands.Max(c => c.EndTime) : double.MaxValue;
public T StartValue => HasCommands ? commands.OrderBy(c => c.StartTime).First().StartValue : default;
public T EndValue => HasCommands ? commands.OrderByDescending(c => c.EndTime).First().EndValue : default;
public T StartValue { get; private set; }
public T EndValue { get; private set; }
public void Add(Easing easing, double startTime, double endTime, T startValue, T endValue)
{
if (endTime < startTime)
return;
commands.Add(new TypedCommand { Easing = easing, StartTime = startTime, EndTime = endTime, StartValue = startValue, EndValue = endValue, });
commands.Add(new TypedCommand { Easing = easing, StartTime = startTime, EndTime = endTime, StartValue = startValue, EndValue = endValue });
startTimeBacking.Invalidate();
endTimeBacking.Invalidate();
if (startTime < StartTime)
{
StartValue = startValue;
StartTime = startTime;
}
if (endTime > EndTime)
{
EndValue = endValue;
EndTime = endTime;
}
}
public override string ToString()

View File

@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using osuTK;
using osuTK.Graphics;
using osu.Framework.Graphics;
@ -25,28 +26,52 @@ namespace osu.Game.Storyboards
public CommandTimeline<bool> FlipH = new CommandTimeline<bool>();
public CommandTimeline<bool> FlipV = new CommandTimeline<bool>();
private readonly ICommandTimeline[] timelines;
public CommandTimelineGroup()
{
timelines = new ICommandTimeline[]
{
X,
Y,
Scale,
VectorScale,
Rotation,
Colour,
Alpha,
BlendingParameters,
FlipH,
FlipV
};
}
[JsonIgnore]
public IEnumerable<ICommandTimeline> Timelines
public double CommandsStartTime
{
get
{
yield return X;
yield return Y;
yield return Scale;
yield return Rotation;
yield return Colour;
yield return Alpha;
yield return BlendingParameters;
yield return FlipH;
yield return FlipV;
double min = double.MaxValue;
for (int i = 0; i < timelines.Length; i++)
min = Math.Min(min, timelines[i].StartTime);
return min;
}
}
[JsonIgnore]
public double CommandsStartTime => Timelines.Where(t => t.HasCommands).Min(t => t.StartTime);
public double CommandsEndTime
{
get
{
double max = double.MinValue;
[JsonIgnore]
public double CommandsEndTime => Timelines.Where(t => t.HasCommands).Max(t => t.EndTime);
for (int i = 0; i < timelines.Length; i++)
max = Math.Max(max, timelines[i].EndTime);
return max;
}
}
[JsonIgnore]
public double CommandsDuration => CommandsEndTime - CommandsStartTime;
@ -61,7 +86,19 @@ namespace osu.Game.Storyboards
public double Duration => EndTime - StartTime;
[JsonIgnore]
public bool HasCommands => Timelines.Any(t => t.HasCommands);
public bool HasCommands
{
get
{
for (int i = 0; i < timelines.Length; i++)
{
if (timelines[i].HasCommands)
return true;
}
return false;
}
}
public virtual IEnumerable<CommandTimeline<T>.TypedCommand> GetCommands<T>(CommandTimelineSelector<T> timelineSelector, double offset = 0)
{

View File

@ -7,6 +7,7 @@ using osu.Game.Storyboards.Drawables;
using System;
using System.Collections.Generic;
using System.Linq;
using JetBrains.Annotations;
namespace osu.Game.Storyboards
{
@ -63,50 +64,56 @@ namespace osu.Game.Storyboards
public void ApplyTransforms(Drawable drawable, IEnumerable<Tuple<CommandTimelineGroup, double>> triggeredGroups = null)
{
applyCommands(drawable, getCommands(g => g.X, triggeredGroups), (d, value) => d.X = value, (d, value, duration, easing) => d.MoveToX(value, duration, easing));
applyCommands(drawable, getCommands(g => g.Y, triggeredGroups), (d, value) => d.Y = value, (d, value, duration, easing) => d.MoveToY(value, duration, easing));
applyCommands(drawable, getCommands(g => g.Scale, triggeredGroups), (d, value) => d.Scale = new Vector2(value), (d, value, duration, easing) => d.ScaleTo(value, duration, easing));
applyCommands(drawable, getCommands(g => g.Rotation, triggeredGroups), (d, value) => d.Rotation = value, (d, value, duration, easing) => d.RotateTo(value, duration, easing));
applyCommands(drawable, getCommands(g => g.Colour, triggeredGroups), (d, value) => d.Colour = value, (d, value, duration, easing) => d.FadeColour(value, duration, easing));
applyCommands(drawable, getCommands(g => g.Alpha, triggeredGroups), (d, value) => d.Alpha = value, (d, value, duration, easing) => d.FadeTo(value, duration, easing));
applyCommands(drawable, getCommands(g => g.BlendingParameters, triggeredGroups), (d, value) => d.Blending = value, (d, value, duration, easing) => d.TransformBlendingMode(value, duration),
// For performance reasons, we need to apply the commands in order by start time. Not doing so will cause many functions to be interleaved, resulting in O(n^2) complexity.
// To achieve this, commands are "generated" as pairs of (command, initFunc, transformFunc) and batched into a contiguous list
// The list is then stably-sorted (to preserve command order), and applied to the drawable sequentially.
List<IGeneratedCommand> generated = new List<IGeneratedCommand>();
generateCommands(generated, getCommands(g => g.X, triggeredGroups), (d, value) => d.X = value, (d, value, duration, easing) => d.MoveToX(value, duration, easing));
generateCommands(generated, getCommands(g => g.Y, triggeredGroups), (d, value) => d.Y = value, (d, value, duration, easing) => d.MoveToY(value, duration, easing));
generateCommands(generated, getCommands(g => g.Scale, triggeredGroups), (d, value) => d.Scale = new Vector2(value), (d, value, duration, easing) => d.ScaleTo(value, duration, easing));
generateCommands(generated, getCommands(g => g.Rotation, triggeredGroups), (d, value) => d.Rotation = value, (d, value, duration, easing) => d.RotateTo(value, duration, easing));
generateCommands(generated, getCommands(g => g.Colour, triggeredGroups), (d, value) => d.Colour = value, (d, value, duration, easing) => d.FadeColour(value, duration, easing));
generateCommands(generated, getCommands(g => g.Alpha, triggeredGroups), (d, value) => d.Alpha = value, (d, value, duration, easing) => d.FadeTo(value, duration, easing));
generateCommands(generated, getCommands(g => g.BlendingParameters, triggeredGroups), (d, value) => d.Blending = value, (d, value, duration, easing) => d.TransformBlendingMode(value, duration),
false);
if (drawable is IVectorScalable vectorScalable)
{
applyCommands(drawable, getCommands(g => g.VectorScale, triggeredGroups), (d, value) => vectorScalable.VectorScale = value,
generateCommands(generated, getCommands(g => g.VectorScale, triggeredGroups), (d, value) => vectorScalable.VectorScale = value,
(d, value, duration, easing) => vectorScalable.VectorScaleTo(value, duration, easing));
}
if (drawable is IFlippable flippable)
{
applyCommands(drawable, getCommands(g => g.FlipH, triggeredGroups), (d, value) => flippable.FlipH = value, (d, value, duration, easing) => flippable.TransformFlipH(value, duration),
generateCommands(generated, getCommands(g => g.FlipH, triggeredGroups), (d, value) => flippable.FlipH = value, (d, value, duration, easing) => flippable.TransformFlipH(value, duration),
false);
applyCommands(drawable, getCommands(g => g.FlipV, triggeredGroups), (d, value) => flippable.FlipV = value, (d, value, duration, easing) => flippable.TransformFlipV(value, duration),
generateCommands(generated, getCommands(g => g.FlipV, triggeredGroups), (d, value) => flippable.FlipV = value, (d, value, duration, easing) => flippable.TransformFlipV(value, duration),
false);
}
foreach (var command in generated.OrderBy(g => g.StartTime))
command.ApplyTo(drawable);
}
private void applyCommands<T>(Drawable drawable, IEnumerable<CommandTimeline<T>.TypedCommand> commands, DrawablePropertyInitializer<T> initializeProperty, DrawableTransformer<T> transform,
bool alwaysInitialize = true)
where T : struct
private void generateCommands<T>(List<IGeneratedCommand> resultList, IEnumerable<CommandTimeline<T>.TypedCommand> commands,
DrawablePropertyInitializer<T> initializeProperty, DrawableTransformer<T> transform, bool alwaysInitialize = true)
{
var initialized = false;
bool initialized = false;
foreach (var command in commands.OrderBy(l => l))
foreach (var command in commands)
{
DrawablePropertyInitializer<T> initFunc = null;
if (!initialized)
{
if (alwaysInitialize || command.StartTime == command.EndTime)
initializeProperty.Invoke(drawable, command.StartValue);
initFunc = initializeProperty;
initialized = true;
}
using (drawable.BeginAbsoluteSequence(command.StartTime))
{
transform(drawable, command.StartValue, 0, Easing.None);
transform(drawable, command.EndValue, command.Duration, command.Easing);
}
resultList.Add(new GeneratedCommand<T>(command, initFunc, transform));
}
}
@ -127,5 +134,39 @@ namespace osu.Game.Storyboards
public override string ToString()
=> $"{Path}, {Origin}, {InitialPosition}";
private interface IGeneratedCommand
{
double StartTime { get; }
void ApplyTo(Drawable drawable);
}
private readonly struct GeneratedCommand<T> : IGeneratedCommand
{
public double StartTime => command.StartTime;
private readonly DrawablePropertyInitializer<T> initializeProperty;
private readonly DrawableTransformer<T> transform;
private readonly CommandTimeline<T>.TypedCommand command;
public GeneratedCommand([NotNull] CommandTimeline<T>.TypedCommand command, [CanBeNull] DrawablePropertyInitializer<T> initializeProperty, [NotNull] DrawableTransformer<T> transform)
{
this.command = command;
this.initializeProperty = initializeProperty;
this.transform = transform;
}
public void ApplyTo(Drawable drawable)
{
initializeProperty?.Invoke(drawable, command.StartValue);
using (drawable.BeginAbsoluteSequence(command.StartTime))
{
transform(drawable, command.StartValue, 0, Easing.None);
transform(drawable, command.EndValue, command.Duration, command.Easing);
}
}
}
}
}

View File

@ -11,7 +11,10 @@ using osu.Game.Overlays.Notifications;
namespace osu.Game.Updater
{
public abstract class UpdateManager : CompositeDrawable
/// <summary>
/// An update manager which only shows notifications after an update completes.
/// </summary>
public class UpdateManager : CompositeDrawable
{
[Resolved]
private OsuConfigManager config { get; set; }

View File

@ -23,7 +23,7 @@
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.6" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2019.1215.0" />
<PackageReference Include="ppy.osu.Framework" Version="2019.1215.0" />
<PackageReference Include="ppy.osu.Framework" Version="2019.1224.0" />
<PackageReference Include="Sentry" Version="1.2.0" />
<PackageReference Include="SharpCompress" Version="0.24.0" />
<PackageReference Include="NUnit" Version="3.12.0" />

View File

@ -74,7 +74,7 @@
</ItemGroup>
<ItemGroup Label="Package References">
<PackageReference Include="ppy.osu.Game.Resources" Version="2019.1215.0" />
<PackageReference Include="ppy.osu.Framework.iOS" Version="2019.1215.0" />
<PackageReference Include="ppy.osu.Framework.iOS" Version="2019.1224.0" />
</ItemGroup>
<!-- Xamarin.iOS does not automatically handle transitive dependencies from NuGet packages. -->
<ItemGroup Label="Transitive Dependencies">
@ -82,7 +82,7 @@
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.6" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.6" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Include="ppy.osu.Framework" Version="2019.1215.0" />
<PackageReference Include="ppy.osu.Framework" Version="2019.1224.0" />
<PackageReference Include="SharpCompress" Version="0.24.0" />
<PackageReference Include="NUnit" Version="3.12.0" />
<PackageReference Include="SharpRaven" Version="2.4.0" />

View File

@ -5,7 +5,7 @@ using UIKit;
namespace osu.iOS
{
public class Application
public static class Application
{
public static void Main(string[] args)
{

View File

@ -4,11 +4,19 @@
using System;
using Foundation;
using osu.Game;
using osu.Game.Updater;
namespace osu.iOS
{
public class OsuGameIOS : OsuGame
{
public override Version AssemblyVersion => new Version(NSBundle.MainBundle.InfoDictionary["CFBundleVersion"].ToString());
protected override void LoadComplete()
{
base.LoadComplete();
Add(new UpdateManager());
}
}
}