1
0
mirror of https://github.com/ppy/osu.git synced 2025-01-06 04:53:12 +08:00

Merge remote-tracking branch 'origin/master' into timeshift-wip

# Conflicts:
#	osu.Game.Tests/Visual/TestCasePollingComponent.cs
#	osu.Game/Online/API/APIRequest.cs
#	osu.Game/osu.Game.csproj
This commit is contained in:
smoogipoo 2018-12-21 12:51:31 +09:00
commit 5d59a1ffca
57 changed files with 907 additions and 292 deletions

View File

@ -1,6 +1,6 @@
<component name="ProjectRunConfigurationManager"> <component name="ProjectRunConfigurationManager">
<configuration default="false" name="RulesetTests (catch)" type="DotNetProject" factoryName=".NET Project"> <configuration default="false" name="RulesetTests (catch)" type="DotNetProject" factoryName=".NET Project">
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Game.Rulesets.Catch.Tests/bin/Debug/netcoreapp2.1/osu.Game.Rulesets.Catch.Tests.dll" /> <option name="EXE_PATH" value="$PROJECT_DIR$/osu.Game.Rulesets.Catch.Tests/bin/Debug/netcoreapp2.2/osu.Game.Rulesets.Catch.Tests.dll" />
<option name="PROGRAM_PARAMETERS" value="" /> <option name="PROGRAM_PARAMETERS" value="" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/osu.Game.Rulesets.Catch.Tests" /> <option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/osu.Game.Rulesets.Catch.Tests" />
<option name="PASS_PARENT_ENVS" value="1" /> <option name="PASS_PARENT_ENVS" value="1" />

View File

@ -1,6 +1,6 @@
<component name="ProjectRunConfigurationManager"> <component name="ProjectRunConfigurationManager">
<configuration default="false" name="RulesetTests (mania)" type="DotNetProject" factoryName=".NET Project"> <configuration default="false" name="RulesetTests (mania)" type="DotNetProject" factoryName=".NET Project">
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Game.Rulesets.Mania.Tests/bin/Debug/netcoreapp2.1/osu.Game.Rulesets.Mania.Tests.dll" /> <option name="EXE_PATH" value="$PROJECT_DIR$/osu.Game.Rulesets.Mania.Tests/bin/Debug/netcoreapp2.2/osu.Game.Rulesets.Mania.Tests.dll" />
<option name="PROGRAM_PARAMETERS" value="" /> <option name="PROGRAM_PARAMETERS" value="" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/osu.Game.Rulesets.Mania.Tests" /> <option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/osu.Game.Rulesets.Mania.Tests" />
<option name="PASS_PARENT_ENVS" value="1" /> <option name="PASS_PARENT_ENVS" value="1" />

View File

@ -1,6 +1,6 @@
<component name="ProjectRunConfigurationManager"> <component name="ProjectRunConfigurationManager">
<configuration default="false" name="RulesetTests (osu!)" type="DotNetProject" factoryName=".NET Project"> <configuration default="false" name="RulesetTests (osu!)" type="DotNetProject" factoryName=".NET Project">
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Game.Rulesets.Osu.Tests/bin/Debug/netcoreapp2.1/osu.Game.Rulesets.Osu.Tests.dll" /> <option name="EXE_PATH" value="$PROJECT_DIR$/osu.Game.Rulesets.Osu.Tests/bin/Debug/netcoreapp2.2/osu.Game.Rulesets.Osu.Tests.dll" />
<option name="PROGRAM_PARAMETERS" value="" /> <option name="PROGRAM_PARAMETERS" value="" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/osu.Game.Rulesets.Osu.Tests" /> <option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/osu.Game.Rulesets.Osu.Tests" />
<option name="PASS_PARENT_ENVS" value="1" /> <option name="PASS_PARENT_ENVS" value="1" />

View File

@ -1,6 +1,6 @@
<component name="ProjectRunConfigurationManager"> <component name="ProjectRunConfigurationManager">
<configuration default="false" name="RulesetTests (taiko)" type="DotNetProject" factoryName=".NET Project"> <configuration default="false" name="RulesetTests (taiko)" type="DotNetProject" factoryName=".NET Project">
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Game.Rulesets.Taiko.Tests/bin/Debug/netcoreapp2.1/osu.Game.Rulesets.Taiko.Tests.dll" /> <option name="EXE_PATH" value="$PROJECT_DIR$/osu.Game.Rulesets.Taiko.Tests/bin/Debug/netcoreapp2.2/osu.Game.Rulesets.Taiko.Tests.dll" />
<option name="PROGRAM_PARAMETERS" value="" /> <option name="PROGRAM_PARAMETERS" value="" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/osu.Game.Rulesets.Taiko.Tests" /> <option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/osu.Game.Rulesets.Taiko.Tests" />
<option name="PASS_PARENT_ENVS" value="1" /> <option name="PASS_PARENT_ENVS" value="1" />

View File

@ -1,6 +1,6 @@
<component name="ProjectRunConfigurationManager"> <component name="ProjectRunConfigurationManager">
<configuration default="false" name="VisualTests" type="DotNetProject" factoryName=".NET Project"> <configuration default="false" name="VisualTests" type="DotNetProject" factoryName=".NET Project">
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Game.Tests/bin/Debug/netcoreapp2.1/osu.Game.Tests.dll" /> <option name="EXE_PATH" value="$PROJECT_DIR$/osu.Game.Tests/bin/Debug/netcoreapp2.2/osu.Game.Tests.dll" />
<option name="PROGRAM_PARAMETERS" value="" /> <option name="PROGRAM_PARAMETERS" value="" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/osu.Game.Tests" /> <option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/osu.Game.Tests" />
<option name="PASS_PARENT_ENVS" value="1" /> <option name="PASS_PARENT_ENVS" value="1" />

View File

@ -1,17 +1,20 @@
<component name="ProjectRunConfigurationManager"> <component name="ProjectRunConfigurationManager">
<configuration default="false" name="osu!" type="DotNetProject" factoryName=".NET Project"> <configuration default="false" name="osu!" type="DotNetProject" factoryName=".NET Project">
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Desktop/bin/Debug/netcoreapp2.1/osu!.dll" /> <option name="EXE_PATH" value="$PROJECT_DIR$/osu.Desktop/bin/Debug/netcoreapp2.2/osu!.dll" />
<option name="PROGRAM_PARAMETERS" value="" /> <option name="PROGRAM_PARAMETERS" value="" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/osu.Desktop" /> <option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/osu.Desktop" />
<option name="PASS_PARENT_ENVS" value="1" /> <option name="PASS_PARENT_ENVS" value="1" />
<option name="USE_EXTERNAL_CONSOLE" value="0" /> <option name="USE_EXTERNAL_CONSOLE" value="0" />
<option name="USE_MONO" value="0" /> <option name="USE_MONO" value="0" />
<option name="RUNTIME_ARGUMENTS" value="" />
<option name="PROJECT_PATH" value="$PROJECT_DIR$/osu.Desktop/osu.Desktop.csproj" /> <option name="PROJECT_PATH" value="$PROJECT_DIR$/osu.Desktop/osu.Desktop.csproj" />
<option name="PROJECT_EXE_PATH_TRACKING" value="1" /> <option name="PROJECT_EXE_PATH_TRACKING" value="1" />
<option name="PROJECT_ARGUMENTS_TRACKING" value="1" /> <option name="PROJECT_ARGUMENTS_TRACKING" value="1" />
<option name="PROJECT_WORKING_DIRECTORY_TRACKING" value="1" /> <option name="PROJECT_WORKING_DIRECTORY_TRACKING" value="1" />
<option name="PROJECT_KIND" value="DotNetCore" /> <option name="PROJECT_KIND" value="DotNetCore" />
<option name="PROJECT_TFM" value=".NETCoreApp,Version=v2.1" /> <option name="PROJECT_TFM" value=".NETCoreApp,Version=v2.2" />
<method /> <method v="2">
<option name="Build" enabled="true" />
</method>
</configuration> </configuration>
</component> </component>

16
.vscode/launch.json vendored
View File

@ -7,13 +7,13 @@
"request": "launch", "request": "launch",
"program": "dotnet", "program": "dotnet",
"args": [ "args": [
"${workspaceRoot}/osu.Game.Tests/bin/Debug/netcoreapp2.1/osu.Game.Tests.dll" "${workspaceRoot}/osu.Game.Tests/bin/Debug/netcoreapp2.2/osu.Game.Tests.dll"
], ],
"cwd": "${workspaceRoot}", "cwd": "${workspaceRoot}",
"preLaunchTask": "Build tests (Debug)", "preLaunchTask": "Build tests (Debug)",
"linux": { "linux": {
"env": { "env": {
"LD_LIBRARY_PATH": "${workspaceRoot}/osu.Game.Tests/bin/Debug/netcoreapp2.1:${env:LD_LIBRARY_PATH}" "LD_LIBRARY_PATH": "${workspaceRoot}/osu.Game.Tests/bin/Debug/netcoreapp2.2:${env:LD_LIBRARY_PATH}"
} }
}, },
"console": "internalConsole" "console": "internalConsole"
@ -24,13 +24,13 @@
"request": "launch", "request": "launch",
"program": "dotnet", "program": "dotnet",
"args": [ "args": [
"${workspaceRoot}/osu.Game.Tests/bin/Release/netcoreapp2.1/osu.Game.Tests.dll" "${workspaceRoot}/osu.Game.Tests/bin/Release/netcoreapp2.2/osu.Game.Tests.dll"
], ],
"cwd": "${workspaceRoot}", "cwd": "${workspaceRoot}",
"preLaunchTask": "Build tests (Release)", "preLaunchTask": "Build tests (Release)",
"linux": { "linux": {
"env": { "env": {
"LD_LIBRARY_PATH": "${workspaceRoot}/osu.Game.Tests/bin/Release/netcoreapp2.1:${env:LD_LIBRARY_PATH}" "LD_LIBRARY_PATH": "${workspaceRoot}/osu.Game.Tests/bin/Release/netcoreapp2.2:${env:LD_LIBRARY_PATH}"
} }
}, },
"console": "internalConsole" "console": "internalConsole"
@ -41,13 +41,13 @@
"request": "launch", "request": "launch",
"program": "dotnet", "program": "dotnet",
"args": [ "args": [
"${workspaceRoot}/osu.Desktop/bin/Debug/netcoreapp2.1/osu!.dll" "${workspaceRoot}/osu.Desktop/bin/Debug/netcoreapp2.2/osu!.dll"
], ],
"cwd": "${workspaceRoot}", "cwd": "${workspaceRoot}",
"preLaunchTask": "Build osu! (Debug)", "preLaunchTask": "Build osu! (Debug)",
"linux": { "linux": {
"env": { "env": {
"LD_LIBRARY_PATH": "${workspaceRoot}/osu.Desktop/bin/Debug/netcoreapp2.1:${env:LD_LIBRARY_PATH}" "LD_LIBRARY_PATH": "${workspaceRoot}/osu.Desktop/bin/Debug/netcoreapp2.2:${env:LD_LIBRARY_PATH}"
} }
}, },
"console": "internalConsole" "console": "internalConsole"
@ -58,13 +58,13 @@
"request": "launch", "request": "launch",
"program": "dotnet", "program": "dotnet",
"args": [ "args": [
"${workspaceRoot}/osu.Desktop/bin/Release/netcoreapp2.1/osu!.dll" "${workspaceRoot}/osu.Desktop/bin/Release/netcoreapp2.2/osu!.dll"
], ],
"cwd": "${workspaceRoot}", "cwd": "${workspaceRoot}",
"preLaunchTask": "Build osu! (Release)", "preLaunchTask": "Build osu! (Release)",
"linux": { "linux": {
"env": { "env": {
"LD_LIBRARY_PATH": "${workspaceRoot}/osu.Desktop/bin/Release/netcoreapp2.1:${env:LD_LIBRARY_PATH}" "LD_LIBRARY_PATH": "${workspaceRoot}/osu.Desktop/bin/Release/netcoreapp2.2:${env:LD_LIBRARY_PATH}"
} }
}, },
"console": "internalConsole" "console": "internalConsole"

6
.vscode/tasks.json vendored
View File

@ -11,7 +11,6 @@
"build", "build",
"--no-restore", "--no-restore",
"osu.Desktop", "osu.Desktop",
"/p:TargetFramework=netcoreapp2.1",
"/p:GenerateFullPaths=true", "/p:GenerateFullPaths=true",
"/m", "/m",
"/verbosity:m" "/verbosity:m"
@ -27,7 +26,6 @@
"build", "build",
"--no-restore", "--no-restore",
"osu.Desktop", "osu.Desktop",
"/p:TargetFramework=netcoreapp2.1",
"/p:Configuration=Release", "/p:Configuration=Release",
"/p:GenerateFullPaths=true", "/p:GenerateFullPaths=true",
"/m", "/m",
@ -44,7 +42,6 @@
"build", "build",
"--no-restore", "--no-restore",
"osu.Game.Tests", "osu.Game.Tests",
"/p:TargetFramework=netcoreapp2.1",
"/p:GenerateFullPaths=true", "/p:GenerateFullPaths=true",
"/m", "/m",
"/verbosity:m" "/verbosity:m"
@ -60,7 +57,6 @@
"build", "build",
"--no-restore", "--no-restore",
"osu.Game.Tests", "osu.Game.Tests",
"/p:TargetFramework=netcoreapp2.1",
"/p:Configuration=Release", "/p:Configuration=Release",
"/p:GenerateFullPaths=true", "/p:GenerateFullPaths=true",
"/m", "/m",
@ -70,7 +66,7 @@
"problemMatcher": "$msCompile" "problemMatcher": "$msCompile"
}, },
{ {
"label": "Restore (netcoreapp2.1)", "label": "Restore (netcoreapp2.2)",
"type": "shell", "type": "shell",
"command": "dotnet", "command": "dotnet",
"args": [ "args": [

View File

@ -10,7 +10,7 @@ We are accepting bug reports (please report with as much detail as possible). Fe
# Requirements # Requirements
- A desktop platform with the [.NET Core SDK 2.1](https://www.microsoft.com/net/learn/get-started) or higher installed. - A desktop platform with the [.NET Core SDK 2.2](https://www.microsoft.com/net/learn/get-started) or higher installed.
- When working with the codebase, we recommend using an IDE with intellisense and syntax highlighting, such as [Visual Studio Community Edition](https://www.visualstudio.com/) (Windows), [Visual Studio Code](https://code.visualstudio.com/) (with the C# plugin installed) or [Jetbrains Rider](https://www.jetbrains.com/rider/) (commercial). - When working with the codebase, we recommend using an IDE with intellisense and syntax highlighting, such as [Visual Studio Community Edition](https://www.visualstudio.com/) (Windows), [Visual Studio Code](https://code.visualstudio.com/) (with the C# plugin installed) or [Jetbrains Rider](https://www.jetbrains.com/rider/) (commercial).
# Building and running # Building and running

@ -1 +1 @@
Subproject commit 694cb03f19c93106ed0f2593f3e506e835fb652a Subproject commit 9880089b4e8fcd78d68f30c8a40d43bf8dccca86

View File

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\osu.Game.props" /> <Import Project="..\osu.Game.props" />
<PropertyGroup Label="Project"> <PropertyGroup Label="Project">
<TargetFramework>netcoreapp2.1</TargetFramework> <TargetFramework>netcoreapp2.2</TargetFramework>
<OutputType>WinExe</OutputType> <OutputType>WinExe</OutputType>
<PlatformTarget>AnyCPU</PlatformTarget> <PlatformTarget>AnyCPU</PlatformTarget>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks> <AllowUnsafeBlocks>true</AllowUnsafeBlocks>

View File

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

View File

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

View File

@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Catch.Judgements
} }
} }
protected override float HealthIncreaseFor(HitResult result) protected override double HealthIncreaseFor(HitResult result)
{ {
switch (result) switch (result)
{ {

View File

@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Catch.Judgements
} }
} }
protected override float HealthIncreaseFor(HitResult result) protected override double HealthIncreaseFor(HitResult result)
{ {
switch (result) switch (result)
{ {

View File

@ -22,29 +22,17 @@ namespace osu.Game.Rulesets.Catch.Judgements
} }
} }
/// <summary> protected override double HealthIncreaseFor(HitResult result)
/// Retrieves the numeric health increase of a <see cref="HitResult"/>.
/// </summary>
/// <param name="result">The <see cref="HitResult"/> to find the numeric health increase for.</param>
/// <returns>The numeric health increase of <paramref name="result"/>.</returns>
protected virtual float HealthIncreaseFor(HitResult result)
{ {
switch (result) switch (result)
{ {
default: default:
return 0; return 0;
case HitResult.Perfect: case HitResult.Perfect:
return 10.2f; return 10.2;
} }
} }
/// <summary>
/// Retrieves the numeric health increase of a <see cref="JudgementResult"/>.
/// </summary>
/// <param name="result">The <see cref="JudgementResult"/> to find the numeric health increase for.</param>
/// <returns>The numeric health increase of <paramref name="result"/>.</returns>
public float HealthIncreaseFor(JudgementResult result) => HealthIncreaseFor(result.Type);
/// <summary> /// <summary>
/// Whether fruit on the platter should explode or drop. /// Whether fruit on the platter should explode or drop.
/// Note that this is only checked if the owning object is also <see cref="IHasComboInformation.LastInCombo" /> /// Note that this is only checked if the owning object is also <see cref="IHasComboInformation.LastInCombo" />

View File

@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Catch.Judgements
} }
} }
protected override float HealthIncreaseFor(HitResult result) protected override double HealthIncreaseFor(HitResult result)
{ {
switch (result) switch (result)
{ {

View File

@ -3,7 +3,6 @@
using System; using System;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Rulesets.Catch.Judgements;
using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
@ -40,8 +39,7 @@ namespace osu.Game.Rulesets.Catch.Scoring
return; return;
} }
if (result.Judgement is CatchJudgement catchJudgement) Health.Value += Math.Max(result.Judgement.HealthIncreaseFor(result) - hpDrainRate, 0) * harshness;
Health.Value += Math.Max(catchJudgement.HealthIncreaseFor(result) - hpDrainRate, 0) * harshness;
} }
} }
} }

View File

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

View File

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

View File

@ -20,11 +20,10 @@ namespace osu.Game.Rulesets.Mania.Objects
{ HitResult.Miss, (376, 346, 316) }, { HitResult.Miss, (376, 346, 316) },
}; };
public override bool IsHitResultAllowed(HitResult result) => true;
public override void SetDifficulty(double difficulty) public override void SetDifficulty(double difficulty)
{ {
AllowsPerfect = true;
AllowsOk = true;
Perfect = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Perfect]); Perfect = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Perfect]);
Great = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Great]); Great = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Great]);
Good = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Good]); Good = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Good]);

View File

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

View File

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

View File

@ -0,0 +1,194 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Textures;
using osu.Game.Beatmaps;
using osu.Game.Graphics;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.UI;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Rulesets.Osu.Mods
{
public class OsuModBlinds : Mod, IApplicableToRulesetContainer<OsuHitObject>, IApplicableToScoreProcessor
{
public override string Name => "Blinds";
public override string Description => "Play with blinds on your screen.";
public override string Acronym => "BL";
public override FontAwesome Icon => FontAwesome.fa_adjust;
public override ModType Type => ModType.DifficultyIncrease;
public override bool Ranked => false;
public override double ScoreMultiplier => 1.12;
private DrawableOsuBlinds blinds;
public void ApplyToRulesetContainer(RulesetContainer<OsuHitObject> rulesetContainer)
{
rulesetContainer.Overlays.Add(blinds = new DrawableOsuBlinds(rulesetContainer.Playfield.HitObjectContainer, rulesetContainer.Beatmap));
}
public void ApplyToScoreProcessor(ScoreProcessor scoreProcessor)
{
scoreProcessor.Health.ValueChanged += val => { blinds.AnimateClosedness((float)val); };
}
/// <summary>
/// Element for the Blinds mod drawing 2 black boxes covering the whole screen which resize inside a restricted area with some leniency.
/// </summary>
public class DrawableOsuBlinds : Container
{
/// <summary>
/// Black background boxes behind blind panel textures.
/// </summary>
private Box blackBoxLeft, blackBoxRight;
private Drawable panelLeft, panelRight, bgPanelLeft, bgPanelRight;
private readonly Beatmap<OsuHitObject> beatmap;
/// <summary>
/// Value between 0 and 1 setting a maximum "closedness" for the blinds.
/// Useful for animating how far the blinds can be opened while keeping them at the original position if they are wider open than this.
/// </summary>
private const float target_clamp = 1;
private readonly float targetBreakMultiplier = 0;
private readonly float easing = 1;
private readonly CompositeDrawable restrictTo;
/// <summary>
/// <para>
/// Percentage of playfield to extend blinds over. Basically moves the origin points where the blinds start.
/// </para>
/// <para>
/// -1 would mean the blinds always cover the whole screen no matter health.
/// 0 would mean the blinds will only ever be on the edge of the playfield on 0% health.
/// 1 would mean the blinds are fully outside the playfield on 50% health.
/// Infinity would mean the blinds are always outside the playfield except on 100% health.
/// </para>
/// </summary>
private const float leniency = 0.1f;
public DrawableOsuBlinds(CompositeDrawable restrictTo, Beatmap<OsuHitObject> beatmap)
{
this.restrictTo = restrictTo;
this.beatmap = beatmap;
}
[BackgroundDependencyLoader]
private void load()
{
RelativeSizeAxes = Axes.Both;
Children = new[]
{
blackBoxLeft = new Box
{
Anchor = Anchor.TopLeft,
Origin = Anchor.TopLeft,
Colour = Color4.Black,
RelativeSizeAxes = Axes.Y,
},
blackBoxRight = new Box
{
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
Colour = Color4.Black,
RelativeSizeAxes = Axes.Y,
},
bgPanelLeft = new ModBlindsPanel
{
Origin = Anchor.TopRight,
Colour = Color4.Gray,
},
panelLeft = new ModBlindsPanel { Origin = Anchor.TopRight, },
bgPanelRight = new ModBlindsPanel { Colour = Color4.Gray },
panelRight = new ModBlindsPanel()
};
}
private float calculateGap(float value) => MathHelper.Clamp(value, 0, target_clamp) * targetBreakMultiplier;
// lagrange polinominal for (0,0) (0.6,0.4) (1,1) should make a good curve
private static float applyAdjustmentCurve(float value) => 0.6f * value * value + 0.4f * value;
protected override void Update()
{
float start = Parent.ToLocalSpace(restrictTo.ScreenSpaceDrawQuad.TopLeft).X;
float end = Parent.ToLocalSpace(restrictTo.ScreenSpaceDrawQuad.TopRight).X;
float rawWidth = end - start;
start -= rawWidth * leniency * 0.5f;
end += rawWidth * leniency * 0.5f;
float width = (end - start) * 0.5f * applyAdjustmentCurve(calculateGap(easing));
// different values in case the playfield ever moves from center to somewhere else.
blackBoxLeft.Width = start + width;
blackBoxRight.Width = DrawWidth - end + width;
panelLeft.X = start + width;
panelRight.X = end - width;
bgPanelLeft.X = start;
bgPanelRight.X = end;
}
protected override void LoadComplete()
{
const float break_open_early = 500;
const float break_close_late = 250;
base.LoadComplete();
var firstObj = beatmap.HitObjects[0];
var startDelay = firstObj.StartTime - firstObj.TimePreempt;
using (BeginAbsoluteSequence(startDelay + break_close_late, true))
leaveBreak();
foreach (var breakInfo in beatmap.Breaks)
{
if (breakInfo.HasEffect)
{
using (BeginAbsoluteSequence(breakInfo.StartTime - break_open_early, true))
{
enterBreak();
using (BeginDelayedSequence(breakInfo.Duration + break_open_early + break_close_late, true))
leaveBreak();
}
}
}
}
private void enterBreak() => this.TransformTo(nameof(targetBreakMultiplier), 0f, 1000, Easing.OutSine);
private void leaveBreak() => this.TransformTo(nameof(targetBreakMultiplier), 1f, 2500, Easing.OutBounce);
/// <summary>
/// 0 is open, 1 is closed.
/// </summary>
public void AnimateClosedness(float value) => this.TransformTo(nameof(easing), value, 200, Easing.OutQuint);
public class ModBlindsPanel : Sprite
{
[BackgroundDependencyLoader]
private void load(TextureStore textures)
{
Texture = textures.Get("Play/osu/blinds-panel");
}
}
}
}
}

View File

@ -103,7 +103,7 @@ namespace osu.Game.Rulesets.Osu
new MultiMod(new OsuModSuddenDeath(), new OsuModPerfect()), new MultiMod(new OsuModSuddenDeath(), new OsuModPerfect()),
new MultiMod(new OsuModDoubleTime(), new OsuModNightcore()), new MultiMod(new OsuModDoubleTime(), new OsuModNightcore()),
new OsuModHidden(), new OsuModHidden(),
new OsuModFlashlight(), new MultiMod(new OsuModFlashlight(), new OsuModBlinds()),
}; };
case ModType.Conversion: case ModType.Conversion:
return new Mod[] return new Mod[]

View File

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

View File

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

View File

@ -0,0 +1,24 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Taiko.Judgements
{
public class TaikoDrumRollJudgement : TaikoJudgement
{
public override bool AffectsCombo => false;
protected override double HealthIncreaseFor(HitResult result)
{
// Drum rolls can be ignored with no health penalty
switch (result)
{
case HitResult.Miss:
return 0;
default:
return base.HealthIncreaseFor(result);
}
}
}
}

View File

@ -13,10 +13,21 @@ namespace osu.Game.Rulesets.Taiko.Judgements
{ {
switch (result) switch (result)
{ {
default:
return 0;
case HitResult.Great: case HitResult.Great:
return 200; return 200;
default:
return 0;
}
}
protected override double HealthIncreaseFor(HitResult result)
{
switch (result)
{
case HitResult.Great:
return 0.15;
default:
return 0;
} }
} }
} }

View File

@ -1,21 +0,0 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Taiko.Judgements
{
public class TaikoIntermediateSwellJudgement : TaikoJudgement
{
public override HitResult MaxResult => HitResult.Great;
public override bool AffectsCombo => false;
/// <summary>
/// Computes the numeric result value for the combo portion of the score.
/// </summary>
/// <param name="result">The result to compute the value for.</param>
/// <returns>The numeric result value.</returns>
protected override int NumericResultFor(HitResult result) => 0;
}
}

View File

@ -10,21 +10,31 @@ namespace osu.Game.Rulesets.Taiko.Judgements
{ {
public override HitResult MaxResult => HitResult.Great; public override HitResult MaxResult => HitResult.Great;
/// <summary>
/// Computes the numeric result value for the combo portion of the score.
/// </summary>
/// <param name="result">The result to compute the value for.</param>
/// <returns>The numeric result value.</returns>
protected override int NumericResultFor(HitResult result) protected override int NumericResultFor(HitResult result)
{ {
switch (result) switch (result)
{ {
default:
return 0;
case HitResult.Good: case HitResult.Good:
return 100; return 100;
case HitResult.Great: case HitResult.Great:
return 300; return 300;
default:
return 0;
}
}
protected override double HealthIncreaseFor(HitResult result)
{
switch (result)
{
case HitResult.Miss:
return -1.0;
case HitResult.Good:
return 1.1;
case HitResult.Great:
return 3.0;
default:
return 0;
} }
} }
} }

View File

@ -1,10 +1,15 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Taiko.Judgements namespace osu.Game.Rulesets.Taiko.Judgements
{ {
public class TaikoStrongJudgement : TaikoJudgement public class TaikoStrongJudgement : TaikoJudgement
{ {
// MainObject already changes the HP
protected override double HealthIncreaseFor(HitResult result) => 0;
public override bool AffectsCombo => false; public override bool AffectsCombo => false;
} }
} }

View File

@ -0,0 +1,23 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Taiko.Judgements
{
public class TaikoSwellJudgement : TaikoJudgement
{
public override bool AffectsCombo => false;
protected override double HealthIncreaseFor(HitResult result)
{
switch (result)
{
case HitResult.Miss:
return -0.65;
default:
return 0;
}
}
}
}

View File

@ -0,0 +1,16 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Taiko.Judgements
{
public class TaikoSwellTickJudgement : TaikoJudgement
{
public override bool AffectsCombo => false;
protected override int NumericResultFor(HitResult result) => 0;
protected override double HealthIncreaseFor(HitResult result) => 0;
}
}

View File

@ -173,13 +173,13 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
if (!userTriggered) if (!userTriggered)
{ {
if (timeOffset > second_hit_window) if (timeOffset - MainObject.Result.TimeOffset > second_hit_window)
ApplyResult(r => r.Type = HitResult.Miss); ApplyResult(r => r.Type = HitResult.Miss);
return; return;
} }
if (Math.Abs(MainObject.Result.TimeOffset - timeOffset) < second_hit_window) if (Math.Abs(timeOffset - MainObject.Result.TimeOffset) <= second_hit_window)
ApplyResult(r => r.Type = HitResult.Great); ApplyResult(r => r.Type = MainObject.Result.Type);
} }
public override bool OnPressed(TaikoAction action) public override bool OnPressed(TaikoAction action)

View File

@ -6,11 +6,11 @@ using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Taiko.Objects.Drawables namespace osu.Game.Rulesets.Taiko.Objects.Drawables
{ {
public class DrawableSwellTick : DrawableTaikoHitObject public class DrawableSwellTick : DrawableTaikoHitObject<SwellTick>
{ {
public override bool DisplayResult => false; public override bool DisplayResult => false;
public DrawableSwellTick(TaikoHitObject hitObject) public DrawableSwellTick(SwellTick hitObject)
: base(hitObject) : base(hitObject)
{ {
} }

View File

@ -5,6 +5,8 @@ using osu.Game.Rulesets.Objects.Types;
using System; using System;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Taiko.Judgements;
namespace osu.Game.Rulesets.Taiko.Objects namespace osu.Game.Rulesets.Taiko.Objects
{ {
@ -81,5 +83,7 @@ namespace osu.Game.Rulesets.Taiko.Objects
first = false; first = false;
} }
} }
public override Judgement CreateJudgement() => new TaikoDrumRollJudgement();
} }
} }

View File

@ -3,6 +3,8 @@
using System; using System;
using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Taiko.Judgements;
namespace osu.Game.Rulesets.Taiko.Objects namespace osu.Game.Rulesets.Taiko.Objects
{ {
@ -26,5 +28,7 @@ namespace osu.Game.Rulesets.Taiko.Objects
for (int i = 0; i < RequiredHits; i++) for (int i = 0; i < RequiredHits; i++)
AddNested(new SwellTick()); AddNested(new SwellTick());
} }
public override Judgement CreateJudgement() => new TaikoSwellJudgement();
} }
} }

View File

@ -1,9 +1,13 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Taiko.Judgements;
namespace osu.Game.Rulesets.Taiko.Objects namespace osu.Game.Rulesets.Taiko.Objects
{ {
public class SwellTick : TaikoHitObject public class SwellTick : TaikoHitObject
{ {
public override Judgement CreateJudgement() => new TaikoSwellTickJudgement();
} }
} }

View File

@ -14,15 +14,26 @@ namespace osu.Game.Rulesets.Taiko.Objects
{ {
{ HitResult.Great, (100, 70, 40) }, { HitResult.Great, (100, 70, 40) },
{ HitResult.Good, (240, 160, 100) }, { HitResult.Good, (240, 160, 100) },
{ HitResult.Meh, (270, 190, 140) }, { HitResult.Miss, (270, 190, 140) },
{ HitResult.Miss, (400, 400, 400) },
}; };
public override bool IsHitResultAllowed(HitResult result)
{
switch (result)
{
case HitResult.Great:
case HitResult.Good:
case HitResult.Miss:
return true;
default:
return false;
}
}
public override void SetDifficulty(double difficulty) public override void SetDifficulty(double difficulty)
{ {
Great = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Great]); Great = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Great]);
Good = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Good]); Good = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Good]);
Meh = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Meh]);
Miss = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Miss]); Miss = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Miss]);
} }
} }

View File

@ -4,7 +4,6 @@
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.Taiko.Judgements;
using osu.Game.Rulesets.Taiko.Objects; using osu.Game.Rulesets.Taiko.Objects;
using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI;
@ -13,51 +12,24 @@ namespace osu.Game.Rulesets.Taiko.Scoring
internal class TaikoScoreProcessor : ScoreProcessor<TaikoHitObject> internal class TaikoScoreProcessor : ScoreProcessor<TaikoHitObject>
{ {
/// <summary> /// <summary>
/// The HP awarded by a <see cref="HitResult.Great"/> hit. /// A value used for calculating <see cref="hpMultiplier"/>.
/// </summary> /// </summary>
private const double hp_hit_great = 0.03; private const double object_count_factor = 3;
/// <summary>
/// The HP awarded for a <see cref="HitResult.Good"/> hit.
/// </summary>
private const double hp_hit_good = 0.011;
/// <summary>
/// The minimum HP deducted for a <see cref="HitResult.Miss"/>.
/// This occurs when HP Drain = 0.
/// </summary>
private const double hp_miss_min = -0.0018;
/// <summary>
/// The median HP deducted for a <see cref="HitResult.Miss"/>.
/// This occurs when HP Drain = 5.
/// </summary>
private const double hp_miss_mid = -0.0075;
/// <summary>
/// The maximum HP deducted for a <see cref="HitResult.Miss"/>.
/// This occurs when HP Drain = 10.
/// </summary>
private const double hp_miss_max = -0.12;
/// <summary>
/// The HP awarded for a <see cref="DrumRollTick"/> hit.
/// <para>
/// <see cref="DrumRollTick"/> hits award less HP as they're more spammable, although in hindsight
/// this probably awards too little HP and is kept at this value for now for compatibility.
/// </para>
/// </summary>
private const double hp_hit_tick = 0.00000003;
/// <summary> /// <summary>
/// Taiko fails at the end of the map if the player has not half-filled their HP bar. /// Taiko fails at the end of the map if the player has not half-filled their HP bar.
/// </summary> /// </summary>
protected override bool DefaultFailCondition => JudgedHits == MaxHits && Health.Value <= 0.5; protected override bool DefaultFailCondition => JudgedHits == MaxHits && Health.Value <= 0.5;
private double hpIncreaseTick; /// <summary>
private double hpIncreaseGreat; /// HP multiplier for a successful <see cref="HitResult"/>.
private double hpIncreaseGood; /// </summary>
private double hpIncreaseMiss; private double hpMultiplier;
/// <summary>
/// HP multiplier for a <see cref="HitResult.Miss"/>.
/// </summary>
private double hpMissMultiplier;
public TaikoScoreProcessor(RulesetContainer<TaikoHitObject> rulesetContainer) public TaikoScoreProcessor(RulesetContainer<TaikoHitObject> rulesetContainer)
: base(rulesetContainer) : base(rulesetContainer)
@ -68,38 +40,23 @@ namespace osu.Game.Rulesets.Taiko.Scoring
{ {
base.ApplyBeatmap(beatmap); base.ApplyBeatmap(beatmap);
double hpMultiplierNormal = 1 / (hp_hit_great * beatmap.HitObjects.FindAll(o => o is Hit).Count * BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.DrainRate, 0.5, 0.75, 0.98)); hpMultiplier = 1 / (object_count_factor * beatmap.HitObjects.FindAll(o => o is Hit).Count * BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.DrainRate, 0.5, 0.75, 0.98));
hpIncreaseTick = hp_hit_tick; hpMissMultiplier = BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.DrainRate, 0.0018, 0.0075, 0.0120);
hpIncreaseGreat = hpMultiplierNormal * hp_hit_great;
hpIncreaseGood = hpMultiplierNormal * hp_hit_good;
hpIncreaseMiss = BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.DrainRate, hp_miss_min, hp_miss_mid, hp_miss_max);
} }
protected override void ApplyResult(JudgementResult result) protected override void ApplyResult(JudgementResult result)
{ {
base.ApplyResult(result); base.ApplyResult(result);
bool isTick = result.Judgement is TaikoDrumRollTickJudgement; double hpIncrease = result.Judgement.HealthIncreaseFor(result);
// Apply HP changes if (result.Type == HitResult.Miss)
switch (result.Type) hpIncrease *= hpMissMultiplier;
{ else
case HitResult.Miss: hpIncrease *= hpMultiplier;
// Missing ticks shouldn't drop HP
if (!isTick) Health.Value += hpIncrease;
Health.Value += hpIncreaseMiss;
break;
case HitResult.Good:
Health.Value += hpIncreaseGood;
break;
case HitResult.Great:
if (isTick)
Health.Value += hpIncreaseTick;
else
Health.Value += hpIncreaseGreat;
break;
}
} }
protected override void Reset(bool storeResults) protected override void Reset(bool storeResults)

View File

@ -55,7 +55,7 @@ namespace osu.Game.Tests.Visual
}; };
}); });
//[Test] [Test]
public void TestInstantPolling() public void TestInstantPolling()
{ {
createPoller(true); createPoller(true);
@ -81,6 +81,7 @@ namespace osu.Game.Tests.Visual
} }
[Test] [Test]
[Ignore("i have no idea how to fix the timing of this one")]
public void TestSlowPolling() public void TestSlowPolling()
{ {
createPoller(false); createPoller(false);
@ -91,8 +92,6 @@ namespace osu.Game.Tests.Visual
checkCount(0); checkCount(0);
skip(); skip();
skip(); skip();
skip();
skip();
checkCount(0); checkCount(0);
skip(); skip();
skip(); skip();

View File

@ -0,0 +1,102 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Game.Online.Chat;
using osu.Game.Users;
using osuTK;
namespace osu.Game.Tests.Visual
{
public class TestCaseStandAloneChatDisplay : OsuTestCase
{
private readonly Channel testChannel = new Channel();
private readonly User admin = new User
{
Username = "HappyStick",
Id = 2,
Colour = "f2ca34"
};
private readonly User redUser = new User
{
Username = "BanchoBot",
Id = 3,
};
private readonly User blueUser = new User
{
Username = "Zallius",
Id = 4,
};
[Cached]
private ChannelManager channelManager = new ChannelManager();
private readonly StandAloneChatDisplay chatDisplay;
private readonly StandAloneChatDisplay chatDisplay2;
public TestCaseStandAloneChatDisplay()
{
Add(channelManager);
Add(chatDisplay = new StandAloneChatDisplay
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
Margin = new MarginPadding(20),
Size = new Vector2(400, 80)
});
Add(chatDisplay2 = new StandAloneChatDisplay(true)
{
Anchor = Anchor.CentreRight,
Origin = Anchor.CentreRight,
Margin = new MarginPadding(20),
Size = new Vector2(400, 150)
});
}
protected override void LoadComplete()
{
base.LoadComplete();
channelManager.CurrentChannel.Value = testChannel;
chatDisplay.Channel.Value = testChannel;
chatDisplay2.Channel.Value = testChannel;
AddStep("message from admin", () => testChannel.AddLocalEcho(new LocalEchoMessage
{
Sender = admin,
Content = "I am a wang!"
}));
AddStep("message from team red", () => testChannel.AddLocalEcho(new LocalEchoMessage
{
Sender = redUser,
Content = "I am team red."
}));
AddStep("message from team red", () => testChannel.AddLocalEcho(new LocalEchoMessage
{
Sender = redUser,
Content = "I plan to win!"
}));
AddStep("message from team blue", () => testChannel.AddLocalEcho(new LocalEchoMessage
{
Sender = blueUser,
Content = "Not on my watch. Prepare to eat saaaaaaaaaand. Lots and lots of saaaaaaand."
}));
AddStep("message from admin", () => testChannel.AddLocalEcho(new LocalEchoMessage
{
Sender = admin,
Content = "Okay okay, calm down guys. Let's do this!"
}));
}
}
}

View File

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

View File

@ -149,8 +149,10 @@ namespace osu.Game.Database
try try
{ {
notification.Text = $"Importing ({++current} of {paths.Length})\n{Path.GetFileName(path)}"; notification.Text = $"Importing ({++current} of {paths.Length})\n{Path.GetFileName(path)}";
TModel import;
using (ArchiveReader reader = getReaderFrom(path)) using (ArchiveReader reader = getReaderFrom(path))
imported.Add(Import(reader)); imported.Add(import = Import(reader));
notification.Progress = (float)current / paths.Length; notification.Progress = (float)current / paths.Length;
@ -160,7 +162,7 @@ namespace osu.Game.Database
// TODO: Add a check to prevent files from storage to be deleted. // TODO: Add a check to prevent files from storage to be deleted.
try try
{ {
if (File.Exists(path)) if (import != null && File.Exists(path))
File.Delete(path); File.Delete(path);
} }
catch (Exception e) catch (Exception e)

View File

@ -102,7 +102,7 @@ namespace osu.Game.Online.API
if (queue.Count == 0) if (queue.Count == 0)
{ {
log.Add(@"Queueing a ping request"); log.Add(@"Queueing a ping request");
Queue(new ListChannelsRequest { Timeout = 5000 }); Queue(new GetUserRequest());
} }
break; break;
@ -173,7 +173,6 @@ namespace osu.Game.Online.API
req = queue.Dequeue(); req = queue.Dequeue();
} }
// TODO: handle failures better
handleRequest(req); handleRequest(req);
} }
@ -191,64 +190,30 @@ namespace osu.Game.Online.API
/// <summary> /// <summary>
/// Handle a single API request. /// Handle a single API request.
/// Ensures all exceptions are caught and dealt with correctly.
/// </summary> /// </summary>
/// <param name="req">The request.</param> /// <param name="req">The request.</param>
/// <returns>true if we should remove this request from the queue.</returns> /// <returns>true if the request succeeded.</returns>
private bool handleRequest(APIRequest req) private bool handleRequest(APIRequest req)
{ {
try try
{ {
Logger.Log($@"Performing request {req}", LoggingTarget.Network);
req.Perform(this); req.Perform(this);
//we could still be in initialisation, at which point we don't want to say we're Online yet. //we could still be in initialisation, at which point we don't want to say we're Online yet.
if (IsLoggedIn) if (IsLoggedIn) State = APIState.Online;
State = APIState.Online;
failureCount = 0; failureCount = 0;
return true; return true;
} }
catch (WebException we) catch (WebException we)
{ {
HttpStatusCode statusCode = (we.Response as HttpWebResponse)?.StatusCode handleWebException(we);
?? (we.Status == WebExceptionStatus.UnknownError ? HttpStatusCode.NotAcceptable : HttpStatusCode.RequestTimeout); return false;
// special cases for un-typed but useful message responses.
switch (we.Message)
{
case "Unauthorized":
statusCode = HttpStatusCode.Unauthorized;
break;
}
switch (statusCode)
{
case HttpStatusCode.Unauthorized:
Logout(false);
return true;
case HttpStatusCode.RequestTimeout:
failureCount++;
log.Add($@"API failure count is now {failureCount}");
if (failureCount < 3)
//we might try again at an api level.
return false;
State = APIState.Failing;
flushQueue();
return true;
}
req.Fail(we);
return true;
} }
catch (Exception e) catch (Exception e)
{ {
if (e is TimeoutException) return false;
log.Add(@"API level timeout exception was hit");
req.Fail(e);
return true;
} }
} }
@ -276,6 +241,45 @@ namespace osu.Game.Online.API
} }
} }
private bool handleWebException(WebException we)
{
HttpStatusCode statusCode = (we.Response as HttpWebResponse)?.StatusCode
?? (we.Status == WebExceptionStatus.UnknownError ? HttpStatusCode.NotAcceptable : HttpStatusCode.RequestTimeout);
// special cases for un-typed but useful message responses.
switch (we.Message)
{
case "Unauthorized":
case "Forbidden":
statusCode = HttpStatusCode.Unauthorized;
break;
}
switch (statusCode)
{
case HttpStatusCode.Unauthorized:
Logout(false);
return true;
case HttpStatusCode.RequestTimeout:
failureCount++;
log.Add($@"API failure count is now {failureCount}");
if (failureCount < 3)
//we might try again at an api level.
return false;
if (State == APIState.Online)
{
State = APIState.Failing;
flushQueue();
}
return true;
}
return true;
}
public bool IsLoggedIn => LocalUser.Value.Id > 1; public bool IsLoggedIn => LocalUser.Value.Id > 1;
public void Queue(APIRequest request) public void Queue(APIRequest request)

View File

@ -3,6 +3,7 @@
using System; using System;
using osu.Framework.IO.Network; using osu.Framework.IO.Network;
using osu.Framework.Logging;
namespace osu.Game.Online.API namespace osu.Game.Online.API
{ {
@ -35,23 +36,12 @@ namespace osu.Game.Online.API
/// </summary> /// </summary>
public abstract class APIRequest public abstract class APIRequest
{ {
/// <summary>
/// The maximum amount of time before this request will fail.
/// </summary>
public int Timeout = WebRequest.DEFAULT_TIMEOUT;
protected abstract string Target { get; } protected abstract string Target { get; }
protected virtual WebRequest CreateWebRequest() => new WebRequest(Uri); protected virtual WebRequest CreateWebRequest() => new WebRequest(Uri);
protected virtual string Uri => $@"{API.Endpoint}/api/v2/{Target}"; protected virtual string Uri => $@"{API.Endpoint}/api/v2/{Target}";
private double remainingTime => Math.Max(0, Timeout - (DateTimeOffset.UtcNow - (startTime ?? DateTimeOffset.MinValue)).TotalMilliseconds);
public bool ExceededTimeout => remainingTime == 0;
private DateTimeOffset? startTime;
protected APIAccess API; protected APIAccess API;
protected WebRequest WebRequest; protected WebRequest WebRequest;
@ -75,27 +65,24 @@ namespace osu.Game.Online.API
{ {
API = api; API = api;
if (checkAndProcessFailure()) if (checkAndScheduleFailure())
return; return;
if (startTime == null)
startTime = DateTimeOffset.UtcNow;
if (remainingTime <= 0)
throw new TimeoutException(@"API request timeout hit");
WebRequest = CreateWebRequest(); WebRequest = CreateWebRequest();
WebRequest.Failed += Fail; WebRequest.Failed += Fail;
WebRequest.AllowRetryOnTimeout = false; WebRequest.AllowRetryOnTimeout = false;
WebRequest.AddHeader("Authorization", $"Bearer {api.AccessToken}"); WebRequest.AddHeader("Authorization", $"Bearer {api.AccessToken}");
if (checkAndProcessFailure()) if (checkAndScheduleFailure())
return; return;
if (!WebRequest.Aborted) //could have been aborted by a Cancel() call if (!WebRequest.Aborted) //could have been aborted by a Cancel() call
{
Logger.Log($@"Performing request {this}", LoggingTarget.Network);
WebRequest.Perform(); WebRequest.Perform();
}
if (checkAndProcessFailure()) if (checkAndScheduleFailure())
return; return;
api.Schedule(delegate { Success?.Invoke(); }); api.Schedule(delegate { Success?.Invoke(); });
@ -105,19 +92,21 @@ namespace osu.Game.Online.API
public void Fail(Exception e) public void Fail(Exception e)
{ {
cancelled = true; if (cancelled) return;
cancelled = true;
WebRequest?.Abort(); WebRequest?.Abort();
Logger.Log($@"Failing request {this} ({e})", LoggingTarget.Network);
pendingFailure = () => Failure?.Invoke(e); pendingFailure = () => Failure?.Invoke(e);
checkAndProcessFailure(); checkAndScheduleFailure();
} }
/// <summary> /// <summary>
/// Checked for cancellation or error. Also queues up the Failed event if we can. /// Checked for cancellation or error. Also queues up the Failed event if we can.
/// </summary> /// </summary>
/// <returns>Whether we are in a failed or cancelled state.</returns> /// <returns>Whether we are in a failed or cancelled state.</returns>
private bool checkAndProcessFailure() private bool checkAndScheduleFailure()
{ {
if (API == null || pendingFailure == null) return cancelled; if (API == null || pendingFailure == null) return cancelled;

View File

@ -96,12 +96,14 @@ namespace osu.Game.Online.Chat
/// </summary> /// </summary>
/// <param name="text">The message text that is going to be posted</param> /// <param name="text">The message text that is going to be posted</param>
/// <param name="isAction">Is true if the message is an action, e.g.: user is currently eating </param> /// <param name="isAction">Is true if the message is an action, e.g.: user is currently eating </param>
public void PostMessage(string text, bool isAction = false) /// <param name="target">An optional target channel. If null, <see cref="CurrentChannel"/> will be used.</param>
public void PostMessage(string text, bool isAction = false, Channel target = null)
{ {
if (CurrentChannel.Value == null) if (target == null)
return; target = CurrentChannel.Value;
var currentChannel = CurrentChannel.Value; if (target == null)
return;
void dequeueAndRun() void dequeueAndRun()
{ {
@ -113,7 +115,7 @@ namespace osu.Game.Online.Chat
{ {
if (!api.IsLoggedIn) if (!api.IsLoggedIn)
{ {
currentChannel.AddNewMessages(new ErrorMessage("Please sign in to participate in chat!")); target.AddNewMessages(new ErrorMessage("Please sign in to participate in chat!"));
return; return;
} }
@ -121,29 +123,29 @@ namespace osu.Game.Online.Chat
{ {
Sender = api.LocalUser.Value, Sender = api.LocalUser.Value,
Timestamp = DateTimeOffset.Now, Timestamp = DateTimeOffset.Now,
ChannelId = CurrentChannel.Value.Id, ChannelId = target.Id,
IsAction = isAction, IsAction = isAction,
Content = text Content = text
}; };
currentChannel.AddLocalEcho(message); target.AddLocalEcho(message);
// if this is a PM and the first message, we need to do a special request to create the PM channel // if this is a PM and the first message, we need to do a special request to create the PM channel
if (currentChannel.Type == ChannelType.PM && !currentChannel.Joined) if (target.Type == ChannelType.PM && !target.Joined)
{ {
var createNewPrivateMessageRequest = new CreateNewPrivateMessageRequest(currentChannel.Users.First(), message); var createNewPrivateMessageRequest = new CreateNewPrivateMessageRequest(target.Users.First(), message);
createNewPrivateMessageRequest.Success += createRes => createNewPrivateMessageRequest.Success += createRes =>
{ {
currentChannel.Id = createRes.ChannelID; target.Id = createRes.ChannelID;
currentChannel.ReplaceMessage(message, createRes.Message); target.ReplaceMessage(message, createRes.Message);
dequeueAndRun(); dequeueAndRun();
}; };
createNewPrivateMessageRequest.Failure += exception => createNewPrivateMessageRequest.Failure += exception =>
{ {
Logger.Error(exception, "Posting message failed."); Logger.Error(exception, "Posting message failed.");
currentChannel.ReplaceMessage(message, null); target.ReplaceMessage(message, null);
dequeueAndRun(); dequeueAndRun();
}; };
@ -155,14 +157,14 @@ namespace osu.Game.Online.Chat
req.Success += m => req.Success += m =>
{ {
currentChannel.ReplaceMessage(message, m); target.ReplaceMessage(message, m);
dequeueAndRun(); dequeueAndRun();
}; };
req.Failure += exception => req.Failure += exception =>
{ {
Logger.Error(exception, "Posting message failed."); Logger.Error(exception, "Posting message failed.");
currentChannel.ReplaceMessage(message, null); target.ReplaceMessage(message, null);
dequeueAndRun(); dequeueAndRun();
}; };
@ -178,9 +180,13 @@ namespace osu.Game.Online.Chat
/// Posts a command locally. Commands like /help will result in a help message written in the current channel. /// Posts a command locally. Commands like /help will result in a help message written in the current channel.
/// </summary> /// </summary>
/// <param name="text">the text containing the command identifier and command parameters.</param> /// <param name="text">the text containing the command identifier and command parameters.</param>
public void PostCommand(string text) /// <param name="target">An optional target channel. If null, <see cref="CurrentChannel"/> will be used.</param>
public void PostCommand(string text, Channel target = null)
{ {
if (CurrentChannel.Value == null) if (target == null)
target = CurrentChannel.Value;
if (target == null)
return; return;
var parameters = text.Split(new[] { ' ' }, 2); var parameters = text.Split(new[] { ' ' }, 2);
@ -192,7 +198,7 @@ namespace osu.Game.Online.Chat
case "me": case "me":
if (string.IsNullOrWhiteSpace(content)) if (string.IsNullOrWhiteSpace(content))
{ {
CurrentChannel.Value.AddNewMessages(new ErrorMessage("Usage: /me [action]")); target.AddNewMessages(new ErrorMessage("Usage: /me [action]"));
break; break;
} }
@ -200,11 +206,11 @@ namespace osu.Game.Online.Chat
break; break;
case "help": case "help":
CurrentChannel.Value.AddNewMessages(new InfoMessage("Supported commands: /help, /me [action]")); target.AddNewMessages(new InfoMessage("Supported commands: /help, /me [action]"));
break; break;
default: default:
CurrentChannel.Value.AddNewMessages(new ErrorMessage($@"""/{command}"" is not supported! For a list of supported commands see /help")); target.AddNewMessages(new ErrorMessage($@"""/{command}"" is not supported! For a list of supported commands see /help"));
break; break;
} }
} }

View File

@ -0,0 +1,227 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Configuration;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.UserInterface;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Online.Chat
{
/// <summary>
/// Display a chat channel in an insolated region.
/// </summary>
public class StandAloneChatDisplay : CompositeDrawable
{
public readonly Bindable<Channel> Channel = new Bindable<Channel>();
private readonly FillFlowContainer messagesFlow;
private Channel lastChannel;
private readonly FocusedTextBox textbox;
protected ChannelManager ChannelManager;
/// <summary>
/// Construct a new instance.
/// </summary>
/// <param name="postingTextbox">Whether a textbox for posting new messages should be displayed.</param>
public StandAloneChatDisplay(bool postingTextbox = false)
{
CornerRadius = 10;
Masking = true;
InternalChildren = new Drawable[]
{
new Box
{
Colour = Color4.Black,
Alpha = 0.8f,
RelativeSizeAxes = Axes.Both
},
messagesFlow = new FillFlowContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
LayoutEasing = Easing.Out,
LayoutDuration = 500,
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
Direction = FillDirection.Vertical
}
};
const float textbox_height = 30;
if (postingTextbox)
{
messagesFlow.Y -= textbox_height;
AddInternal(textbox = new FocusedTextBox
{
RelativeSizeAxes = Axes.X,
Height = textbox_height,
PlaceholderText = "type your message",
OnCommit = postMessage,
ReleaseFocusOnCommit = false,
HoldFocus = true,
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
});
}
Channel.BindValueChanged(channelChanged);
}
[BackgroundDependencyLoader(true)]
private void load(ChannelManager manager)
{
if (ChannelManager == null)
ChannelManager = manager;
}
private void postMessage(TextBox sender, bool newtext)
{
var text = textbox.Text.Trim();
if (string.IsNullOrWhiteSpace(text))
return;
if (text[0] == '/')
ChannelManager?.PostCommand(text.Substring(1));
else
ChannelManager?.PostMessage(text);
textbox.Text = string.Empty;
}
public void Contract()
{
this.FadeIn(300);
this.MoveToY(0, 500, Easing.OutQuint);
}
public void Expand()
{
this.FadeOut(200);
this.MoveToY(100, 500, Easing.In);
}
protected virtual Drawable CreateMessage(Message message)
{
return new StandAloneMessage(message);
}
private void channelChanged(Channel channel)
{
if (lastChannel != null)
lastChannel.NewMessagesArrived -= newMessages;
lastChannel = channel;
messagesFlow.Clear();
if (channel == null) return;
channel.NewMessagesArrived += newMessages;
}
private void newMessages(IEnumerable<Message> messages)
{
var excessChildren = messagesFlow.Children.Count - 10;
if (excessChildren > 0)
foreach (var c in messagesFlow.Children.Take(excessChildren))
c.Expire();
foreach (var message in messages)
{
var formatted = MessageFormatter.FormatMessage(message);
var drawable = CreateMessage(formatted);
drawable.Y = messagesFlow.Height;
messagesFlow.Add(drawable);
}
}
protected class StandAloneMessage : CompositeDrawable
{
protected readonly Message Message;
protected OsuSpriteText SenderText;
protected Circle ColourBox;
public StandAloneMessage(Message message)
{
Message = message;
}
[BackgroundDependencyLoader]
private void load()
{
Margin = new MarginPadding(3);
RelativeSizeAxes = Axes.X;
AutoSizeAxes = Axes.Y;
InternalChildren = new Drawable[]
{
new FillFlowContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Direction = FillDirection.Horizontal,
Children = new Drawable[]
{
new Container
{
RelativeSizeAxes = Axes.X,
Width = 0.2f,
Children = new Drawable[]
{
SenderText = new OsuSpriteText
{
Font = @"Exo2.0-Bold",
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
Text = Message.Sender.ToString()
}
}
},
new Container
{
Size = new Vector2(8, OsuSpriteText.FONT_SIZE),
Margin = new MarginPadding { Horizontal = 3 },
Children = new Drawable[]
{
ColourBox = new Circle
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = new Vector2(8)
}
}
},
new OsuTextFlowContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Width = 0.5f,
Text = Message.DisplayContent
}
}
}
};
if (!string.IsNullOrEmpty(Message.Sender.Colour))
SenderText.Colour = ColourBox.Colour = OsuColour.FromHex(Message.Sender.Colour);
}
}
}
}

View File

@ -22,7 +22,8 @@ namespace osu.Game.Online
private double timeBetweenPolls; private double timeBetweenPolls;
/// <summary> /// <summary>
/// The time that should be waited between polls. /// The time in milliseconds to wait between polls.
/// Setting to zero stops all polling.
/// </summary> /// </summary>
public double TimeBetweenPolls public double TimeBetweenPolls
{ {
@ -35,6 +36,15 @@ namespace osu.Game.Online
} }
} }
/// <summary>
///
/// </summary>
/// <param name="timeBetweenPolls">The initial time in milliseconds to wait between polls. Setting to zero stops al polling.</param>
protected PollingComponent(double timeBetweenPolls = 0)
{
TimeBetweenPolls = timeBetweenPolls;
}
protected override void LoadComplete() protected override void LoadComplete()
{ {
base.LoadComplete(); base.LoadComplete();

View File

@ -20,7 +20,7 @@ using osu.Game.Graphics.Containers;
namespace osu.Game.Overlays.Chat.Selection namespace osu.Game.Overlays.Chat.Selection
{ {
public class ChannelSelectionOverlay : OsuFocusedOverlayContainer public class ChannelSelectionOverlay : WaveOverlayContainer
{ {
public static readonly float WIDTH_PADDING = 170; public static readonly float WIDTH_PADDING = 170;
@ -39,6 +39,11 @@ namespace osu.Game.Overlays.Chat.Selection
{ {
RelativeSizeAxes = Axes.X; RelativeSizeAxes = Axes.X;
Waves.FirstWaveColour = OsuColour.FromHex("353535");
Waves.SecondWaveColour = OsuColour.FromHex("434343");
Waves.ThirdWaveColour = OsuColour.FromHex("515151");
Waves.FourthWaveColour = OsuColour.FromHex("595959");
Children = new Drawable[] Children = new Drawable[]
{ {
new Container new Container

View File

@ -44,5 +44,19 @@ namespace osu.Game.Rulesets.Judgements
/// <param name="result">The <see cref="JudgementResult"/> to find the numeric score representation for.</param> /// <param name="result">The <see cref="JudgementResult"/> to find the numeric score representation for.</param>
/// <returns>The numeric score representation of <paramref name="result"/>.</returns> /// <returns>The numeric score representation of <paramref name="result"/>.</returns>
public int NumericResultFor(JudgementResult result) => NumericResultFor(result.Type); public int NumericResultFor(JudgementResult result) => NumericResultFor(result.Type);
/// <summary>
/// Retrieves the numeric health increase of a <see cref="HitResult"/>.
/// </summary>
/// <param name="result">The <see cref="HitResult"/> to find the numeric health increase for.</param>
/// <returns>The numeric health increase of <paramref name="result"/>.</returns>
protected virtual double HealthIncreaseFor(HitResult result) => 0;
/// <summary>
/// Retrieves the numeric health increase of a <see cref="JudgementResult"/>.
/// </summary>
/// <param name="result">The <see cref="JudgementResult"/> to find the numeric health increase for.</param>
/// <returns>The numeric health increase of <paramref name="result"/>.</returns>
public double HealthIncreaseFor(JudgementResult result) => HealthIncreaseFor(result.Type);
} }
} }

View File

@ -22,7 +22,6 @@ namespace osu.Game.Rulesets.Objects
/// <summary> /// <summary>
/// Hit window for a <see cref="HitResult.Perfect"/> result. /// Hit window for a <see cref="HitResult.Perfect"/> result.
/// The user can only achieve receive this result if <see cref="AllowsPerfect"/> is true.
/// </summary> /// </summary>
public double Perfect { get; protected set; } public double Perfect { get; protected set; }
@ -38,7 +37,6 @@ namespace osu.Game.Rulesets.Objects
/// <summary> /// <summary>
/// Hit window for an <see cref="HitResult.Ok"/> result. /// Hit window for an <see cref="HitResult.Ok"/> result.
/// The user can only achieve this result if <see cref="AllowsOk"/> is true.
/// </summary> /// </summary>
public double Ok { get; protected set; } public double Ok { get; protected set; }
@ -53,14 +51,36 @@ namespace osu.Game.Rulesets.Objects
public double Miss { get; protected set; } public double Miss { get; protected set; }
/// <summary> /// <summary>
/// Whether it's possible to achieve a <see cref="HitResult.Perfect"/> result. /// Retrieves the <see cref="HitResult"/> with the largest hit window that produces a successful hit.
/// </summary> /// </summary>
public bool AllowsPerfect; /// <returns>The lowest allowed successful <see cref="HitResult"/>.</returns>
protected HitResult LowestSuccessfulHitResult()
{
for (var result = HitResult.Meh; result <= HitResult.Perfect; ++result)
{
if (IsHitResultAllowed(result))
return result;
}
return HitResult.None;
}
/// <summary> /// <summary>
/// Whether it's possible to achieve a <see cref="HitResult.Ok"/> result. /// Check whether it is possible to achieve the provided <see cref="HitResult"/>.
/// </summary> /// </summary>
public bool AllowsOk; /// <param name="result">The result type to check.</param>
/// <returns>Whether the <see cref="HitResult"/> can be achieved.</returns>
public virtual bool IsHitResultAllowed(HitResult result)
{
switch (result)
{
case HitResult.Perfect:
case HitResult.Ok:
return false;
default:
return true;
}
}
/// <summary> /// <summary>
/// Sets hit windows with values that correspond to a difficulty parameter. /// Sets hit windows with values that correspond to a difficulty parameter.
@ -85,18 +105,11 @@ namespace osu.Game.Rulesets.Objects
{ {
timeOffset = Math.Abs(timeOffset); timeOffset = Math.Abs(timeOffset);
if (AllowsPerfect && timeOffset <= HalfWindowFor(HitResult.Perfect)) for (var result = HitResult.Perfect; result >= HitResult.Miss; --result)
return HitResult.Perfect; {
if (timeOffset <= HalfWindowFor(HitResult.Great)) if (IsHitResultAllowed(result) && timeOffset <= HalfWindowFor(result))
return HitResult.Great; return result;
if (timeOffset <= HalfWindowFor(HitResult.Good)) }
return HitResult.Good;
if (AllowsOk && timeOffset <= HalfWindowFor(HitResult.Ok))
return HitResult.Ok;
if (timeOffset <= HalfWindowFor(HitResult.Meh))
return HitResult.Meh;
if (timeOffset <= HalfWindowFor(HitResult.Miss))
return HitResult.Miss;
return HitResult.None; return HitResult.None;
} }
@ -130,10 +143,10 @@ namespace osu.Game.Rulesets.Objects
/// <summary> /// <summary>
/// Given a time offset, whether the <see cref="HitObject"/> can ever be hit in the future with a non-<see cref="HitResult.Miss"/> result. /// Given a time offset, whether the <see cref="HitObject"/> can ever be hit in the future with a non-<see cref="HitResult.Miss"/> result.
/// This happens if <paramref name="timeOffset"/> is less than what is required for a <see cref="Meh"/> result. /// This happens if <paramref name="timeOffset"/> is less than what is required for a <see cref="SuccessfulHitWindow"/> result.
/// </summary> /// </summary>
/// <param name="timeOffset">The time offset.</param> /// <param name="timeOffset">The time offset.</param>
/// <returns>Whether the <see cref="HitObject"/> can be hit at any point in the future from this time offset.</returns> /// <returns>Whether the <see cref="HitObject"/> can be hit at any point in the future from this time offset.</returns>
public bool CanBeHit(double timeOffset) => timeOffset <= HalfWindowFor(HitResult.Meh); public bool CanBeHit(double timeOffset) => timeOffset <= HalfWindowFor(LowestSuccessfulHitResult());
} }
} }

View File

@ -68,6 +68,11 @@ namespace osu.Game.Rulesets.UI
/// </summary> /// </summary>
public Playfield Playfield => playfield.Value; public Playfield Playfield => playfield.Value;
/// <summary>
/// Place to put drawables above hit objects but below UI.
/// </summary>
public Container Overlays { get; protected set; }
/// <summary> /// <summary>
/// The cursor provided by this <see cref="RulesetContainer"/>. May be null if no cursor is provided. /// The cursor provided by this <see cref="RulesetContainer"/>. May be null if no cursor is provided.
/// </summary> /// </summary>
@ -215,7 +220,6 @@ namespace osu.Game.Rulesets.UI
protected override Container<Drawable> Content => content; protected override Container<Drawable> Content => content;
private Container content; private Container content;
private IEnumerable<Mod> mods;
/// <summary> /// <summary>
/// Whether to assume the beatmap passed into this <see cref="RulesetContainer{TObject}"/> is for the current ruleset. /// Whether to assume the beatmap passed into this <see cref="RulesetContainer{TObject}"/> is for the current ruleset.
@ -245,17 +249,24 @@ namespace osu.Game.Rulesets.UI
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(OsuConfigManager config) private void load(OsuConfigManager config)
{ {
KeyBindingInputManager.Add(content = new Container KeyBindingInputManager.Children = new Drawable[]
{ {
RelativeSizeAxes = Axes.Both, content = new Container
}); {
RelativeSizeAxes = Axes.Both,
AddInternal(KeyBindingInputManager); },
KeyBindingInputManager.Add(Playfield); Playfield
};
if (Cursor != null) if (Cursor != null)
KeyBindingInputManager.Add(Cursor); KeyBindingInputManager.Add(Cursor);
InternalChildren = new Drawable[]
{
KeyBindingInputManager,
Overlays = new Container { RelativeSizeAxes = Axes.Both }
};
// Apply mods // Apply mods
applyRulesetMods(Mods, config); applyRulesetMods(Mods, config);
@ -330,7 +341,6 @@ namespace osu.Game.Rulesets.UI
Playfield.Add(drawableObject); Playfield.Add(drawableObject);
} }
/// <summary> /// <summary>
/// Creates a DrawableHitObject from a HitObject. /// Creates a DrawableHitObject from a HitObject.
/// </summary> /// </summary>

View File

@ -170,7 +170,7 @@ namespace osu.Game.Screens.Play
{ {
Retries = RestartCount, Retries = RestartCount,
OnRetry = Restart, OnRetry = Restart,
OnQuit = Exit, OnQuit = performUserRequestedExit,
CheckCanPause = () => AllowPause && ValidForResume && !HasFailed && !RulesetContainer.HasReplayLoaded, CheckCanPause = () => AllowPause && ValidForResume && !HasFailed && !RulesetContainer.HasReplayLoaded,
Children = new[] Children = new[]
{ {
@ -211,7 +211,7 @@ namespace osu.Game.Screens.Play
failOverlay = new FailOverlay failOverlay = new FailOverlay
{ {
OnRetry = Restart, OnRetry = Restart,
OnQuit = Exit, OnQuit = performUserRequestedExit,
}, },
new HotkeyRetryOverlay new HotkeyRetryOverlay
{ {
@ -225,7 +225,7 @@ namespace osu.Game.Screens.Play
} }
}; };
hudOverlay.HoldToQuit.Action = Exit; hudOverlay.HoldToQuit.Action = performUserRequestedExit;
hudOverlay.KeyCounter.Visible.BindTo(RulesetContainer.HasReplayLoaded); hudOverlay.KeyCounter.Visible.BindTo(RulesetContainer.HasReplayLoaded);
RulesetContainer.IsPaused.BindTo(pauseContainer.IsPaused); RulesetContainer.IsPaused.BindTo(pauseContainer.IsPaused);
@ -250,8 +250,16 @@ namespace osu.Game.Screens.Play
mod.ApplyToClock(sourceClock); mod.ApplyToClock(sourceClock);
} }
private void performUserRequestedExit()
{
if (!IsCurrentScreen) return;
Exit();
}
public void Restart() public void Restart()
{ {
if (!IsCurrentScreen) return;
sampleRestart?.Play(); sampleRestart?.Play();
ValidForResume = false; ValidForResume = false;
RestartRequested?.Invoke(); RestartRequested?.Invoke();

View File

@ -16,8 +16,8 @@ namespace osu.Game.Skinning
{ {
public event Action SourceChanged; public event Action SourceChanged;
private Bindable<bool> beatmapSkins = new Bindable<bool>(); private readonly Bindable<bool> beatmapSkins = new Bindable<bool>();
private Bindable<bool> beatmapHitsounds = new Bindable<bool>(); private readonly Bindable<bool> beatmapHitsounds = new Bindable<bool>();
public Drawable GetDrawableComponent(string componentName) public Drawable GetDrawableComponent(string componentName)
{ {
@ -84,11 +84,8 @@ namespace osu.Game.Skinning
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(OsuConfigManager config) private void load(OsuConfigManager config)
{ {
beatmapSkins = config.GetBindable<bool>(OsuSetting.BeatmapSkins); config.BindWith(OsuSetting.BeatmapSkins, beatmapSkins);
beatmapSkins.BindValueChanged(_ => onSourceChanged()); config.BindWith(OsuSetting.BeatmapHitsounds, beatmapHitsounds);
beatmapHitsounds = config.GetBindable<bool>(OsuSetting.BeatmapHitsounds);
beatmapHitsounds.BindValueChanged(_ => onSourceChanged(), true);
} }
protected override void LoadComplete() protected override void LoadComplete()
@ -97,6 +94,9 @@ namespace osu.Game.Skinning
if (fallbackSource != null) if (fallbackSource != null)
fallbackSource.SourceChanged += onSourceChanged; fallbackSource.SourceChanged += onSourceChanged;
beatmapSkins.BindValueChanged(_ => onSourceChanged());
beatmapHitsounds.BindValueChanged(_ => onSourceChanged(), true);
} }
protected override void Dispose(bool isDisposing) protected override void Dispose(bool isDisposing)

View File

@ -18,7 +18,7 @@
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.0" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.0" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.1" /> <PackageReference Include="Newtonsoft.Json" Version="12.0.1" />
<PackageReference Include="ppy.osu.Framework" Version="0.0.7654" /> <PackageReference Include="ppy.osu.Framework" Version="2018.1219.0" />
<PackageReference Include="SharpCompress" Version="0.22.0" /> <PackageReference Include="SharpCompress" Version="0.22.0" />
<PackageReference Include="NUnit" Version="3.11.0" /> <PackageReference Include="NUnit" Version="3.11.0" />
<PackageReference Include="SharpRaven" Version="2.4.0" /> <PackageReference Include="SharpRaven" Version="2.4.0" />