diff --git a/.github/ISSUE_TEMPLATE/bug-issues.md b/.github/ISSUE_TEMPLATE/bug-issues.md new file mode 100644 index 0000000000..8d85c92fec --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug-issues.md @@ -0,0 +1,14 @@ +--- +name: Bug Report +about: For issues regarding encountered game bugs +--- + + + +**Describe your problem:** + +**Screenshots or videos showing encountered issue:** + +**osu!lazer version:** + +**Logs:** \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/crash-issues.md b/.github/ISSUE_TEMPLATE/crash-issues.md new file mode 100644 index 0000000000..849f042c1f --- /dev/null +++ b/.github/ISSUE_TEMPLATE/crash-issues.md @@ -0,0 +1,16 @@ +--- +name: Crash Report +about: For issues regarding game crashes or permanent freezes +--- + + + +**Describe your problem:** + +**Screenshots or videos showing encountered issue:** + +**osu!lazer version:** + +**Logs:** + +**Computer Specifications:** \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/feature-request-issues.md b/.github/ISSUE_TEMPLATE/feature-request-issues.md new file mode 100644 index 0000000000..73c4f37a3e --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature-request-issues.md @@ -0,0 +1,10 @@ +--- +name: Feature Request +about: Let us know what you would like to see in the game! +--- + + + +**Describe the feature:** + +**Proposal designs of the feature:** diff --git a/.github/ISSUE_TEMPLATE/missing-for-live-issues.md b/.github/ISSUE_TEMPLATE/missing-for-live-issues.md new file mode 100644 index 0000000000..ae3cf20a8c --- /dev/null +++ b/.github/ISSUE_TEMPLATE/missing-for-live-issues.md @@ -0,0 +1,10 @@ +--- +name: Missing for Live +about: Let us know the features you need which are available in osu-stable but not lazer +--- + + + +**Describe the feature:** + +**Designs:** diff --git a/.gitignore b/.gitignore index 5138e940ed..f95a04e517 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,10 @@ # User-specific files (MonoDevelop/Xamarin Studio) *.userprefs +### Cake ### +tools/* +!tools/cakebuild.csproj + # Build results bin/[Dd]ebug/ [Dd]ebugPublic/ @@ -98,6 +102,7 @@ $tf/ _ReSharper*/ *.[Rr]e[Ss]harper *.DotSettings.user +inspectcode # JustCode is a .NET coding add-in .JustCode @@ -247,7 +252,11 @@ paket-files/ .fake/ # JetBrains Rider -.idea/ +.idea/.idea.osu/.idea/*.xml +.idea/.idea.osu/.idea/codeStyles/*.xml +.idea/.idea.osu/.idea/dataSources/*.xml +.idea/.idea.osu/.idea/dictionaries/*.xml +.idea/.idea.osu/*.iml *.sln.iml # CodeRush @@ -257,3 +266,5 @@ paket-files/ __pycache__/ *.pyc Staging/ + +inspectcodereport.xml diff --git a/README.md b/README.md index dc36145337..baaba22726 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,7 @@ Build and run - Using Visual Studio 2017, Rider or Visual Studio Code (configurations are included) - From command line using `dotnet run --project osu.Desktop`. When building for non-development purposes, add `-c Release` to gain higher performance. +- To run with code analysis, instead use `powershell ./build.ps1` or `build.sh`. This is currently only supported under windows due to [resharper cli shortcomings](https://youtrack.jetbrains.com/issue/RSRP-410004). Alternative, you can install resharper or use rider to get inline support in your IDE of choice. Note: If you run from command line under linux, you will need to prefix the output folder to your `LD_LIBRARY_PATH`. See `.vscode/launch.json` for an example diff --git a/appveyor.yml b/appveyor.yml index 8c06b42fd2..1f485485da 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,22 +1,8 @@ clone_depth: 1 version: '{branch}-{build}' image: Visual Studio 2017 -configuration: Debug -cache: - - C:\ProgramData\chocolatey\bin -> appveyor.yml - - C:\ProgramData\chocolatey\lib -> appveyor.yml +test: off install: - cmd: git submodule update --init --recursive --depth=5 - - cmd: choco install resharper-clt -y - - cmd: choco install nvika -y - - cmd: dotnet tool install CodeFileSanity --version 0.0.16 --global -before_build: - - cmd: CodeFileSanity - - cmd: nuget restore -verbosity quiet -build: - project: osu.sln - parallel: true - verbosity: minimal -after_build: - - cmd: inspectcode --o="inspectcodereport.xml" --projects:osu.Game* --caches-home="inspectcode" osu.sln > NUL - - cmd: NVika parsereport "inspectcodereport.xml" --treatwarningsaserrors +build_script: + - cmd: PowerShell -Version 2.0 .\build.ps1 diff --git a/build.cake b/build.cake new file mode 100644 index 0000000000..bc7dfafb8c --- /dev/null +++ b/build.cake @@ -0,0 +1,72 @@ +#addin "nuget:?package=CodeFileSanity&version=0.0.21" +#addin "nuget:?package=JetBrains.ReSharper.CommandLineTools&version=2018.2.2" +#tool "nuget:?package=NVika.MSBuild&version=1.0.1" + +/////////////////////////////////////////////////////////////////////////////// +// ARGUMENTS +/////////////////////////////////////////////////////////////////////////////// + +var target = Argument("target", "Build"); +var configuration = Argument("configuration", "Release"); + +var osuSolution = new FilePath("./osu.sln"); + +/////////////////////////////////////////////////////////////////////////////// +// TASKS +/////////////////////////////////////////////////////////////////////////////// + +Task("Restore") + .Does(() => { + DotNetCoreRestore(osuSolution.FullPath); + }); + +Task("Compile") + .IsDependentOn("Restore") + .Does(() => { + DotNetCoreBuild(osuSolution.FullPath, new DotNetCoreBuildSettings { + Configuration = configuration, + NoRestore = true, + }); + }); + +Task("Test") + .IsDependentOn("Compile") + .Does(() => { + var testAssemblies = GetFiles("**/*.Tests/bin/**/*.Tests.dll"); + + DotNetCoreVSTest(testAssemblies, new DotNetCoreVSTestSettings { + Logger = AppVeyor.IsRunningOnAppVeyor ? "Appveyor" : $"trx", + Parallel = true, + ToolTimeout = TimeSpan.FromMinutes(10), + }); + }); + +// windows only because both inspectcore and nvika depend on net45 +Task("InspectCode") + .WithCriteria(IsRunningOnWindows()) + .IsDependentOn("Compile") + .Does(() => { + var nVikaToolPath = GetFiles("./tools/NVika.MSBuild.*/tools/NVika.exe").First(); + + InspectCode(osuSolution, new InspectCodeSettings { + CachesHome = "inspectcode", + OutputFile = "inspectcodereport.xml", + }); + + StartProcess(nVikaToolPath, @"parsereport ""inspectcodereport.xml"" --treatwarningsaserrors"); + }); + +Task("CodeFileSanity") + .Does(() => { + ValidateCodeSanity(new ValidateCodeSanitySettings { + RootDirectory = ".", + IsAppveyorBuild = AppVeyor.IsRunningOnAppVeyor + }); + }); + +Task("Build") + .IsDependentOn("CodeFileSanity") + .IsDependentOn("InspectCode") + .IsDependentOn("Test"); + +RunTarget(target); \ No newline at end of file diff --git a/build.ps1 b/build.ps1 new file mode 100644 index 0000000000..9968673c90 --- /dev/null +++ b/build.ps1 @@ -0,0 +1,79 @@ +########################################################################## +# This is a customized Cake bootstrapper script for PowerShell. +########################################################################## + +<# + +.SYNOPSIS +This is a Powershell script to bootstrap a Cake build. + +.DESCRIPTION +This Powershell script restores NuGet tools (including Cake) +and execute your Cake build script with the parameters you provide. + +.PARAMETER Script +The build script to execute. +.PARAMETER Target +The build script target to run. +.PARAMETER Configuration +The build configuration to use. +.PARAMETER Verbosity +Specifies the amount of information to be displayed. +.PARAMETER ShowDescription +Shows description about tasks. +.PARAMETER DryRun +Performs a dry run. +.PARAMETER ScriptArgs +Remaining arguments are added here. + +.LINK +https://cakebuild.net + +#> + +[CmdletBinding()] +Param( + [string]$Script = "build.cake", + [string]$Target, + [string]$Configuration, + [ValidateSet("Quiet", "Minimal", "Normal", "Verbose", "Diagnostic")] + [string]$Verbosity, + [switch]$ShowDescription, + [Alias("WhatIf", "Noop")] + [switch]$DryRun, + [Parameter(Position=0,Mandatory=$false,ValueFromRemainingArguments=$true)] + [string[]]$ScriptArgs +) + +Write-Host "Preparing to run build script..." + +# Determine the script root for resolving other paths. +if(!$PSScriptRoot){ + $PSScriptRoot = Split-Path $MyInvocation.MyCommand.Path -Parent +} + +# Resolve the paths for resources used for debugging. +$TOOLS_DIR = Join-Path $PSScriptRoot "tools" +$CAKE_CSPROJ = Join-Path $TOOLS_DIR "cakebuild.csproj" + +# Install the required tools locally. +Write-Host "Restoring cake tools..." +Invoke-Expression "dotnet restore `"$CAKE_CSPROJ`" --packages `"$TOOLS_DIR`"" | Out-Null + +# Find the Cake executable +$CAKE_EXECUTABLE = (Get-ChildItem -Path ./tools/cake.coreclr/ -Filter Cake.dll -Recurse).FullName + +# Build Cake arguments +$cakeArguments = @("$Script"); +if ($Target) { $cakeArguments += "-target=$Target" } +if ($Configuration) { $cakeArguments += "-configuration=$Configuration" } +if ($Verbosity) { $cakeArguments += "-verbosity=$Verbosity" } +if ($ShowDescription) { $cakeArguments += "-showdescription" } +if ($DryRun) { $cakeArguments += "-dryrun" } +if ($Experimental) { $cakeArguments += "-experimental" } +$cakeArguments += $ScriptArgs + +# Start Cake +Write-Host "Running build script..." +Invoke-Expression "dotnet `"$CAKE_EXECUTABLE`" $cakeArguments" +exit $LASTEXITCODE diff --git a/build.sh b/build.sh new file mode 100755 index 0000000000..caf1702f41 --- /dev/null +++ b/build.sh @@ -0,0 +1,37 @@ +#!/usr/bin/env bash + +########################################################################## +# This is a customized Cake bootstrapper script for Shell. +########################################################################## + +echo "Preparing to run build script..." + +SCRIPT_DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd ) +TOOLS_DIR=$SCRIPT_DIR/tools +CAKE_BINARY_PATH=$TOOLS_DIR/"cake.coreclr" + +SCRIPT="build.cake" +CAKE_CSPROJ=$TOOLS_DIR/"cakebuild.csproj" + +# Parse arguments. +CAKE_ARGUMENTS=() +for i in "$@"; do + case $1 in + -s|--script) SCRIPT="$2"; shift ;; + --) shift; CAKE_ARGUMENTS+=("$@"); break ;; + *) CAKE_ARGUMENTS+=("$1") ;; + esac + shift +done + +# Install the required tools locally. +echo "Restoring cake tools..." +dotnet restore $CAKE_CSPROJ --packages $TOOLS_DIR > /dev/null 2>&1 + +# Search for the CakeBuild binary. +CAKE_BINARY=$(find $CAKE_BINARY_PATH -name "Cake.dll") + +# Start Cake +echo "Running build script..." + +dotnet "$CAKE_BINARY" $SCRIPT "${CAKE_ARGUMENTS[@]}" diff --git a/cake.config b/cake.config new file mode 100644 index 0000000000..187d825591 --- /dev/null +++ b/cake.config @@ -0,0 +1,5 @@ + +[Nuget] +Source=https://api.nuget.org/v3/index.json +UseInProcessClient=true +LoadDependencies=true diff --git a/osu-resources b/osu-resources index c3848d8b1c..694cb03f19 160000 --- a/osu-resources +++ b/osu-resources @@ -1 +1 @@ -Subproject commit c3848d8b1c84966abe851d915bcca878415614b4 +Subproject commit 694cb03f19c93106ed0f2593f3e506e835fb652a diff --git a/osu.Desktop/OsuGameDesktop.cs b/osu.Desktop/OsuGameDesktop.cs index 79ac24a1da..93fd3935c6 100644 --- a/osu.Desktop/OsuGameDesktop.cs +++ b/osu.Desktop/OsuGameDesktop.cs @@ -10,7 +10,7 @@ using osu.Desktop.Overlays; using osu.Framework.Graphics.Containers; using osu.Framework.Platform; using osu.Game; -using OpenTK.Input; +using osuTK.Input; using Microsoft.Win32; using osu.Desktop.Updater; using osu.Framework; diff --git a/osu.Desktop/Overlays/VersionManager.cs b/osu.Desktop/Overlays/VersionManager.cs index 96857d6b4f..f31bba1e1e 100644 --- a/osu.Desktop/Overlays/VersionManager.cs +++ b/osu.Desktop/Overlays/VersionManager.cs @@ -15,8 +15,8 @@ using osu.Game.Graphics.Sprites; using osu.Game.Overlays; using osu.Game.Overlays.Notifications; using osu.Game.Utils; -using OpenTK; -using OpenTK.Graphics; +using osuTK; +using osuTK.Graphics; namespace osu.Desktop.Overlays { diff --git a/osu.Desktop/Updater/SimpleUpdateManager.cs b/osu.Desktop/Updater/SimpleUpdateManager.cs index 6c363422f7..e404ccd2b3 100644 --- a/osu.Desktop/Updater/SimpleUpdateManager.cs +++ b/osu.Desktop/Updater/SimpleUpdateManager.cs @@ -41,24 +41,32 @@ namespace osu.Desktop.Updater private async void checkForUpdateAsync() { - var releases = new JsonWebRequest("https://api.github.com/repos/ppy/osu/releases/latest"); - await releases.PerformAsync(); - - var latest = releases.ResponseObject; - - if (latest.TagName != version) + try { - notificationOverlay.Post(new SimpleNotification + var releases = new JsonWebRequest("https://api.github.com/repos/ppy/osu/releases/latest"); + + await releases.PerformAsync(); + + var latest = releases.ResponseObject; + + if (latest.TagName != version) { - Text = $"A newer release of osu! has been found ({version} → {latest.TagName}).\n\n" - + "Click here to download the new version, which can be installed over the top of your existing installation", - Icon = FontAwesome.fa_upload, - Activated = () => + notificationOverlay.Post(new SimpleNotification { - host.OpenUrlExternally(getBestUrl(latest)); - return true; - } - }); + Text = $"A newer release of osu! has been found ({version} → {latest.TagName}).\n\n" + + "Click here to download the new version, which can be installed over the top of your existing installation", + Icon = FontAwesome.fa_upload, + Activated = () => + { + host.OpenUrlExternally(getBestUrl(latest)); + return true; + } + }); + } + } + catch + { + // we shouldn't crash on a web failure. or any failure for the matter. } } diff --git a/osu.Desktop/Updater/SquirrelUpdateManager.cs b/osu.Desktop/Updater/SquirrelUpdateManager.cs index 1f8bff74f4..19383d617f 100644 --- a/osu.Desktop/Updater/SquirrelUpdateManager.cs +++ b/osu.Desktop/Updater/SquirrelUpdateManager.cs @@ -2,6 +2,8 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System; +using System.IO; +using System.Reflection; using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Graphics; @@ -12,9 +14,10 @@ using osu.Game; using osu.Game.Graphics; using osu.Game.Overlays; using osu.Game.Overlays.Notifications; -using OpenTK; -using OpenTK.Graphics; +using osuTK; +using osuTK.Graphics; using Squirrel; +using LogLevel = Splat.LogLevel; namespace osu.Desktop.Updater { @@ -35,7 +38,10 @@ namespace osu.Desktop.Updater notificationOverlay = notification; if (game.IsDeployedBuild) + { + Splat.Locator.CurrentMutable.Register(() => new SquirrelLogger(), typeof(Splat.ILogger)); Schedule(() => Task.Run(() => checkForUpdateAsync())); + } } private async void checkForUpdateAsync(bool useDeltaPatching = true, UpdateProgressNotification notification = null) @@ -159,5 +165,31 @@ namespace osu.Desktop.Updater }); } } + + private class SquirrelLogger : Splat.ILogger, IDisposable + { + private readonly string path; + private readonly object locker = new object(); + public LogLevel Level { get; set; } = LogLevel.Info; + + public SquirrelLogger() + { + var file = Path.Combine(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location), "SquirrelSetupUpdater.log"); + if (File.Exists(file)) File.Delete(file); + path = file; + } + + public void Write(string message, LogLevel logLevel) + { + if (logLevel < Level) + return; + + lock (locker) File.AppendAllText(path, message + "\r\n"); + } + + public void Dispose() + { + } + } } } diff --git a/osu.Desktop/osu.Desktop.csproj b/osu.Desktop/osu.Desktop.csproj index e1e59804e5..09bfdc67d4 100644 --- a/osu.Desktop/osu.Desktop.csproj +++ b/osu.Desktop/osu.Desktop.csproj @@ -27,7 +27,7 @@ - + diff --git a/osu.Game.Rulesets.Catch.Tests/TestCaseAutoJuiceStream.cs b/osu.Game.Rulesets.Catch.Tests/TestCaseAutoJuiceStream.cs index 5e68acde94..fc3809fae4 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestCaseAutoJuiceStream.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestCaseAutoJuiceStream.cs @@ -5,10 +5,11 @@ using System.Linq; using osu.Game.Beatmaps; using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.UI; +using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; using osu.Game.Screens.Play; using osu.Game.Tests.Visual; -using OpenTK; +using osuTK; namespace osu.Game.Rulesets.Catch.Tests { @@ -37,13 +38,11 @@ namespace osu.Game.Rulesets.Catch.Tests beatmap.HitObjects.Add(new JuiceStream { X = 0.5f - width / 2, - ControlPoints = new[] + Path = new SliderPath(PathType.Linear, new[] { Vector2.Zero, new Vector2(width * CatchPlayfield.BASE_WIDTH, 0) - }, - CurveType = CurveType.Linear, - Distance = width * CatchPlayfield.BASE_WIDTH, + }), StartTime = i * 2000, NewCombo = i % 8 == 0 }); diff --git a/osu.Game.Rulesets.Catch.Tests/TestCaseFruitObjects.cs b/osu.Game.Rulesets.Catch.Tests/TestCaseFruitObjects.cs index 5c41e4136c..7b6773ad58 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestCaseFruitObjects.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestCaseFruitObjects.cs @@ -10,7 +10,7 @@ using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.Objects.Drawable; using osu.Game.Rulesets.Catch.Objects.Drawable.Pieces; using osu.Game.Tests.Visual; -using OpenTK; +using osuTK; namespace osu.Game.Rulesets.Catch.Tests { diff --git a/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj b/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj index 326791f506..b76f591239 100644 --- a/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj +++ b/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj @@ -4,7 +4,7 @@ - + diff --git a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs index 15d4edc411..c3dc9499c2 100644 --- a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs +++ b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs @@ -34,10 +34,8 @@ namespace osu.Game.Rulesets.Catch.Beatmaps { StartTime = obj.StartTime, Samples = obj.Samples, - ControlPoints = curveData.ControlPoints, - CurveType = curveData.CurveType, - Distance = curveData.Distance, - RepeatSamples = curveData.RepeatSamples, + Path = curveData.Path, + NodeSamples = curveData.NodeSamples, RepeatCount = curveData.RepeatCount, X = (positionData?.X ?? 0) / CatchPlayfield.BASE_WIDTH, NewCombo = comboData?.NewCombo ?? false, diff --git a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs index c7ea29f8c0..22c1180c09 100644 --- a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs +++ b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs @@ -8,7 +8,7 @@ using osu.Game.Beatmaps; using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.UI; using osu.Game.Rulesets.Objects.Types; -using OpenTK; +using osuTK; using osu.Game.Rulesets.Catch.MathUtils; namespace osu.Game.Rulesets.Catch.Beatmaps diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyHitObject.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyHitObject.cs index 720c1d8653..bd7796ed91 100644 --- a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyHitObject.cs +++ b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyHitObject.cs @@ -4,7 +4,7 @@ using System; using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.UI; -using OpenTK; +using osuTK; namespace osu.Game.Rulesets.Catch.Difficulty { diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModAutoplay.cs b/osu.Game.Rulesets.Catch/Mods/CatchModAutoplay.cs index 5ed563614a..8bba73ed64 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModAutoplay.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModAutoplay.cs @@ -5,20 +5,17 @@ using osu.Game.Beatmaps; using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.Replays; using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Scoring; +using osu.Game.Scoring; using osu.Game.Users; namespace osu.Game.Rulesets.Catch.Mods { public class CatchModAutoplay : ModAutoplay { - protected override Score CreateReplayScore(Beatmap beatmap) + protected override Score CreateReplayScore(Beatmap beatmap) => new Score { - return new Score - { - User = new User { Username = "osu!salad!" }, - Replay = new CatchAutoGenerator(beatmap).Generate(), - }; - } + ScoreInfo = new ScoreInfo { User = new User { Username = "osu!salad!" } }, + Replay = new CatchAutoGenerator(beatmap).Generate(), + }; } } diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs b/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs index 21e09f991c..9cfba0236a 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs @@ -1,12 +1,66 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using osu.Framework.Graphics; +using osu.Game.Rulesets.Catch.Objects; +using osu.Game.Rulesets.Catch.UI; using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.UI; +using osuTK; namespace osu.Game.Rulesets.Catch.Mods { - public class CatchModFlashlight : ModFlashlight + public class CatchModFlashlight : ModFlashlight { public override double ScoreMultiplier => 1.12; + + private const float default_flashlight_size = 350; + + public override Flashlight CreateFlashlight() => new CatchFlashlight(playfield); + + private CatchPlayfield playfield; + + public override void ApplyToRulesetContainer(RulesetContainer rulesetContainer) + { + playfield = (CatchPlayfield)rulesetContainer.Playfield; + base.ApplyToRulesetContainer(rulesetContainer); + } + + private class CatchFlashlight : Flashlight + { + private readonly CatchPlayfield playfield; + + public CatchFlashlight(CatchPlayfield playfield) + { + this.playfield = playfield; + FlashlightSize = new Vector2(0, getSizeFor(0)); + } + + protected override void Update() + { + base.Update(); + + var catcherArea = playfield.CatcherArea; + + FlashlightPosition = catcherArea.ToSpaceOfOtherDrawable(catcherArea.MovableCatcher.DrawPosition, this); + } + + private float getSizeFor(int combo) + { + if (combo > 200) + return default_flashlight_size * 0.8f; + else if (combo > 100) + return default_flashlight_size * 0.9f; + else + return default_flashlight_size; + } + + protected override void OnComboChange(int newCombo) + { + this.TransformTo(nameof(FlashlightSize), new Vector2(0, getSizeFor(newCombo)), FLASHLIGHT_FADE_DURATION); + } + + protected override string FragmentShader => "CircularFlashlight"; + } } } diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableCatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableCatchHitObject.cs index 9e840301fd..2db252ebc6 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableCatchHitObject.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableCatchHitObject.cs @@ -2,8 +2,8 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System; -using OpenTK; -using OpenTK.Graphics; +using osuTK; +using osuTK.Graphics; using osu.Framework.Graphics; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Types; diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableDroplet.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableDroplet.cs index 5c8a7c4a7c..a896d13132 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableDroplet.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableDroplet.cs @@ -4,8 +4,8 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Game.Rulesets.Catch.Objects.Drawable.Pieces; -using OpenTK; -using OpenTK.Graphics; +using osuTK; +using osuTK.Graphics; namespace osu.Game.Rulesets.Catch.Objects.Drawable { diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableFruit.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableFruit.cs index 4603148114..4bd50f29f6 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableFruit.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableFruit.cs @@ -9,8 +9,8 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.MathUtils; using osu.Game.Rulesets.Catch.Objects.Drawable.Pieces; -using OpenTK; -using OpenTK.Graphics; +using osuTK; +using osuTK.Graphics; namespace osu.Game.Rulesets.Catch.Objects.Drawable { @@ -40,7 +40,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable private void load() { // todo: this should come from the skin. - AccentColour = colourForRrepesentation(HitObject.VisualRepresentation); + AccentColour = colourForRepresentation(HitObject.VisualRepresentation); InternalChildren = new[] { @@ -275,7 +275,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable border.Alpha = (float)MathHelper.Clamp((HitObject.StartTime - Time.Current) / 500, 0, 1); } - private Color4 colourForRrepesentation(FruitVisualRepresentation representation) + private Color4 colourForRepresentation(FruitVisualRepresentation representation) { switch (representation) { diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableTinyDroplet.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableTinyDroplet.cs index e0f02454c4..2f167121aa 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableTinyDroplet.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableTinyDroplet.cs @@ -1,7 +1,7 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using OpenTK; +using osuTK; namespace osu.Game.Rulesets.Catch.Objects.Drawable { diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/Pieces/Pulp.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/Pieces/Pulp.cs index 250dc8c7f1..f21c14f076 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawable/Pieces/Pulp.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawable/Pieces/Pulp.cs @@ -6,7 +6,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; -using OpenTK.Graphics; +using osuTK.Graphics; namespace osu.Game.Rulesets.Catch.Objects.Drawable.Pieces { diff --git a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs index 82e32d24d2..d8bd3e0edc 100644 --- a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs +++ b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs @@ -10,7 +10,6 @@ using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Catch.UI; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; -using OpenTK; namespace osu.Game.Rulesets.Catch.Objects { @@ -50,7 +49,7 @@ namespace osu.Game.Rulesets.Catch.Objects if (TickDistance == 0) return; - var length = Curve.Distance; + var length = Path.Distance; var tickDistance = Math.Min(TickDistance, length); var spanDuration = length / Velocity; @@ -95,7 +94,7 @@ namespace osu.Game.Rulesets.Catch.Objects AddNested(new TinyDroplet { StartTime = t, - X = X + Curve.PositionAt(progress).X / CatchPlayfield.BASE_WIDTH, + X = X + Path.PositionAt(progress).X / CatchPlayfield.BASE_WIDTH, Samples = new List(Samples.Select(s => new SampleInfo { Bank = s.Bank, @@ -110,7 +109,7 @@ namespace osu.Game.Rulesets.Catch.Objects AddNested(new Droplet { StartTime = time, - X = X + Curve.PositionAt(distanceProgress).X / CatchPlayfield.BASE_WIDTH, + X = X + Path.PositionAt(distanceProgress).X / CatchPlayfield.BASE_WIDTH, Samples = new List(Samples.Select(s => new SampleInfo { Bank = s.Bank, @@ -127,38 +126,28 @@ namespace osu.Game.Rulesets.Catch.Objects { Samples = Samples, StartTime = spanStartTime + spanDuration, - X = X + Curve.PositionAt(reversed ? 0 : 1).X / CatchPlayfield.BASE_WIDTH + X = X + Path.PositionAt(reversed ? 0 : 1).X / CatchPlayfield.BASE_WIDTH }); } } - public double EndTime => StartTime + this.SpanCount() * Curve.Distance / Velocity; + public double EndTime => StartTime + this.SpanCount() * Path.Distance / Velocity; public float EndX => X + this.CurvePositionAt(1).X / CatchPlayfield.BASE_WIDTH; public double Duration => EndTime - StartTime; - public double Distance + private SliderPath path; + + public SliderPath Path { - get { return Curve.Distance; } - set { Curve.Distance = value; } + get => path; + set => path = value; } - public SliderCurve Curve { get; } = new SliderCurve(); + public double Distance => Path.Distance; - public Vector2[] ControlPoints - { - get { return Curve.ControlPoints; } - set { Curve.ControlPoints = value; } - } - - public List> RepeatSamples { get; set; } = new List>(); - - public CurveType CurveType - { - get { return Curve.CurveType; } - set { Curve.CurveType = value; } - } + public List> NodeSamples { get; set; } = new List>(); public double? LegacyLastTickOffset { get; set; } } diff --git a/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs b/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs index 23b620248f..20bf2ee5c7 100644 --- a/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs +++ b/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs @@ -5,10 +5,10 @@ using System; using System.Linq; using osu.Framework.MathUtils; using osu.Game.Beatmaps; +using osu.Game.Replays; using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.UI; using osu.Game.Rulesets.Replays; -using osu.Game.Users; namespace osu.Game.Rulesets.Catch.Replays { @@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Catch.Replays public CatchAutoGenerator(Beatmap beatmap) : base(beatmap) { - Replay = new Replay { User = new User { Username = @"Autoplay" } }; + Replay = new Replay(); } protected Replay Replay; diff --git a/osu.Game.Rulesets.Catch/Replays/CatchFramedReplayInputHandler.cs b/osu.Game.Rulesets.Catch/Replays/CatchFramedReplayInputHandler.cs index f05eb31454..c907fec653 100644 --- a/osu.Game.Rulesets.Catch/Replays/CatchFramedReplayInputHandler.cs +++ b/osu.Game.Rulesets.Catch/Replays/CatchFramedReplayInputHandler.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using osu.Framework.Input.StateChanges; using osu.Framework.MathUtils; +using osu.Game.Replays; using osu.Game.Rulesets.Replays; namespace osu.Game.Rulesets.Catch.Replays diff --git a/osu.Game.Rulesets.Catch/Replays/CatchReplayFrame.cs b/osu.Game.Rulesets.Catch/Replays/CatchReplayFrame.cs index d5c5eb844a..8c32b75959 100644 --- a/osu.Game.Rulesets.Catch/Replays/CatchReplayFrame.cs +++ b/osu.Game.Rulesets.Catch/Replays/CatchReplayFrame.cs @@ -2,9 +2,9 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using osu.Game.Beatmaps; +using osu.Game.Replays.Legacy; using osu.Game.Rulesets.Catch.UI; using osu.Game.Rulesets.Replays; -using osu.Game.Rulesets.Replays.Legacy; using osu.Game.Rulesets.Replays.Types; namespace osu.Game.Rulesets.Catch.Replays diff --git a/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs index 925e7aaac9..0697a72325 100644 --- a/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs +++ b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs @@ -5,13 +5,12 @@ using System; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Beatmaps; -using osu.Game.Configuration; using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.Objects.Drawable; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.UI.Scrolling; -using OpenTK; +using osuTK; namespace osu.Game.Rulesets.Catch.UI { @@ -19,16 +18,10 @@ namespace osu.Game.Rulesets.Catch.UI { public const float BASE_WIDTH = 512; - private readonly CatcherArea catcherArea; - - protected override bool UserScrollSpeedAdjustment => false; - - protected override SpeedChangeVisualisationMethod VisualisationMethod => SpeedChangeVisualisationMethod.Constant; + internal readonly CatcherArea CatcherArea; public CatchPlayfield(BeatmapDifficulty difficulty, Func> getVisualRepresentation) { - Direction.Value = ScrollingDirection.Down; - Container explodingFruitContainer; Anchor = Anchor.TopCentre; @@ -45,7 +38,7 @@ namespace osu.Game.Rulesets.Catch.UI { RelativeSizeAxes = Axes.Both, }, - catcherArea = new CatcherArea(difficulty) + CatcherArea = new CatcherArea(difficulty) { GetVisualRepresentation = getVisualRepresentation, ExplodingFruitTarget = explodingFruitContainer, @@ -55,11 +48,9 @@ namespace osu.Game.Rulesets.Catch.UI HitObjectContainer } }; - - VisibleTimeRange.Value = BeatmapDifficulty.DifficultyRange(difficulty.ApproachRate, 1800, 1200, 450); } - public bool CheckIfWeCanCatch(CatchHitObject obj) => catcherArea.AttemptCatch(obj); + public bool CheckIfWeCanCatch(CatchHitObject obj) => CatcherArea.AttemptCatch(obj); public override void Add(DrawableHitObject h) { @@ -72,6 +63,6 @@ namespace osu.Game.Rulesets.Catch.UI } private void onNewResult(DrawableHitObject judgedObject, JudgementResult result) - => catcherArea.OnResult((DrawableCatchHitObject)judgedObject, result); + => CatcherArea.OnResult((DrawableCatchHitObject)judgedObject, result); } } diff --git a/osu.Game.Rulesets.Catch/UI/CatchRulesetContainer.cs b/osu.Game.Rulesets.Catch/UI/CatchRulesetContainer.cs index bd0fec43a1..3673657cd8 100644 --- a/osu.Game.Rulesets.Catch/UI/CatchRulesetContainer.cs +++ b/osu.Game.Rulesets.Catch/UI/CatchRulesetContainer.cs @@ -3,13 +3,14 @@ using osu.Framework.Input; using osu.Game.Beatmaps; +using osu.Game.Configuration; using osu.Game.Input.Handlers; +using osu.Game.Replays; using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.Objects.Drawable; using osu.Game.Rulesets.Catch.Replays; using osu.Game.Rulesets.Catch.Scoring; using osu.Game.Rulesets.Objects.Drawables; -using osu.Game.Rulesets.Replays; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI.Scrolling; @@ -18,9 +19,15 @@ namespace osu.Game.Rulesets.Catch.UI { public class CatchRulesetContainer : ScrollingRulesetContainer { + protected override ScrollVisualisationMethod VisualisationMethod => ScrollVisualisationMethod.Constant; + + protected override bool UserScrollSpeedAdjustment => false; + public CatchRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap) : base(ruleset, beatmap) { + Direction.Value = ScrollingDirection.Down; + TimeRange.Value = BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.ApproachRate, 1800, 1200, 450); } public override ScoreProcessor CreateScoreProcessor() => new CatchScoreProcessor(this); @@ -31,7 +38,7 @@ namespace osu.Game.Rulesets.Catch.UI public override PassThroughInputManager CreateInputManager() => new CatchInputManager(Ruleset.RulesetInfo); - protected override DrawableHitObject GetVisualRepresentation(CatchHitObject h) + public override DrawableHitObject GetVisualRepresentation(CatchHitObject h) { switch (h) { diff --git a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs index 06453ac32d..3637fe0c36 100644 --- a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs +++ b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs @@ -18,8 +18,8 @@ using osu.Game.Rulesets.Catch.Replays; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.UI; -using OpenTK; -using OpenTK.Graphics; +using osuTK; +using osuTK.Graphics; namespace osu.Game.Rulesets.Catch.UI { @@ -27,7 +27,7 @@ namespace osu.Game.Rulesets.Catch.UI { public const float CATCHER_SIZE = 100; - protected readonly Catcher MovableCatcher; + protected internal readonly Catcher MovableCatcher; public Func> GetVisualRepresentation; diff --git a/osu.Game.Rulesets.Catch/UI/PlayfieldAdjustmentContainer.cs b/osu.Game.Rulesets.Catch/UI/PlayfieldAdjustmentContainer.cs index ad0073ff12..fa1a7ee868 100644 --- a/osu.Game.Rulesets.Catch/UI/PlayfieldAdjustmentContainer.cs +++ b/osu.Game.Rulesets.Catch/UI/PlayfieldAdjustmentContainer.cs @@ -3,7 +3,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using OpenTK; +using osuTK; namespace osu.Game.Rulesets.Catch.UI { diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaPlacementBlueprintTestCase.cs b/osu.Game.Rulesets.Mania.Tests/ManiaPlacementBlueprintTestCase.cs new file mode 100644 index 0000000000..fb8c740d16 --- /dev/null +++ b/osu.Game.Rulesets.Mania.Tests/ManiaPlacementBlueprintTestCase.cs @@ -0,0 +1,52 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// 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.Timing; +using osu.Game.Rulesets.Mania.Edit; +using osu.Game.Rulesets.Mania.Objects.Drawables; +using osu.Game.Rulesets.Mania.UI; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.UI.Scrolling; +using osu.Game.Tests.Visual; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Rulesets.Mania.Tests +{ + [Cached(Type = typeof(IManiaHitObjectComposer))] + public abstract class ManiaPlacementBlueprintTestCase : PlacementBlueprintTestCase, IManiaHitObjectComposer + { + private readonly Column column; + + protected ManiaPlacementBlueprintTestCase() + { + Add(column = new Column(0) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + AccentColour = Color4.OrangeRed, + Clock = new FramedClock(new StopwatchClock()), // No scroll + }); + } + + protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) + { + var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); + + dependencies.CacheAs(((ScrollingTestContainer)HitObjectContainer).ScrollingInfo); + + return dependencies; + } + + protected override Container CreateHitObjectContainer() => new ScrollingTestContainer(ScrollingDirection.Down) { RelativeSizeAxes = Axes.Both }; + + protected override void AddHitObject(DrawableHitObject hitObject) => column.Add((DrawableManiaHitObject)hitObject); + + public Column ColumnAt(Vector2 screenSpacePosition) => column; + + public int TotalColumns => 1; + } +} diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaSelectionBlueprintTestCase.cs b/osu.Game.Rulesets.Mania.Tests/ManiaSelectionBlueprintTestCase.cs new file mode 100644 index 0000000000..50de5ce7a3 --- /dev/null +++ b/osu.Game.Rulesets.Mania.Tests/ManiaSelectionBlueprintTestCase.cs @@ -0,0 +1,38 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Timing; +using osu.Game.Rulesets.Mania.Edit; +using osu.Game.Rulesets.Mania.UI; +using osu.Game.Tests.Visual; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Rulesets.Mania.Tests +{ + [Cached(Type = typeof(IManiaHitObjectComposer))] + public abstract class ManiaSelectionBlueprintTestCase : SelectionBlueprintTestCase, IManiaHitObjectComposer + { + [Cached(Type = typeof(IAdjustableClock))] + private readonly IAdjustableClock clock = new StopwatchClock(); + + private readonly Column column; + + protected ManiaSelectionBlueprintTestCase() + { + Add(column = new Column(0) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + AccentColour = Color4.OrangeRed, + Clock = new FramedClock(new StopwatchClock()), // No scroll + }); + } + + public Column ColumnAt(Vector2 screenSpacePosition) => column; + + public int TotalColumns => 1; + } +} diff --git a/osu.Game.Rulesets.Mania.Tests/ScrollingTestContainer.cs b/osu.Game.Rulesets.Mania.Tests/ScrollingTestContainer.cs deleted file mode 100644 index 29663c2093..0000000000 --- a/osu.Game.Rulesets.Mania.Tests/ScrollingTestContainer.cs +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Allocation; -using osu.Framework.Configuration; -using osu.Framework.Graphics.Containers; -using osu.Game.Rulesets.Mania.UI; -using osu.Game.Rulesets.UI.Scrolling; - -namespace osu.Game.Rulesets.Mania.Tests -{ - /// - /// A container which provides a to children. - /// - public class ScrollingTestContainer : Container - { - [Cached(Type = typeof(IScrollingInfo))] - private readonly TestScrollingInfo scrollingInfo = new TestScrollingInfo(); - - public ScrollingTestContainer(ScrollingDirection direction) - { - scrollingInfo.Direction.Value = direction; - } - - public void Flip() => scrollingInfo.Direction.Value = scrollingInfo.Direction.Value == ScrollingDirection.Up ? ScrollingDirection.Down : ScrollingDirection.Up; - } - - public class TestScrollingInfo : IScrollingInfo - { - public readonly Bindable Direction = new Bindable(); - IBindable IScrollingInfo.Direction => Direction; - } -} diff --git a/osu.Game.Rulesets.Mania.Tests/TestCaseColumn.cs b/osu.Game.Rulesets.Mania.Tests/TestCaseColumn.cs index cceee718ca..eecc578861 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestCaseColumn.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestCaseColumn.cs @@ -14,8 +14,9 @@ using osu.Game.Rulesets.Mania.Objects.Drawables; using osu.Game.Rulesets.Mania.UI; using osu.Game.Rulesets.Mania.UI.Components; using osu.Game.Rulesets.UI.Scrolling; -using OpenTK; -using OpenTK.Graphics; +using osu.Game.Tests.Visual; +using osuTK; +using osuTK.Graphics; namespace osu.Game.Rulesets.Mania.Tests { @@ -48,8 +49,8 @@ namespace osu.Game.Rulesets.Mania.Tests Spacing = new Vector2(20, 0), Children = new[] { - createColumn(ScrollingDirection.Up, ManiaAction.Key1), - createColumn(ScrollingDirection.Down, ManiaAction.Key2) + createColumn(ScrollingDirection.Up, ManiaAction.Key1, 0), + createColumn(ScrollingDirection.Down, ManiaAction.Key2, 1) } }; } @@ -84,16 +85,15 @@ namespace osu.Game.Rulesets.Mania.Tests } } - private Drawable createColumn(ScrollingDirection direction, ManiaAction action) + private Drawable createColumn(ScrollingDirection direction, ManiaAction action, int index) { - var column = new Column + var column = new Column(index) { Anchor = Anchor.Centre, Origin = Anchor.Centre, Height = 0.85f, AccentColour = Color4.OrangeRed, Action = { Value = action }, - VisibleTimeRange = { Value = 2000 } }; columns.Add(column); @@ -104,6 +104,7 @@ namespace osu.Game.Rulesets.Mania.Tests Origin = Anchor.Centre, AutoSizeAxes = Axes.X, RelativeSizeAxes = Axes.Y, + TimeRange = 2000, Child = column }; } diff --git a/osu.Game.Rulesets.Mania.Tests/TestCaseHoldNotePlacementBlueprint.cs b/osu.Game.Rulesets.Mania.Tests/TestCaseHoldNotePlacementBlueprint.cs new file mode 100644 index 0000000000..ea7433268d --- /dev/null +++ b/osu.Game.Rulesets.Mania.Tests/TestCaseHoldNotePlacementBlueprint.cs @@ -0,0 +1,18 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Mania.Edit.Blueprints; +using osu.Game.Rulesets.Mania.Objects; +using osu.Game.Rulesets.Mania.Objects.Drawables; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Drawables; + +namespace osu.Game.Rulesets.Mania.Tests +{ + public class TestCaseHoldNotePlacementBlueprint : ManiaPlacementBlueprintTestCase + { + protected override DrawableHitObject CreateHitObject(HitObject hitObject) => new DrawableHoldNote((HoldNote)hitObject); + protected override PlacementBlueprint CreateBlueprint() => new HoldNotePlacementBlueprint(); + } +} diff --git a/osu.Game.Rulesets.Mania.Tests/TestCaseHoldNoteSelectionBlueprint.cs b/osu.Game.Rulesets.Mania.Tests/TestCaseHoldNoteSelectionBlueprint.cs new file mode 100644 index 0000000000..756031a463 --- /dev/null +++ b/osu.Game.Rulesets.Mania.Tests/TestCaseHoldNoteSelectionBlueprint.cs @@ -0,0 +1,57 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Graphics; +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Mania.Edit.Blueprints; +using osu.Game.Rulesets.Mania.Objects; +using osu.Game.Rulesets.Mania.Objects.Drawables; +using osu.Game.Rulesets.UI.Scrolling; +using osu.Game.Tests.Visual; + +namespace osu.Game.Rulesets.Mania.Tests +{ + public class TestCaseHoldNoteSelectionBlueprint : ManiaSelectionBlueprintTestCase + { + private readonly DrawableHoldNote drawableObject; + + protected override Container Content => content ?? base.Content; + private readonly Container content; + + public TestCaseHoldNoteSelectionBlueprint() + { + var holdNote = new HoldNote { Column = 0, Duration = 1000 }; + holdNote.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); + + base.Content.Child = content = new ScrollingTestContainer(ScrollingDirection.Down) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + AutoSizeAxes = Axes.Y, + Width = 50, + Child = drawableObject = new DrawableHoldNote(holdNote) + { + Height = 300, + AccentColour = OsuColour.Gray(0.3f) + } + }; + } + + protected override void Update() + { + base.Update(); + + foreach (var nested in drawableObject.NestedHitObjects) + { + double finalPosition = (nested.HitObject.StartTime - drawableObject.HitObject.StartTime) / drawableObject.HitObject.Duration; + nested.Y = (float)(-finalPosition * content.DrawHeight); + } + } + + protected override SelectionBlueprint CreateBlueprint() => new HoldNoteSelectionBlueprint(drawableObject); + } +} diff --git a/osu.Game.Rulesets.Mania.Tests/TestCaseNotePlacementBlueprint.cs b/osu.Game.Rulesets.Mania.Tests/TestCaseNotePlacementBlueprint.cs new file mode 100644 index 0000000000..9ae49d200e --- /dev/null +++ b/osu.Game.Rulesets.Mania.Tests/TestCaseNotePlacementBlueprint.cs @@ -0,0 +1,18 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Mania.Edit.Blueprints; +using osu.Game.Rulesets.Mania.Objects; +using osu.Game.Rulesets.Mania.Objects.Drawables; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Drawables; + +namespace osu.Game.Rulesets.Mania.Tests +{ + public class TestCaseNotePlacementBlueprint : ManiaPlacementBlueprintTestCase + { + protected override DrawableHitObject CreateHitObject(HitObject hitObject) => new DrawableNote((Note)hitObject); + protected override PlacementBlueprint CreateBlueprint() => new NotePlacementBlueprint(); + } +} diff --git a/osu.Game.Rulesets.Mania.Tests/TestCaseNoteSelectionBlueprint.cs b/osu.Game.Rulesets.Mania.Tests/TestCaseNoteSelectionBlueprint.cs new file mode 100644 index 0000000000..4023f97eb3 --- /dev/null +++ b/osu.Game.Rulesets.Mania.Tests/TestCaseNoteSelectionBlueprint.cs @@ -0,0 +1,41 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Mania.Edit.Blueprints; +using osu.Game.Rulesets.Mania.Objects; +using osu.Game.Rulesets.Mania.Objects.Drawables; +using osu.Game.Rulesets.UI.Scrolling; +using osu.Game.Tests.Visual; +using osuTK; + +namespace osu.Game.Rulesets.Mania.Tests +{ + public class TestCaseNoteSelectionBlueprint : ManiaSelectionBlueprintTestCase + { + private readonly DrawableNote drawableObject; + + protected override Container Content => content ?? base.Content; + private readonly Container content; + + public TestCaseNoteSelectionBlueprint() + { + var note = new Note { Column = 0 }; + note.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); + + base.Content.Child = content = new ScrollingTestContainer(ScrollingDirection.Down) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(50, 20), + Child = drawableObject = new DrawableNote(note) + }; + } + + protected override SelectionBlueprint CreateBlueprint() => new NoteSelectionBlueprint(drawableObject); + } +} diff --git a/osu.Game.Rulesets.Mania.Tests/TestCaseNotes.cs b/osu.Game.Rulesets.Mania.Tests/TestCaseNotes.cs index a8b2b20fda..6344aee6bd 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestCaseNotes.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestCaseNotes.cs @@ -20,8 +20,8 @@ using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Tests.Visual; -using OpenTK; -using OpenTK.Graphics; +using osuTK; +using osuTK.Graphics; namespace osu.Game.Rulesets.Mania.Tests { diff --git a/osu.Game.Rulesets.Mania.Tests/TestCaseStage.cs b/osu.Game.Rulesets.Mania.Tests/TestCaseStage.cs index 5c5d955168..7acc37cb67 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestCaseStage.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestCaseStage.cs @@ -14,7 +14,8 @@ using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Objects.Drawables; using osu.Game.Rulesets.Mania.UI; using osu.Game.Rulesets.UI.Scrolling; -using OpenTK; +using osu.Game.Tests.Visual; +using osuTK; namespace osu.Game.Rulesets.Mania.Tests { @@ -122,7 +123,7 @@ namespace osu.Game.Rulesets.Mania.Tests { var specialAction = ManiaAction.Special1; - var stage = new ManiaStage(0, new StageDefinition { Columns = 2 }, ref action, ref specialAction) { VisibleTimeRange = { Value = 2000 } }; + var stage = new ManiaStage(0, new StageDefinition { Columns = 2 }, ref action, ref specialAction); stages.Add(stage); return new ScrollingTestContainer(direction) @@ -131,6 +132,7 @@ namespace osu.Game.Rulesets.Mania.Tests Origin = Anchor.Centre, RelativeSizeAxes = Axes.Y, AutoSizeAxes = Axes.X, + TimeRange = 2000, Child = stage }; } diff --git a/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj b/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj index bf75ebbff8..98ad086c66 100644 --- a/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj +++ b/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj @@ -4,7 +4,7 @@ - + diff --git a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs index 5d8480d969..2770a6ff5b 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs @@ -12,7 +12,7 @@ using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Mania.Beatmaps.Patterns; using osu.Game.Rulesets.Mania.MathUtils; using osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy; -using OpenTK; +using osuTK; using osu.Game.Audio; namespace osu.Game.Rulesets.Mania.Beatmaps @@ -247,7 +247,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps double segmentTime = (curveData.EndTime - HitObject.StartTime) / curveData.SpanCount(); int index = (int)(segmentTime == 0 ? 0 : (time - HitObject.StartTime) / segmentTime); - return curveData.RepeatSamples[index]; + return curveData.NodeSamples[index]; } } } diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs index 37a8062d75..635004d2f6 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs @@ -470,7 +470,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy double segmentTime = (EndTime - HitObject.StartTime) / spanCount; int index = (int)(segmentTime == 0 ? 0 : (time - HitObject.StartTime) / segmentTime); - return curveData.RepeatSamples[index]; + return curveData.NodeSamples[index]; } /// diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs index da1dd62cf5..8463e209e4 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs @@ -4,7 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; -using OpenTK; +using osuTK; using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs index 7bd39adb45..f412e127c5 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs @@ -7,7 +7,7 @@ using JetBrains.Annotations; using osu.Game.Beatmaps; using osu.Game.Rulesets.Mania.MathUtils; using osu.Game.Rulesets.Objects; -using OpenTK; +using osuTK; namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy { diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs index 1f26bda1b2..610c7a8fee 100644 --- a/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs @@ -8,6 +8,7 @@ using osu.Game.Beatmaps; using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Scoring; +using osu.Game.Scoring; namespace osu.Game.Rulesets.Mania.Difficulty { @@ -27,7 +28,7 @@ namespace osu.Game.Rulesets.Mania.Difficulty private int countMeh; private int countMiss; - public ManiaPerformanceCalculator(Ruleset ruleset, WorkingBeatmap beatmap, Score score) + public ManiaPerformanceCalculator(Ruleset ruleset, WorkingBeatmap beatmap, ScoreInfo score) : base(ruleset, beatmap, score) { } diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/Components/EditBodyPiece.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/Components/EditBodyPiece.cs new file mode 100644 index 0000000000..41b2e950f9 --- /dev/null +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/Components/EditBodyPiece.cs @@ -0,0 +1,21 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Allocation; +using osu.Game.Graphics; +using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces; + +namespace osu.Game.Rulesets.Mania.Edit.Blueprints.Components +{ + public class EditBodyPiece : BodyPiece + { + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + AccentColour = colours.Yellow; + + Background.Alpha = 0.5f; + Foreground.Alpha = 0; + } + } +} diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/Components/EditNotePiece.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/Components/EditNotePiece.cs new file mode 100644 index 0000000000..424ff1118c --- /dev/null +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/Components/EditNotePiece.cs @@ -0,0 +1,29 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Allocation; +using osu.Framework.Graphics.Containers; +using osu.Game.Graphics; +using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces; + +namespace osu.Game.Rulesets.Mania.Edit.Blueprints.Components +{ + public class EditNotePiece : CompositeDrawable + { + public EditNotePiece() + { + Height = NotePiece.NOTE_HEIGHT; + + CornerRadius = 5; + Masking = true; + + InternalChild = new NotePiece(); + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + Colour = colours.Yellow; + } + } +} diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNotePlacementBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNotePlacementBlueprint.cs new file mode 100644 index 0000000000..081bdffc27 --- /dev/null +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNotePlacementBlueprint.cs @@ -0,0 +1,74 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using osu.Framework.Graphics; +using osu.Framework.Input.Events; +using osu.Game.Rulesets.Mania.Edit.Blueprints.Components; +using osu.Game.Rulesets.Mania.Objects; +using osuTK; + +namespace osu.Game.Rulesets.Mania.Edit.Blueprints +{ + public class HoldNotePlacementBlueprint : ManiaPlacementBlueprint + { + private readonly EditBodyPiece bodyPiece; + private readonly EditNotePiece headPiece; + private readonly EditNotePiece tailPiece; + + public HoldNotePlacementBlueprint() + : base(new HoldNote()) + { + RelativeSizeAxes = Axes.Both; + + InternalChildren = new Drawable[] + { + bodyPiece = new EditBodyPiece { Origin = Anchor.TopCentre }, + headPiece = new EditNotePiece { Origin = Anchor.Centre }, + tailPiece = new EditNotePiece { Origin = Anchor.Centre } + }; + } + + protected override void Update() + { + base.Update(); + + if (Column != null) + { + headPiece.Y = PositionAt(HitObject.StartTime); + tailPiece.Y = PositionAt(HitObject.EndTime); + } + + var topPosition = new Vector2(headPiece.DrawPosition.X, Math.Min(headPiece.DrawPosition.Y, tailPiece.DrawPosition.Y)); + var bottomPosition = new Vector2(headPiece.DrawPosition.X, Math.Max(headPiece.DrawPosition.Y, tailPiece.DrawPosition.Y)); + + bodyPiece.Position = topPosition; + bodyPiece.Width = headPiece.Width; + bodyPiece.Height = (bottomPosition - topPosition).Y; + } + + private double originalStartTime; + + protected override bool OnMouseMove(MouseMoveEvent e) + { + base.OnMouseMove(e); + + if (PlacementBegun) + { + var endTime = TimeAt(e.ScreenSpaceMousePosition); + + HitObject.StartTime = endTime < originalStartTime ? endTime : originalStartTime; + HitObject.Duration = Math.Abs(endTime - originalStartTime); + } + else + { + headPiece.Width = tailPiece.Width = SnappedWidth; + headPiece.X = tailPiece.X = SnappedMousePosition.X; + + originalStartTime = HitObject.StartTime = TimeAt(e.ScreenSpaceMousePosition); + } + + return true; + } + } +} diff --git a/osu.Game.Rulesets.Mania/Edit/Layers/Selection/Overlays/HoldNoteMask.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs similarity index 79% rename from osu.Game.Rulesets.Mania/Edit/Layers/Selection/Overlays/HoldNoteMask.cs rename to osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs index 03d2ba19cb..4b78dd68cb 100644 --- a/osu.Game.Rulesets.Mania/Edit/Layers/Selection/Overlays/HoldNoteMask.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs @@ -4,18 +4,17 @@ using osu.Framework.Allocation; using osu.Framework.Configuration; using osu.Framework.Graphics; +using osu.Framework.Graphics.Primitives; using osu.Game.Graphics; -using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Mania.Objects.Drawables; using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces; -using osu.Game.Rulesets.Mania.UI; using osu.Game.Rulesets.UI.Scrolling; -using OpenTK; -using OpenTK.Graphics; +using osuTK; +using osuTK.Graphics; -namespace osu.Game.Rulesets.Mania.Edit.Layers.Selection.Overlays +namespace osu.Game.Rulesets.Mania.Edit.Blueprints { - public class HoldNoteMask : HitObjectMask + public class HoldNoteSelectionBlueprint : ManiaSelectionBlueprint { public new DrawableHoldNote HitObject => (DrawableHoldNote)base.HitObject; @@ -23,13 +22,13 @@ namespace osu.Game.Rulesets.Mania.Edit.Layers.Selection.Overlays private readonly BodyPiece body; - public HoldNoteMask(DrawableHoldNote hold) + public HoldNoteSelectionBlueprint(DrawableHoldNote hold) : base(hold) { InternalChildren = new Drawable[] { - new HoldNoteNoteMask(hold.Head), - new HoldNoteNoteMask(hold.Tail), + new HoldNoteNoteSelectionBlueprint(hold.Head), + new HoldNoteNoteSelectionBlueprint(hold.Tail), body = new BodyPiece { AccentColour = Color4.Transparent @@ -50,7 +49,6 @@ namespace osu.Game.Rulesets.Mania.Edit.Layers.Selection.Overlays base.Update(); Size = HitObject.DrawSize + new Vector2(0, HitObject.Tail.DrawHeight); - Position = Parent.ToLocalSpace(HitObject.ScreenSpaceDrawQuad.TopLeft); // This is a side-effect of not matching the hitobject's anchors/origins, which is kinda hard to do // When scrolling upwards our origin is already at the top of the head note (which is the intended location), @@ -59,9 +57,11 @@ namespace osu.Game.Rulesets.Mania.Edit.Layers.Selection.Overlays Y -= HitObject.Tail.DrawHeight; } - private class HoldNoteNoteMask : NoteMask + public override Quad SelectionQuad => ScreenSpaceDrawQuad; + + private class HoldNoteNoteSelectionBlueprint : NoteSelectionBlueprint { - public HoldNoteNoteMask(DrawableNote note) + public HoldNoteNoteSelectionBlueprint(DrawableNote note) : base(note) { Select(); diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs new file mode 100644 index 0000000000..d76d20f2b8 --- /dev/null +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs @@ -0,0 +1,118 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Input; +using osu.Framework.Input.Events; +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Mania.Objects; +using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces; +using osu.Game.Rulesets.Mania.UI; +using osu.Game.Rulesets.UI.Scrolling; +using osuTK; + +namespace osu.Game.Rulesets.Mania.Edit.Blueprints +{ + public abstract class ManiaPlacementBlueprint : PlacementBlueprint, + IRequireHighFrequencyMousePosition // the playfield could be moving behind us + where T : ManiaHitObject + { + protected new T HitObject => (T)base.HitObject; + + protected Column Column; + + /// + /// The current mouse position, snapped to the closest column. + /// + protected Vector2 SnappedMousePosition { get; private set; } + + /// + /// The width of the closest column to the current mouse position. + /// + protected float SnappedWidth { get; private set; } + + [Resolved] + private IManiaHitObjectComposer composer { get; set; } + + [Resolved] + private IScrollingInfo scrollingInfo { get; set; } + + protected ManiaPlacementBlueprint(T hitObject) + : base(hitObject) + { + RelativeSizeAxes = Axes.None; + } + + protected override bool OnMouseDown(MouseDownEvent e) + { + if (Column == null) + return base.OnMouseDown(e); + + HitObject.StartTime = TimeAt(e.ScreenSpaceMousePosition); + HitObject.Column = Column.Index; + + BeginPlacement(); + return true; + } + + protected override bool OnMouseUp(MouseUpEvent e) + { + EndPlacement(); + return base.OnMouseUp(e); + } + + protected override bool OnMouseMove(MouseMoveEvent e) + { + if (!PlacementBegun) + Column = ColumnAt(e.ScreenSpaceMousePosition); + + if (Column == null) return false; + + SnappedWidth = Column.DrawWidth; + + // Snap to the column + var parentPos = Parent.ToLocalSpace(Column.ToScreenSpace(new Vector2(Column.DrawWidth / 2, 0))); + SnappedMousePosition = new Vector2(parentPos.X, e.MousePosition.Y); + return true; + } + + protected double TimeAt(Vector2 screenSpacePosition) + { + if (Column == null) + return 0; + + var hitObjectContainer = Column.HitObjectContainer; + + // If we're scrolling downwards, a position of 0 is actually further away from the hit target + // so we need to flip the vertical coordinate in the hitobject container's space + var hitObjectPos = Column.HitObjectContainer.ToLocalSpace(applyPositionOffset(screenSpacePosition, false)).Y; + if (scrollingInfo.Direction.Value == ScrollingDirection.Down) + hitObjectPos = hitObjectContainer.DrawHeight - hitObjectPos; + + return scrollingInfo.Algorithm.TimeAt(hitObjectPos, + EditorClock.CurrentTime, + scrollingInfo.TimeRange.Value, + hitObjectContainer.DrawHeight); + } + + protected float PositionAt(double time) + { + var pos = scrollingInfo.Algorithm.PositionAt(time, + EditorClock.CurrentTime, + scrollingInfo.TimeRange.Value, + Column.HitObjectContainer.DrawHeight); + + return applyPositionOffset(Column.HitObjectContainer.ToSpaceOfOtherDrawable(new Vector2(0, pos), Parent), true).Y; + } + + protected Column ColumnAt(Vector2 screenSpacePosition) + => composer.ColumnAt(applyPositionOffset(screenSpacePosition, false)); + + private Vector2 applyPositionOffset(Vector2 position, bool reverse) + { + position.Y += (scrollingInfo.Direction.Value == ScrollingDirection.Up && !reverse ? -1 : 1) * NotePiece.NOTE_HEIGHT / 2; + return position; + } + } +} diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaSelectionBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaSelectionBlueprint.cs new file mode 100644 index 0000000000..f190843f5d --- /dev/null +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaSelectionBlueprint.cs @@ -0,0 +1,80 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Input.Events; +using osu.Framework.Timing; +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Mania.Objects.Drawables; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.UI.Scrolling; +using osuTK; + +namespace osu.Game.Rulesets.Mania.Edit.Blueprints +{ + public class ManiaSelectionBlueprint : SelectionBlueprint + { + public Vector2 ScreenSpaceDragPosition { get; private set; } + public Vector2 DragPosition { get; private set; } + + protected new DrawableManiaHitObject HitObject => (DrawableManiaHitObject)base.HitObject; + + protected IClock EditorClock { get; private set; } + + [Resolved] + private IScrollingInfo scrollingInfo { get; set; } + + [Resolved] + private IManiaHitObjectComposer composer { get; set; } + + public ManiaSelectionBlueprint(DrawableHitObject hitObject) + : base(hitObject) + { + RelativeSizeAxes = Axes.None; + } + + [BackgroundDependencyLoader] + private void load(IAdjustableClock clock) + { + EditorClock = clock; + } + + protected override void Update() + { + base.Update(); + + Position = Parent.ToLocalSpace(HitObject.ToScreenSpace(Vector2.Zero)); + } + + protected override bool OnMouseDown(MouseDownEvent e) + { + ScreenSpaceDragPosition = e.ScreenSpaceMousePosition; + DragPosition = HitObject.ToLocalSpace(e.ScreenSpaceMousePosition); + + return base.OnMouseDown(e); + } + + protected override bool OnDrag(DragEvent e) + { + var result = base.OnDrag(e); + + ScreenSpaceDragPosition = e.ScreenSpaceMousePosition; + DragPosition = HitObject.ToLocalSpace(e.ScreenSpaceMousePosition); + + return result; + } + + public override void Show() + { + HitObject.AlwaysAlive = true; + base.Show(); + } + + public override void Hide() + { + HitObject.AlwaysAlive = false; + base.Hide(); + } + } +} diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/NotePlacementBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/NotePlacementBlueprint.cs new file mode 100644 index 0000000000..acb43e38ba --- /dev/null +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/NotePlacementBlueprint.cs @@ -0,0 +1,30 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Graphics; +using osu.Game.Rulesets.Mania.Edit.Blueprints.Components; +using osu.Game.Rulesets.Mania.Objects; + +namespace osu.Game.Rulesets.Mania.Edit.Blueprints +{ + public class NotePlacementBlueprint : ManiaPlacementBlueprint + { + public NotePlacementBlueprint() + : base(new Note()) + { + Origin = Anchor.Centre; + + AutoSizeAxes = Axes.Y; + + InternalChild = new EditNotePiece { RelativeSizeAxes = Axes.X }; + } + + protected override void Update() + { + base.Update(); + + Width = SnappedWidth; + Position = SnappedMousePosition; + } + } +} diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/NoteSelectionBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/NoteSelectionBlueprint.cs new file mode 100644 index 0000000000..440a539412 --- /dev/null +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/NoteSelectionBlueprint.cs @@ -0,0 +1,25 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Graphics; +using osu.Game.Rulesets.Mania.Edit.Blueprints.Components; +using osu.Game.Rulesets.Mania.Objects.Drawables; + +namespace osu.Game.Rulesets.Mania.Edit.Blueprints +{ + public class NoteSelectionBlueprint : ManiaSelectionBlueprint + { + public NoteSelectionBlueprint(DrawableNote note) + : base(note) + { + AddInternal(new EditNotePiece { RelativeSizeAxes = Axes.X }); + } + + protected override void Update() + { + base.Update(); + + Size = HitObject.DrawSize; + } + } +} diff --git a/osu.Game.Rulesets.Mania/Edit/HoldNoteCompositionTool.cs b/osu.Game.Rulesets.Mania/Edit/HoldNoteCompositionTool.cs new file mode 100644 index 0000000000..b1872c200f --- /dev/null +++ b/osu.Game.Rulesets.Mania/Edit/HoldNoteCompositionTool.cs @@ -0,0 +1,19 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Edit.Tools; +using osu.Game.Rulesets.Mania.Edit.Blueprints; + +namespace osu.Game.Rulesets.Mania.Edit +{ + public class HoldNoteCompositionTool : HitObjectCompositionTool + { + public HoldNoteCompositionTool() + : base("Hold") + { + } + + public override PlacementBlueprint CreatePlacementBlueprint() => new HoldNotePlacementBlueprint(); + } +} diff --git a/osu.Game.Rulesets.Mania/Edit/IManiaHitObjectComposer.cs b/osu.Game.Rulesets.Mania/Edit/IManiaHitObjectComposer.cs new file mode 100644 index 0000000000..d9de400ac5 --- /dev/null +++ b/osu.Game.Rulesets.Mania/Edit/IManiaHitObjectComposer.cs @@ -0,0 +1,15 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Rulesets.Mania.UI; +using osuTK; + +namespace osu.Game.Rulesets.Mania.Edit +{ + public interface IManiaHitObjectComposer + { + Column ColumnAt(Vector2 screenSpacePosition); + + int TotalColumns { get; } + } +} diff --git a/osu.Game.Rulesets.Mania/Edit/Layers/Selection/Overlays/NoteMask.cs b/osu.Game.Rulesets.Mania/Edit/Layers/Selection/Overlays/NoteMask.cs deleted file mode 100644 index 78f876cb14..0000000000 --- a/osu.Game.Rulesets.Mania/Edit/Layers/Selection/Overlays/NoteMask.cs +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Allocation; -using osu.Game.Graphics; -using osu.Game.Rulesets.Edit; -using osu.Game.Rulesets.Mania.Objects.Drawables; -using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces; - -namespace osu.Game.Rulesets.Mania.Edit.Layers.Selection.Overlays -{ - public class NoteMask : HitObjectMask - { - public NoteMask(DrawableNote note) - : base(note) - { - Scale = note.Scale; - - CornerRadius = 5; - Masking = true; - - AddInternal(new NotePiece()); - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - Colour = colours.Yellow; - } - - protected override void Update() - { - base.Update(); - - Size = HitObject.DrawSize; - Position = Parent.ToLocalSpace(HitObject.ScreenSpaceDrawQuad.TopLeft); - } - } -} diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaEditRulesetContainer.cs b/osu.Game.Rulesets.Mania/Edit/ManiaEditRulesetContainer.cs index 138a2c0273..f605ad0a22 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaEditRulesetContainer.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaEditRulesetContainer.cs @@ -2,15 +2,18 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using osu.Framework.Graphics; -using OpenTK; +using osuTK; using osu.Game.Beatmaps; using osu.Game.Rulesets.Mania.UI; using osu.Game.Rulesets.UI; +using osu.Game.Rulesets.UI.Scrolling; namespace osu.Game.Rulesets.Mania.Edit { public class ManiaEditRulesetContainer : ManiaRulesetContainer { + public new IScrollingInfo ScrollingInfo => base.ScrollingInfo; + public ManiaEditRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap) : base(ruleset, beatmap) { diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs index f37d8134ce..d73ce3966f 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs @@ -4,53 +4,72 @@ using osu.Game.Beatmaps; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit.Tools; -using osu.Game.Rulesets.Mania.Edit.Layers.Selection.Overlays; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Objects.Drawables; using osu.Game.Rulesets.Objects.Drawables; -using osu.Game.Rulesets.UI; using System.Collections.Generic; using osu.Framework.Allocation; -using osu.Game.Rulesets.Mania.Configuration; +using osu.Game.Rulesets.Mania.Edit.Blueprints; using osu.Game.Rulesets.Mania.UI; +using osu.Game.Rulesets.UI; +using osu.Game.Screens.Edit.Compose.Components; +using osuTK; namespace osu.Game.Rulesets.Mania.Edit { - public class ManiaHitObjectComposer : HitObjectComposer + [Cached(Type = typeof(IManiaHitObjectComposer))] + public class ManiaHitObjectComposer : HitObjectComposer, IManiaHitObjectComposer { - protected new ManiaConfigManager Config => (ManiaConfigManager)base.Config; + protected new ManiaEditRulesetContainer RulesetContainer { get; private set; } public ManiaHitObjectComposer(Ruleset ruleset) : base(ruleset) { } + /// + /// Retrieves the column that intersects a screen-space position. + /// + /// The screen-space position. + /// The column which intersects with . + public Column ColumnAt(Vector2 screenSpacePosition) => RulesetContainer.GetColumnByPosition(screenSpacePosition); + + private DependencyContainer dependencies; + protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) + => dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); + + public int TotalColumns => ((ManiaPlayfield)RulesetContainer.Playfield).TotalColumns; + + protected override RulesetContainer CreateRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap) { - var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); - dependencies.CacheAs(new ManiaScrollingInfo(Config)); - return dependencies; + RulesetContainer = new ManiaEditRulesetContainer(ruleset, beatmap); + + // This is the earliest we can cache the scrolling info to ourselves, before masks are added to the hierarchy and inject it + dependencies.CacheAs(RulesetContainer.ScrollingInfo); + + return RulesetContainer; } - protected override RulesetContainer CreateRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap) => new ManiaEditRulesetContainer(ruleset, beatmap); - - protected override IReadOnlyList CompositionTools => new ICompositionTool[] + protected override IReadOnlyList CompositionTools => new HitObjectCompositionTool[] { - new HitObjectCompositionTool("Note"), - new HitObjectCompositionTool("Hold"), + new NoteCompositionTool(), + new HoldNoteCompositionTool() }; - public override HitObjectMask CreateMaskFor(DrawableHitObject hitObject) + public override SelectionHandler CreateSelectionHandler() => new ManiaSelectionHandler(); + + public override SelectionBlueprint CreateBlueprintFor(DrawableHitObject hitObject) { switch (hitObject) { case DrawableNote note: - return new NoteMask(note); + return new NoteSelectionBlueprint(note); case DrawableHoldNote holdNote: - return new HoldNoteMask(holdNote); + return new HoldNoteSelectionBlueprint(holdNote); } - return base.CreateMaskFor(hitObject); + return base.CreateBlueprintFor(hitObject); } } } diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs b/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs new file mode 100644 index 0000000000..828f6d87bc --- /dev/null +++ b/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs @@ -0,0 +1,125 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Input.Events; +using osu.Framework.Timing; +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Mania.Edit.Blueprints; +using osu.Game.Rulesets.Mania.Objects; +using osu.Game.Rulesets.UI; +using osu.Game.Rulesets.UI.Scrolling; +using osu.Game.Screens.Edit.Compose.Components; +using osuTK; + +namespace osu.Game.Rulesets.Mania.Edit +{ + public class ManiaSelectionHandler : SelectionHandler + { + [Resolved] + private IScrollingInfo scrollingInfo { get; set; } + + [Resolved] + private IManiaHitObjectComposer composer { get; set; } + + private IClock editorClock; + + [BackgroundDependencyLoader] + private void load(IAdjustableClock clock) + { + editorClock = clock; + } + + public override void HandleDrag(SelectionBlueprint blueprint, DragEvent dragEvent) + { + adjustOrigins((ManiaSelectionBlueprint)blueprint); + performDragMovement(dragEvent); + performColumnMovement(dragEvent); + + base.HandleDrag(blueprint, dragEvent); + } + + /// + /// Ensures that the position of hitobjects remains centred to the mouse position. + /// E.g. The hitobject position will change if the editor scrolls while a hitobject is dragged. + /// + /// The that received the drag event. + private void adjustOrigins(ManiaSelectionBlueprint reference) + { + var referenceParent = (HitObjectContainer)reference.HitObject.Parent; + + float offsetFromReferenceOrigin = reference.DragPosition.Y - reference.HitObject.OriginPosition.Y; + float targetPosition = referenceParent.ToLocalSpace(reference.ScreenSpaceDragPosition).Y - offsetFromReferenceOrigin; + + // Flip the vertical coordinate space when scrolling downwards + if (scrollingInfo.Direction.Value == ScrollingDirection.Down) + targetPosition = targetPosition - referenceParent.DrawHeight; + + float movementDelta = targetPosition - reference.HitObject.Position.Y; + + foreach (var b in SelectedBlueprints.OfType()) + b.HitObject.Y += movementDelta; + } + + private void performDragMovement(DragEvent dragEvent) + { + foreach (var b in SelectedBlueprints) + { + var hitObject = b.HitObject; + + var objectParent = (HitObjectContainer)hitObject.Parent; + + // Using the hitobject position is required since AdjustPosition can be invoked multiple times per frame + // without the position having been updated by the parenting ScrollingHitObjectContainer + hitObject.Y += dragEvent.Delta.Y; + + float targetPosition; + + // If we're scrolling downwards, a position of 0 is actually further away from the hit target + // so we need to flip the vertical coordinate in the hitobject container's space + if (scrollingInfo.Direction.Value == ScrollingDirection.Down) + targetPosition = -hitObject.Position.Y; + else + targetPosition = hitObject.Position.Y; + + objectParent.Remove(hitObject); + + hitObject.HitObject.StartTime = scrollingInfo.Algorithm.TimeAt(targetPosition, + editorClock.CurrentTime, + scrollingInfo.TimeRange.Value, + objectParent.DrawHeight); + + objectParent.Add(hitObject); + } + } + + private void performColumnMovement(DragEvent dragEvent) + { + var lastColumn = composer.ColumnAt(dragEvent.ScreenSpaceLastMousePosition); + var currentColumn = composer.ColumnAt(dragEvent.ScreenSpaceMousePosition); + if (lastColumn == null || currentColumn == null) + return; + + int columnDelta = currentColumn.Index - lastColumn.Index; + if (columnDelta == 0) + return; + + int minColumn = int.MaxValue; + int maxColumn = int.MinValue; + + foreach (var obj in SelectedHitObjects.OfType()) + { + if (obj.Column < minColumn) + minColumn = obj.Column; + if (obj.Column > maxColumn) + maxColumn = obj.Column; + } + + columnDelta = MathHelper.Clamp(columnDelta, -minColumn, composer.TotalColumns - 1 - maxColumn); + + foreach (var obj in SelectedHitObjects.OfType()) + obj.Column += columnDelta; + } + } +} diff --git a/osu.Game.Rulesets.Mania/Edit/Masks/ManiaSelectionBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Masks/ManiaSelectionBlueprint.cs new file mode 100644 index 0000000000..81a2728ad4 --- /dev/null +++ b/osu.Game.Rulesets.Mania/Edit/Masks/ManiaSelectionBlueprint.cs @@ -0,0 +1,18 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Graphics; +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Objects.Drawables; + +namespace osu.Game.Rulesets.Mania.Edit.Masks +{ + public abstract class ManiaSelectionBlueprint : SelectionBlueprint + { + protected ManiaSelectionBlueprint(DrawableHitObject hitObject) + : base(hitObject) + { + RelativeSizeAxes = Axes.None; + } + } +} diff --git a/osu.Game.Rulesets.Mania/Edit/NoteCompositionTool.cs b/osu.Game.Rulesets.Mania/Edit/NoteCompositionTool.cs new file mode 100644 index 0000000000..93f49d1cc0 --- /dev/null +++ b/osu.Game.Rulesets.Mania/Edit/NoteCompositionTool.cs @@ -0,0 +1,20 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Edit.Tools; +using osu.Game.Rulesets.Mania.Edit.Blueprints; +using osu.Game.Rulesets.Mania.Objects; + +namespace osu.Game.Rulesets.Mania.Edit +{ + public class NoteCompositionTool : HitObjectCompositionTool + { + public NoteCompositionTool() + : base(nameof(Note)) + { + } + + public override PlacementBlueprint CreatePlacementBlueprint() => new NotePlacementBlueprint(); + } +} diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index 19e89c8ec5..392d10b414 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -24,7 +24,7 @@ using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.Configuration; using osu.Game.Rulesets.Mania.Difficulty; using osu.Game.Rulesets.Mania.Edit; -using osu.Game.Rulesets.Scoring; +using osu.Game.Scoring; namespace osu.Game.Rulesets.Mania { @@ -32,7 +32,7 @@ namespace osu.Game.Rulesets.Mania { public override RulesetContainer CreateRulesetContainerWith(WorkingBeatmap beatmap) => new ManiaRulesetContainer(this, beatmap); public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new ManiaBeatmapConverter(beatmap); - public override PerformanceCalculator CreatePerformanceCalculator(WorkingBeatmap beatmap, Score score) => new ManiaPerformanceCalculator(this, beatmap, score); + public override PerformanceCalculator CreatePerformanceCalculator(WorkingBeatmap beatmap, ScoreInfo score) => new ManiaPerformanceCalculator(this, beatmap, score); public override HitObjectComposer CreateHitObjectComposer() => new ManiaHitObjectComposer(this); diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaKeyMod.cs b/osu.Game.Rulesets.Mania/Mods/ManiaKeyMod.cs index 6d91c03c13..d4164ec1f6 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaKeyMod.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaKeyMod.cs @@ -11,7 +11,7 @@ namespace osu.Game.Rulesets.Mania.Mods { public abstract class ManiaKeyMod : Mod, IApplicableToBeatmapConverter { - public override string ShortenedName => Name; + public override string Acronym => Name; public abstract int KeyCount { get; } public override ModType Type => ModType.Conversion; public override double ScoreMultiplier => 1; // TODO: Implement the mania key mod score multiplier diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModAutoplay.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModAutoplay.cs index 89792956bb..f53943ec85 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModAutoplay.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModAutoplay.cs @@ -6,20 +6,17 @@ using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Replays; using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Scoring; +using osu.Game.Scoring; using osu.Game.Users; namespace osu.Game.Rulesets.Mania.Mods { public class ManiaModAutoplay : ModAutoplay { - protected override Score CreateReplayScore(Beatmap beatmap) + protected override Score CreateReplayScore(Beatmap beatmap) => new Score { - return new Score - { - User = new User { Username = "osu!topus!" }, - Replay = new ManiaAutoGenerator((ManiaBeatmap)beatmap).Generate(), - }; - } + ScoreInfo = new ScoreInfo { User = new User { Username = "osu!topus!" } }, + Replay = new ManiaAutoGenerator((ManiaBeatmap)beatmap).Generate(), + }; } } diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModDualStages.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModDualStages.cs index aecfb50fbe..5a8ce05873 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModDualStages.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModDualStages.cs @@ -5,16 +5,14 @@ using System.Collections.Generic; using osu.Game.Beatmaps; using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.Objects; -using osu.Game.Rulesets.Mania.UI; using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.UI; namespace osu.Game.Rulesets.Mania.Mods { - public class ManiaModDualStages : Mod, IPlayfieldTypeMod, IApplicableToBeatmapConverter, IApplicableToRulesetContainer + public class ManiaModDualStages : Mod, IPlayfieldTypeMod, IApplicableToBeatmapConverter, IApplicableToBeatmap { public override string Name => "Dual Stages"; - public override string ShortenedName => "DS"; + public override string Acronym => "DS"; public override string Description => @"Double the stages, double the fun!"; public override ModType Type => ModType.Conversion; public override double ScoreMultiplier => 1; @@ -34,22 +32,21 @@ namespace osu.Game.Rulesets.Mania.Mods mbc.TargetColumns *= 2; } - public void ApplyToRulesetContainer(RulesetContainer rulesetContainer) + public void ApplyToBeatmap(Beatmap beatmap) { - var mrc = (ManiaRulesetContainer)rulesetContainer; - - // Although this can work, for now let's not allow keymods for mania-specific beatmaps if (isForCurrentRuleset) return; + var maniaBeatmap = (ManiaBeatmap)beatmap; + var newDefinitions = new List(); - foreach (var existing in mrc.Beatmap.Stages) + foreach (var existing in maniaBeatmap.Stages) { newDefinitions.Add(new StageDefinition { Columns = existing.Columns / 2 }); newDefinitions.Add(new StageDefinition { Columns = existing.Columns / 2 }); } - mrc.Beatmap.Stages = newDefinitions; + maniaBeatmap.Stages = newDefinitions; } public PlayfieldType PlayfieldType => PlayfieldType.Dual; diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModFadeIn.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModFadeIn.cs index 08815ede09..229170cea8 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModFadeIn.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModFadeIn.cs @@ -3,6 +3,7 @@ using System; using osu.Game.Graphics; +using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mods; namespace osu.Game.Rulesets.Mania.Mods @@ -10,12 +11,12 @@ namespace osu.Game.Rulesets.Mania.Mods public class ManiaModFadeIn : Mod { public override string Name => "Fade In"; - public override string ShortenedName => "FI"; + public override string Acronym => "FI"; public override FontAwesome Icon => FontAwesome.fa_osu_mod_hidden; public override ModType Type => ModType.DifficultyIncrease; public override string Description => @"Keys appear out of nowhere!"; public override double ScoreMultiplier => 1; public override bool Ranked => true; - public override Type[] IncompatibleMods => new[] { typeof(ModFlashlight) }; + public override Type[] IncompatibleMods => new[] { typeof(ModFlashlight) }; } } diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs index d7a1bc4fbe..cd84483eb9 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs @@ -2,13 +2,60 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System; +using osu.Framework.Caching; +using osu.Framework.Graphics; +using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mods; +using osuTK; namespace osu.Game.Rulesets.Mania.Mods { - public class ManiaModFlashlight : ModFlashlight + public class ManiaModFlashlight : ModFlashlight { public override double ScoreMultiplier => 1; public override Type[] IncompatibleMods => new[] { typeof(ModHidden) }; + + private const float default_flashlight_size = 180; + + public override Flashlight CreateFlashlight() => new ManiaFlashlight(); + + private class ManiaFlashlight : Flashlight + { + private readonly Cached flashlightProperties = new Cached(); + + public ManiaFlashlight() + { + FlashlightSize = new Vector2(0, default_flashlight_size); + } + + public override bool Invalidate(Invalidation invalidation = Invalidation.All, Drawable source = null, bool shallPropagate = true) + { + if ((invalidation & Invalidation.DrawSize) > 0) + { + flashlightProperties.Invalidate(); + } + + return base.Invalidate(invalidation, source, shallPropagate); + } + + protected override void Update() + { + base.Update(); + + if (!flashlightProperties.IsValid) + { + FlashlightSize = new Vector2(DrawWidth, FlashlightSize.Y); + + FlashlightPosition = DrawPosition + DrawSize / 2; + flashlightProperties.Validate(); + } + } + + protected override void OnComboChange(int newCombo) + { + } + + protected override string FragmentShader => "RectangularFlashlight"; + } } } diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModHidden.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModHidden.cs index 2ef68a35fa..9bc2502a8f 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModHidden.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModHidden.cs @@ -2,6 +2,7 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System; +using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mods; namespace osu.Game.Rulesets.Mania.Mods @@ -10,6 +11,6 @@ namespace osu.Game.Rulesets.Mania.Mods { public override string Description => @"Keys fade out before you hit them!"; public override double ScoreMultiplier => 1; - public override Type[] IncompatibleMods => new[] { typeof(ModFlashlight) }; + public override Type[] IncompatibleMods => new[] { typeof(ModFlashlight) }; } } diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModKey1.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModKey1.cs index c2a4ed444f..11143f715e 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModKey1.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModKey1.cs @@ -7,7 +7,7 @@ namespace osu.Game.Rulesets.Mania.Mods { public override int KeyCount => 1; public override string Name => "One Key"; - public override string ShortenedName => "1K"; + public override string Acronym => "1K"; public override string Description => @"Play with one key."; } } diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModKey2.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModKey2.cs index 3d78ad449b..0169d4ecac 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModKey2.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModKey2.cs @@ -7,7 +7,7 @@ namespace osu.Game.Rulesets.Mania.Mods { public override int KeyCount => 2; public override string Name => "Two Keys"; - public override string ShortenedName => "2K"; + public override string Acronym => "2K"; public override string Description => @"Play with two keys."; } } diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModKey3.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModKey3.cs index a96375a81d..12e84eda0f 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModKey3.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModKey3.cs @@ -7,7 +7,7 @@ namespace osu.Game.Rulesets.Mania.Mods { public override int KeyCount => 3; public override string Name => "Three Keys"; - public override string ShortenedName => "3K"; + public override string Acronym => "3K"; public override string Description => @"Play with three keys."; } } diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModKey4.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModKey4.cs index 2bd3d08648..ab5ed91d9d 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModKey4.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModKey4.cs @@ -7,7 +7,7 @@ namespace osu.Game.Rulesets.Mania.Mods { public override int KeyCount => 4; public override string Name => "Four Keys"; - public override string ShortenedName => "4K"; + public override string Acronym => "4K"; public override string Description => @"Play with four keys."; } } diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModKey5.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModKey5.cs index e59b2d6e59..be9f0f8066 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModKey5.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModKey5.cs @@ -7,7 +7,7 @@ namespace osu.Game.Rulesets.Mania.Mods { public override int KeyCount => 5; public override string Name => "Five Keys"; - public override string ShortenedName => "5K"; + public override string Acronym => "5K"; public override string Description => @"Play with five keys."; } } diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModKey6.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModKey6.cs index 6a05317f46..571e533078 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModKey6.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModKey6.cs @@ -7,7 +7,7 @@ namespace osu.Game.Rulesets.Mania.Mods { public override int KeyCount => 6; public override string Name => "Six Keys"; - public override string ShortenedName => "6K"; + public override string Acronym => "6K"; public override string Description => @"Play with six keys."; } } diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModKey7.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModKey7.cs index 7280c345b8..89c6bd997d 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModKey7.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModKey7.cs @@ -7,7 +7,7 @@ namespace osu.Game.Rulesets.Mania.Mods { public override int KeyCount => 7; public override string Name => "Seven Keys"; - public override string ShortenedName => "7K"; + public override string Acronym => "7K"; public override string Description => @"Play with seven keys."; } } diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModKey8.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModKey8.cs index dddef0fa9d..8d043651d3 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModKey8.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModKey8.cs @@ -7,7 +7,7 @@ namespace osu.Game.Rulesets.Mania.Mods { public override int KeyCount => 8; public override string Name => "Eight Keys"; - public override string ShortenedName => "8K"; + public override string Acronym => "8K"; public override string Description => @"Play with eight keys."; } } diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModKey9.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModKey9.cs index 4ef38503a4..20471ebffc 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModKey9.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModKey9.cs @@ -7,7 +7,7 @@ namespace osu.Game.Rulesets.Mania.Mods { public override int KeyCount => 9; public override string Name => "Nine Keys"; - public override string ShortenedName => "9K"; + public override string Acronym => "9K"; public override string Description => @"Play with nine keys."; } } diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModMirror.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModMirror.cs index 847b0037f1..68325b40bf 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModMirror.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModMirror.cs @@ -13,7 +13,7 @@ namespace osu.Game.Rulesets.Mania.Mods public class ManiaModMirror : Mod, IApplicableToRulesetContainer { public override string Name => "Mirror"; - public override string ShortenedName => "MR"; + public override string Acronym => "MR"; public override ModType Type => ModType.Conversion; public override double ScoreMultiplier => 1; public override bool Ranked => true; diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModRandom.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModRandom.cs index 0915b80742..b3a3d4280b 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModRandom.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModRandom.cs @@ -15,7 +15,7 @@ namespace osu.Game.Rulesets.Mania.Mods public class ManiaModRandom : Mod, IApplicableToRulesetContainer { public override string Name => "Random"; - public override string ShortenedName => "RD"; + public override string Acronym => "RD"; public override ModType Type => ModType.Conversion; public override FontAwesome Icon => FontAwesome.fa_osu_dice; public override string Description => @"Shuffle around the keys!"; diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableBarLine.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableBarLine.cs index d0fc6aa3d6..cf718ebdb0 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableBarLine.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableBarLine.cs @@ -1,11 +1,11 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using OpenTK; +using osuTK; using osu.Framework.Graphics; using osu.Framework.Graphics.Shapes; using osu.Game.Rulesets.Objects.Drawables; -using OpenTK.Graphics; +using osuTK.Graphics; namespace osu.Game.Rulesets.Mania.Objects.Drawables { diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs index 6a0457efc6..5fcc71a039 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs @@ -5,7 +5,7 @@ using System.Linq; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces; -using OpenTK.Graphics; +using osuTK.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input.Bindings; using osu.Game.Rulesets.Scoring; diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTick.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTick.cs index 01d5bc6fd4..3e014ec35c 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTick.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTick.cs @@ -2,8 +2,8 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System; -using OpenTK; -using OpenTK.Graphics; +using osuTK; +using osuTK.Graphics; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs index cb6196a890..70ff7b4124 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs @@ -5,7 +5,6 @@ using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Configuration; using osu.Framework.Graphics; -using osu.Game.Rulesets.Mania.UI; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.UI.Scrolling; @@ -13,6 +12,11 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables { public abstract class DrawableManiaHitObject : DrawableHitObject { + /// + /// Whether this should always remain alive. + /// + internal bool AlwaysAlive; + /// /// The which causes this to be hit. /// @@ -35,6 +39,8 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables Direction.BindValueChanged(OnDirectionChanged, true); } + protected override bool ShouldBeAlive => AlwaysAlive || base.ShouldBeAlive; + protected virtual void OnDirectionChanged(ScrollingDirection direction) { Anchor = Origin = direction == ScrollingDirection.Up ? Anchor.TopCentre : Anchor.BottomCentre; diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs index 7567f40b2f..423712b026 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs @@ -2,7 +2,7 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using osu.Framework.Extensions.Color4Extensions; -using OpenTK.Graphics; +using osuTK.Graphics; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input.Bindings; diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/BodyPiece.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/BodyPiece.cs index 619fe06c73..8dbf33c183 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/BodyPiece.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/BodyPiece.cs @@ -3,7 +3,7 @@ using System; using osu.Framework.Caching; -using OpenTK.Graphics; +using osuTK.Graphics; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -15,12 +15,12 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces /// /// Represents length-wise portion of a hold note. /// - internal class BodyPiece : Container, IHasAccentColour + public class BodyPiece : Container, IHasAccentColour { private readonly Container subtractionLayer; - private readonly Drawable background; - private readonly BufferedContainer foreground; + protected readonly Drawable Background; + protected readonly BufferedContainer Foreground; private readonly BufferedContainer subtractionContainer; public BodyPiece() @@ -29,8 +29,8 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces Children = new[] { - background = new Box { RelativeSizeAxes = Axes.Both }, - foreground = new BufferedContainer + Background = new Box { RelativeSizeAxes = Axes.Both }, + Foreground = new BufferedContainer { Blending = BlendingMode.Additive, RelativeSizeAxes = Axes.Both, @@ -123,7 +123,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces Radius = DrawWidth }; - foreground.ForceRedraw(); + Foreground.ForceRedraw(); subtractionContainer.ForceRedraw(); subtractionCache.Validate(); @@ -137,18 +137,18 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces if (!IsLoaded) return; - foreground.Colour = AccentColour.Opacity(0.5f); - background.Colour = AccentColour.Opacity(0.7f); + Foreground.Colour = AccentColour.Opacity(0.5f); + Background.Colour = AccentColour.Opacity(0.7f); const float animation_length = 50; - foreground.ClearTransforms(false, nameof(foreground.Colour)); + Foreground.ClearTransforms(false, nameof(Foreground.Colour)); if (hitting) { // wait for the next sync point double synchronisedOffset = animation_length * 2 - Time.Current % (animation_length * 2); - using (foreground.BeginDelayedSequence(synchronisedOffset)) - foreground.FadeColour(AccentColour.Lighten(0.2f), animation_length).Then().FadeColour(foreground.Colour, animation_length).Loop(); + using (Foreground.BeginDelayedSequence(synchronisedOffset)) + Foreground.FadeColour(AccentColour.Lighten(0.2f), animation_length).Then().FadeColour(Foreground.Colour, animation_length).Loop(); } subtractionCache.Invalidate(); diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/GlowPiece.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/GlowPiece.cs index 68c9ce07d4..3a524bfc90 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/GlowPiece.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/GlowPiece.cs @@ -6,7 +6,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; -using OpenTK.Graphics; +using osuTK.Graphics; namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces { diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/LaneGlowPiece.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/LaneGlowPiece.cs index 445abc28e2..8325cb8ac0 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/LaneGlowPiece.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/LaneGlowPiece.cs @@ -1,7 +1,7 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using OpenTK.Graphics; +using osuTK.Graphics; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/NotePiece.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/NotePiece.cs index 2c74f5b168..26d5a7f188 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/NotePiece.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/NotePiece.cs @@ -3,13 +3,12 @@ using osu.Framework.Allocation; using osu.Framework.Configuration; -using OpenTK.Graphics; +using osuTK.Graphics; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; -using osu.Game.Rulesets.Mania.UI; using osu.Game.Rulesets.UI.Scrolling; namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces diff --git a/osu.Game.Rulesets.Mania/Objects/ManiaHitObject.cs b/osu.Game.Rulesets.Mania/Objects/ManiaHitObject.cs index e183098a51..bb3d060e61 100644 --- a/osu.Game.Rulesets.Mania/Objects/ManiaHitObject.cs +++ b/osu.Game.Rulesets.Mania/Objects/ManiaHitObject.cs @@ -1,6 +1,7 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using osu.Framework.Configuration; using osu.Game.Rulesets.Mania.Objects.Types; using osu.Game.Rulesets.Objects; @@ -8,7 +9,13 @@ namespace osu.Game.Rulesets.Mania.Objects { public abstract class ManiaHitObject : HitObject, IHasColumn { - public virtual int Column { get; set; } + public readonly Bindable ColumnBindable = new Bindable(); + + public virtual int Column + { + get => ColumnBindable; + set => ColumnBindable.Value = value; + } protected override HitWindows CreateHitWindows() => new ManiaHitWindows(); } diff --git a/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs b/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs index 0eef540d97..c58d66c66a 100644 --- a/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs +++ b/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs @@ -3,11 +3,11 @@ using System.Collections.Generic; using System.Linq; +using osu.Game.Replays; using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Replays; -using osu.Game.Users; namespace osu.Game.Rulesets.Mania.Replays { @@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Mania.Replays public ManiaAutoGenerator(ManiaBeatmap beatmap) : base(beatmap) { - Replay = new Replay { User = new User { Username = @"Autoplay" } }; + Replay = new Replay(); columnActions = new ManiaAction[Beatmap.TotalColumns]; diff --git a/osu.Game.Rulesets.Mania/Replays/ManiaFramedReplayInputHandler.cs b/osu.Game.Rulesets.Mania/Replays/ManiaFramedReplayInputHandler.cs index ea239bf80f..20b0775c57 100644 --- a/osu.Game.Rulesets.Mania/Replays/ManiaFramedReplayInputHandler.cs +++ b/osu.Game.Rulesets.Mania/Replays/ManiaFramedReplayInputHandler.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Linq; using osu.Framework.Input.StateChanges; +using osu.Game.Replays; using osu.Game.Rulesets.Replays; namespace osu.Game.Rulesets.Mania.Replays diff --git a/osu.Game.Rulesets.Mania/Replays/ManiaReplayFrame.cs b/osu.Game.Rulesets.Mania/Replays/ManiaReplayFrame.cs index bc9fd6e06f..04d3bdf5f5 100644 --- a/osu.Game.Rulesets.Mania/Replays/ManiaReplayFrame.cs +++ b/osu.Game.Rulesets.Mania/Replays/ManiaReplayFrame.cs @@ -3,9 +3,9 @@ using System.Collections.Generic; using osu.Game.Beatmaps; +using osu.Game.Replays.Legacy; using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Replays; -using osu.Game.Rulesets.Replays.Legacy; using osu.Game.Rulesets.Replays.Types; namespace osu.Game.Rulesets.Mania.Replays diff --git a/osu.Game.Rulesets.Mania/UI/Column.cs b/osu.Game.Rulesets.Mania/UI/Column.cs index 09976e5994..efa4a671a3 100644 --- a/osu.Game.Rulesets.Mania/UI/Column.cs +++ b/osu.Game.Rulesets.Mania/UI/Column.cs @@ -1,26 +1,32 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using OpenTK.Graphics; +using System.Linq; +using osuTK.Graphics; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Graphics; using osu.Game.Rulesets.Objects.Drawables; -using System.Linq; using osu.Framework.Allocation; using osu.Framework.Configuration; using osu.Framework.Input.Bindings; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Mania.UI.Components; using osu.Game.Rulesets.UI.Scrolling; +using osuTK; namespace osu.Game.Rulesets.Mania.UI { - public class Column : ManiaScrollingPlayfield, IKeyBindingHandler, IHasAccentColour + public class Column : ScrollingPlayfield, IKeyBindingHandler, IHasAccentColour { private const float column_width = 45; private const float special_column_width = 70; + /// + /// The index of this column as part of the whole playfield. + /// + public readonly int Index; + public readonly Bindable Action = new Bindable(); private readonly ColumnBackground background; @@ -30,8 +36,10 @@ namespace osu.Game.Rulesets.Mania.UI internal readonly Container TopLevelContainer; private readonly Container explosionContainer; - public Column() + public Column(int index) { + Index = index; + RelativeSizeAxes = Axes.Y; Width = column_width; @@ -134,7 +142,16 @@ namespace osu.Game.Rulesets.Mania.UI hitObject.AccentColour = AccentColour; hitObject.OnNewResult += OnNewResult; - HitObjects.Add(hitObject); + HitObjectContainer.Add(hitObject); + } + + public override bool Remove(DrawableHitObject h) + { + if (!base.Remove(h)) + return false; + + h.OnNewResult -= OnNewResult; + return true; } internal void OnNewResult(DrawableHitObject judgedObject, JudgementResult result) @@ -144,7 +161,7 @@ namespace osu.Game.Rulesets.Mania.UI explosionContainer.Add(new HitExplosion(judgedObject) { - Anchor = Direction == ScrollingDirection.Up ? Anchor.TopCentre : Anchor.BottomCentre + Anchor = Direction.Value == ScrollingDirection.Up ? Anchor.TopCentre : Anchor.BottomCentre }); } @@ -154,10 +171,10 @@ namespace osu.Game.Rulesets.Mania.UI return false; var nextObject = - HitObjects.AliveObjects.FirstOrDefault(h => h.HitObject.StartTime > Time.Current) ?? + HitObjectContainer.AliveObjects.FirstOrDefault(h => h.HitObject.StartTime > Time.Current) ?? // fallback to non-alive objects to find next off-screen object - HitObjects.Objects.FirstOrDefault(h => h.HitObject.StartTime > Time.Current) ?? - HitObjects.Objects.LastOrDefault(); + HitObjectContainer.Objects.FirstOrDefault(h => h.HitObject.StartTime > Time.Current) ?? + HitObjectContainer.Objects.LastOrDefault(); nextObject?.PlaySamples(); @@ -165,5 +182,9 @@ namespace osu.Game.Rulesets.Mania.UI } public bool OnReleased(ManiaAction action) => false; + + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) + // This probably shouldn't exist as is, but the columns in the stage are separated by a 1px border + => DrawRectangle.Inflate(new Vector2(ManiaStage.COLUMN_SPACING / 2, 0)).Contains(ToLocalSpace(screenSpacePos)); } } diff --git a/osu.Game.Rulesets.Mania/UI/Components/ColumnBackground.cs b/osu.Game.Rulesets.Mania/UI/Components/ColumnBackground.cs index 19cc8fffef..6aef158205 100644 --- a/osu.Game.Rulesets.Mania/UI/Components/ColumnBackground.cs +++ b/osu.Game.Rulesets.Mania/UI/Components/ColumnBackground.cs @@ -11,7 +11,7 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Bindings; using osu.Game.Graphics; using osu.Game.Rulesets.UI.Scrolling; -using OpenTK.Graphics; +using osuTK.Graphics; namespace osu.Game.Rulesets.Mania.UI.Components { diff --git a/osu.Game.Rulesets.Mania/UI/Components/ColumnHitObjectArea.cs b/osu.Game.Rulesets.Mania/UI/Components/ColumnHitObjectArea.cs index 5a4adfd72e..f158d5be07 100644 --- a/osu.Game.Rulesets.Mania/UI/Components/ColumnHitObjectArea.cs +++ b/osu.Game.Rulesets.Mania/UI/Components/ColumnHitObjectArea.cs @@ -10,7 +10,7 @@ using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI.Scrolling; -using OpenTK.Graphics; +using osuTK.Graphics; namespace osu.Game.Rulesets.Mania.UI.Components { diff --git a/osu.Game.Rulesets.Mania/UI/Components/ColumnKeyArea.cs b/osu.Game.Rulesets.Mania/UI/Components/ColumnKeyArea.cs index e30a033831..228e81fef3 100644 --- a/osu.Game.Rulesets.Mania/UI/Components/ColumnKeyArea.cs +++ b/osu.Game.Rulesets.Mania/UI/Components/ColumnKeyArea.cs @@ -11,8 +11,8 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Bindings; using osu.Game.Graphics; using osu.Game.Rulesets.UI.Scrolling; -using OpenTK; -using OpenTK.Graphics; +using osuTK; +using osuTK.Graphics; namespace osu.Game.Rulesets.Mania.UI.Components { diff --git a/osu.Game.Rulesets.Mania/UI/HitExplosion.cs b/osu.Game.Rulesets.Mania/UI/HitExplosion.cs index c74745868a..817b60d14a 100644 --- a/osu.Game.Rulesets.Mania/UI/HitExplosion.cs +++ b/osu.Game.Rulesets.Mania/UI/HitExplosion.cs @@ -1,7 +1,7 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using OpenTK.Graphics; +using osuTK.Graphics; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -9,7 +9,7 @@ using osu.Framework.MathUtils; using osu.Game.Rulesets.Mania.Objects.Drawables; using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces; using osu.Game.Rulesets.Objects.Drawables; -using OpenTK; +using osuTK; namespace osu.Game.Rulesets.Mania.UI { diff --git a/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs b/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs index 5c3a618a19..0caac39f21 100644 --- a/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs +++ b/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs @@ -1,23 +1,25 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // 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 System; using System.Collections.Generic; +using System.Linq; using osu.Game.Rulesets.Mania.Beatmaps; -using osu.Game.Rulesets.Mania.Configuration; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Objects.Drawables; -using OpenTK; +using osu.Game.Rulesets.UI.Scrolling; +using osuTK; namespace osu.Game.Rulesets.Mania.UI { - public class ManiaPlayfield : ManiaScrollingPlayfield + public class ManiaPlayfield : ScrollingPlayfield { private readonly List stages = new List(); + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => stages.Any(s => s.ReceivePositionalInputAt(screenSpacePos)); + public ManiaPlayfield(List stageDefinitions) { if (stageDefinitions == null) @@ -41,7 +43,6 @@ namespace osu.Game.Rulesets.Mania.UI for (int i = 0; i < stageDefinitions.Count; i++) { var newStage = new ManiaStage(firstColumnIndex, stageDefinitions[i], ref normalColumnAction, ref specialColumnAction); - newStage.VisibleTimeRange.BindTo(VisibleTimeRange); playfieldGrid.Content[0][i] = newStage; @@ -54,8 +55,42 @@ namespace osu.Game.Rulesets.Mania.UI public override void Add(DrawableHitObject h) => getStageByColumn(((ManiaHitObject)h.HitObject).Column).Add(h); + public override bool Remove(DrawableHitObject h) => getStageByColumn(((ManiaHitObject)h.HitObject).Column).Remove(h); + public void Add(BarLine barline) => stages.ForEach(s => s.Add(barline)); + /// + /// Retrieves a column from a screen-space position. + /// + /// The screen-space position. + /// The column which the lies in. + public Column GetColumnByPosition(Vector2 screenSpacePosition) + { + Column found = null; + + foreach (var stage in stages) + { + foreach (var column in stage.Columns) + { + if (column.ReceivePositionalInputAt(screenSpacePosition)) + { + found = column; + break; + } + } + + if (found != null) + break; + } + + return found; + } + + /// + /// Retrieves the total amount of columns across all stages in this playfield. + /// + public int TotalColumns => stages.Sum(s => s.Columns.Count); + private ManiaStage getStageByColumn(int column) { int sum = 0; @@ -68,11 +103,5 @@ namespace osu.Game.Rulesets.Mania.UI return null; } - - [BackgroundDependencyLoader] - private void load(ManiaConfigManager maniaConfig) - { - maniaConfig.BindWith(ManiaSetting.ScrollTime, VisibleTimeRange); - } } } diff --git a/osu.Game.Rulesets.Mania/UI/ManiaRulesetContainer.cs b/osu.Game.Rulesets.Mania/UI/ManiaRulesetContainer.cs index 49874f6dc1..db65dfae41 100644 --- a/osu.Game.Rulesets.Mania/UI/ManiaRulesetContainer.cs +++ b/osu.Game.Rulesets.Mania/UI/ManiaRulesetContainer.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; +using osu.Framework.Configuration; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Input; @@ -11,6 +12,7 @@ using osu.Framework.MathUtils; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Input.Handlers; +using osu.Game.Replays; using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.Configuration; using osu.Game.Rulesets.Mania.Mods; @@ -20,10 +22,10 @@ using osu.Game.Rulesets.Mania.Replays; using osu.Game.Rulesets.Mania.Scoring; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Types; -using osu.Game.Rulesets.Replays; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI.Scrolling; +using osuTK; namespace osu.Game.Rulesets.Mania.UI { @@ -35,6 +37,8 @@ namespace osu.Game.Rulesets.Mania.UI protected new ManiaConfigManager Config => (ManiaConfigManager)base.Config; + private readonly Bindable configDirection = new Bindable(); + public ManiaRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap) : base(ruleset, beatmap) { @@ -70,19 +74,19 @@ namespace osu.Game.Rulesets.Mania.UI private void load() { BarLines.ForEach(Playfield.Add); + + Config.BindWith(ManiaSetting.ScrollDirection, configDirection); + configDirection.BindValueChanged(v => Direction.Value = (ScrollingDirection)v, true); + + Config.BindWith(ManiaSetting.ScrollTime, TimeRange); } - private DependencyContainer dependencies; - - protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) - { - dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); - - if (dependencies.Get() == null) - dependencies.CacheAs(new ManiaScrollingInfo(Config)); - - return dependencies; - } + /// + /// Retrieves the column that intersects a screen-space position. + /// + /// The screen-space position. + /// The column which intersects with . + public Column GetColumnByPosition(Vector2 screenSpacePosition) => Playfield.GetColumnByPosition(screenSpacePosition); protected override Playfield CreatePlayfield() => new ManiaPlayfield(Beatmap.Stages) { @@ -96,7 +100,7 @@ namespace osu.Game.Rulesets.Mania.UI public override PassThroughInputManager CreateInputManager() => new ManiaInputManager(Ruleset.RulesetInfo, Variant); - protected override DrawableHitObject GetVisualRepresentation(ManiaHitObject h) + public override DrawableHitObject GetVisualRepresentation(ManiaHitObject h) { switch (h) { diff --git a/osu.Game.Rulesets.Mania/UI/ManiaScrollingInfo.cs b/osu.Game.Rulesets.Mania/UI/ManiaScrollingInfo.cs deleted file mode 100644 index 624ea13e1b..0000000000 --- a/osu.Game.Rulesets.Mania/UI/ManiaScrollingInfo.cs +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Configuration; -using osu.Game.Rulesets.Mania.Configuration; -using osu.Game.Rulesets.UI.Scrolling; - -namespace osu.Game.Rulesets.Mania.UI -{ - public class ManiaScrollingInfo : IScrollingInfo - { - private readonly Bindable configDirection = new Bindable(); - - public readonly Bindable Direction = new Bindable(); - IBindable IScrollingInfo.Direction => Direction; - - public ManiaScrollingInfo(ManiaConfigManager config) - { - config.BindWith(ManiaSetting.ScrollDirection, configDirection); - configDirection.BindValueChanged(v => Direction.Value = (ScrollingDirection)v, true); - } - } -} diff --git a/osu.Game.Rulesets.Mania/UI/ManiaScrollingPlayfield.cs b/osu.Game.Rulesets.Mania/UI/ManiaScrollingPlayfield.cs deleted file mode 100644 index 8ee0fbf7fe..0000000000 --- a/osu.Game.Rulesets.Mania/UI/ManiaScrollingPlayfield.cs +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Allocation; -using osu.Framework.Configuration; -using osu.Game.Rulesets.UI.Scrolling; - -namespace osu.Game.Rulesets.Mania.UI -{ - public abstract class ManiaScrollingPlayfield : ScrollingPlayfield - { - private readonly IBindable direction = new Bindable(); - - [BackgroundDependencyLoader] - private void load(IScrollingInfo scrollingInfo) - { - direction.BindTo(scrollingInfo.Direction); - direction.BindValueChanged(direction => Direction.Value = direction, true); - } - } -} diff --git a/osu.Game.Rulesets.Mania/UI/ManiaStage.cs b/osu.Game.Rulesets.Mania/UI/ManiaStage.cs index 8cf49686b9..f71b866912 100644 --- a/osu.Game.Rulesets.Mania/UI/ManiaStage.cs +++ b/osu.Game.Rulesets.Mania/UI/ManiaStage.cs @@ -15,16 +15,18 @@ using osu.Game.Rulesets.Mania.Objects.Drawables; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI.Scrolling; -using OpenTK; -using OpenTK.Graphics; +using osuTK; +using osuTK.Graphics; namespace osu.Game.Rulesets.Mania.UI { /// /// A collection of s. /// - public class ManiaStage : ManiaScrollingPlayfield + public class ManiaStage : ScrollingPlayfield { + public const float COLUMN_SPACING = 1; + public const float HIT_TARGET_POSITION = 50; public IReadOnlyList Columns => columnFlow.Children; @@ -40,6 +42,8 @@ namespace osu.Game.Rulesets.Mania.UI private List normalColumnColours = new List(); private Color4 specialColumnColour; + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => Columns.Any(c => c.ReceivePositionalInputAt(screenSpacePos)); + private readonly int firstColumnIndex; public ManiaStage(int firstColumnIndex, StageDefinition definition, ref ManiaAction normalColumnStartAction, ref ManiaAction specialColumnStartAction) @@ -84,8 +88,8 @@ namespace osu.Game.Rulesets.Mania.UI RelativeSizeAxes = Axes.Y, AutoSizeAxes = Axes.X, Direction = FillDirection.Horizontal, - Padding = new MarginPadding { Left = 1, Right = 1 }, - Spacing = new Vector2(1, 0) + Padding = new MarginPadding { Left = COLUMN_SPACING, Right = COLUMN_SPACING }, + Spacing = new Vector2(COLUMN_SPACING, 0) }, } }, @@ -123,7 +127,7 @@ namespace osu.Game.Rulesets.Mania.UI for (int i = 0; i < definition.Columns; i++) { var isSpecial = definition.IsSpecialColumn(i); - var column = new Column + var column = new Column(firstColumnIndex + i) { IsSpecial = isSpecial, Action = { Value = isSpecial ? specialColumnStartAction++ : normalColumnStartAction++ } @@ -144,8 +148,6 @@ namespace osu.Game.Rulesets.Mania.UI public void AddColumn(Column c) { - c.VisibleTimeRange.BindTo(VisibleTimeRange); - topLevelContainer.Add(c.TopLevelContainer.CreateProxy()); columnFlow.Add(c); AddNested(c); @@ -154,11 +156,31 @@ namespace osu.Game.Rulesets.Mania.UI public override void Add(DrawableHitObject h) { var maniaObject = (ManiaHitObject)h.HitObject; - int columnIndex = maniaObject.Column - firstColumnIndex; - Columns.ElementAt(columnIndex).Add(h); + + int columnIndex = -1; + + maniaObject.ColumnBindable.BindValueChanged(_ => + { + if (columnIndex != -1) + Columns.ElementAt(columnIndex).Remove(h); + + columnIndex = maniaObject.Column - firstColumnIndex; + Columns.ElementAt(columnIndex).Add(h); + }, true); + h.OnNewResult += OnNewResult; } + public override bool Remove(DrawableHitObject h) + { + var maniaObject = (ManiaHitObject)h.HitObject; + int columnIndex = maniaObject.Column - firstColumnIndex; + Columns.ElementAt(columnIndex).Remove(h); + + h.OnNewResult -= OnNewResult; + return true; + } + public void Add(BarLine barline) => base.Add(new DrawableBarLine(barline)); internal void OnNewResult(DrawableHitObject judgedObject, JudgementResult result) diff --git a/osu.Game.Rulesets.Osu.Tests/TestCaseHitCircle.cs b/osu.Game.Rulesets.Osu.Tests/TestCaseHitCircle.cs index 6b67188791..1c1a027411 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestCaseHitCircle.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestCaseHitCircle.cs @@ -8,7 +8,7 @@ using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Tests.Visual; -using OpenTK; +using osuTK; using System.Collections.Generic; using System; using osu.Game.Rulesets.Mods; diff --git a/osu.Game.Rulesets.Osu.Tests/TestCaseHitCirclePlacementBlueprint.cs b/osu.Game.Rulesets.Osu.Tests/TestCaseHitCirclePlacementBlueprint.cs new file mode 100644 index 0000000000..313438a337 --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests/TestCaseHitCirclePlacementBlueprint.cs @@ -0,0 +1,19 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Objects.Drawables; +using osu.Game.Tests.Visual; + +namespace osu.Game.Rulesets.Osu.Tests +{ + public class TestCaseHitCirclePlacementBlueprint : PlacementBlueprintTestCase + { + protected override DrawableHitObject CreateHitObject(HitObject hitObject) => new DrawableHitCircle((HitCircle)hitObject); + protected override PlacementBlueprint CreateBlueprint() => new HitCirclePlacementBlueprint(); + } +} diff --git a/osu.Game.Rulesets.Osu.Tests/TestCaseHitCircleSelectionBlueprint.cs b/osu.Game.Rulesets.Osu.Tests/TestCaseHitCircleSelectionBlueprint.cs new file mode 100644 index 0000000000..fed62188ab --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests/TestCaseHitCircleSelectionBlueprint.cs @@ -0,0 +1,29 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Objects.Drawables; +using osu.Game.Tests.Visual; +using osuTK; + +namespace osu.Game.Rulesets.Osu.Tests +{ + public class TestCaseHitCircleSelectionBlueprint : SelectionBlueprintTestCase + { + private readonly DrawableHitCircle drawableObject; + + public TestCaseHitCircleSelectionBlueprint() + { + var hitCircle = new HitCircle { Position = new Vector2(256, 192) }; + hitCircle.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty { CircleSize = 2 }); + + Add(drawableObject = new DrawableHitCircle(hitCircle)); + } + + protected override SelectionBlueprint CreateBlueprint() => new HitCircleSelectionBlueprint(drawableObject); + } +} diff --git a/osu.Game.Rulesets.Osu.Tests/TestCaseSlider.cs b/osu.Game.Rulesets.Osu.Tests/TestCaseSlider.cs index 300ac16155..1895913917 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestCaseSlider.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestCaseSlider.cs @@ -11,13 +11,14 @@ using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Tests.Visual; -using OpenTK; -using OpenTK.Graphics; +using osuTK; +using osuTK.Graphics; using osu.Game.Rulesets.Mods; using System.Linq; using NUnit.Framework; using osu.Game.Graphics.Sprites; using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces; @@ -108,15 +109,14 @@ namespace osu.Game.Rulesets.Osu.Tests { StartTime = Time.Current + 1000, Position = new Vector2(239, 176), - ControlPoints = new[] + Path = new SliderPath(PathType.PerfectCurve, new[] { Vector2.Zero, new Vector2(154, 28), new Vector2(52, -34) - }, - Distance = 700, + }, 700), RepeatCount = repeats, - RepeatSamples = createEmptySamples(repeats), + NodeSamples = createEmptySamples(repeats), StackHeight = 10 }; @@ -141,14 +141,13 @@ namespace osu.Game.Rulesets.Osu.Tests { StartTime = Time.Current + 1000, Position = new Vector2(-(distance / 2), 0), - ControlPoints = new[] + Path = new SliderPath(PathType.PerfectCurve, new[] { Vector2.Zero, new Vector2(distance, 0), - }, - Distance = distance, + }, distance), RepeatCount = repeats, - RepeatSamples = createEmptySamples(repeats), + NodeSamples = createEmptySamples(repeats), StackHeight = stackHeight }; @@ -161,15 +160,14 @@ namespace osu.Game.Rulesets.Osu.Tests { StartTime = Time.Current + 1000, Position = new Vector2(-200, 0), - ControlPoints = new[] + Path = new SliderPath(PathType.PerfectCurve, new[] { Vector2.Zero, new Vector2(200, 200), new Vector2(400, 0) - }, - Distance = 600, + }, 600), RepeatCount = repeats, - RepeatSamples = createEmptySamples(repeats) + NodeSamples = createEmptySamples(repeats) }; addSlider(slider, 2, 3); @@ -181,10 +179,9 @@ namespace osu.Game.Rulesets.Osu.Tests { var slider = new Slider { - CurveType = CurveType.Linear, StartTime = Time.Current + 1000, Position = new Vector2(-200, 0), - ControlPoints = new[] + Path = new SliderPath(PathType.Linear, new[] { Vector2.Zero, new Vector2(150, 75), @@ -192,10 +189,9 @@ namespace osu.Game.Rulesets.Osu.Tests new Vector2(300, -200), new Vector2(400, 0), new Vector2(430, 0) - }, - Distance = 793.4417, + }), RepeatCount = repeats, - RepeatSamples = createEmptySamples(repeats) + NodeSamples = createEmptySamples(repeats) }; addSlider(slider, 2, 3); @@ -207,20 +203,18 @@ namespace osu.Game.Rulesets.Osu.Tests { var slider = new Slider { - CurveType = CurveType.Bezier, StartTime = Time.Current + 1000, Position = new Vector2(-200, 0), - ControlPoints = new[] + Path = new SliderPath(PathType.Bezier, new[] { Vector2.Zero, new Vector2(150, 75), new Vector2(200, 100), new Vector2(300, -200), new Vector2(430, 0) - }, - Distance = 480, + }), RepeatCount = repeats, - RepeatSamples = createEmptySamples(repeats) + NodeSamples = createEmptySamples(repeats) }; addSlider(slider, 2, 3); @@ -232,10 +226,9 @@ namespace osu.Game.Rulesets.Osu.Tests { var slider = new Slider { - CurveType = CurveType.Linear, StartTime = Time.Current + 1000, Position = new Vector2(0, 0), - ControlPoints = new[] + Path = new SliderPath(PathType.Linear, new[] { Vector2.Zero, new Vector2(-200, 0), @@ -243,10 +236,9 @@ namespace osu.Game.Rulesets.Osu.Tests new Vector2(0, -200), new Vector2(-200, -200), new Vector2(0, -200) - }, - Distance = 1000, + }), RepeatCount = repeats, - RepeatSamples = createEmptySamples(repeats) + NodeSamples = createEmptySamples(repeats) }; addSlider(slider, 2, 3); @@ -264,17 +256,15 @@ namespace osu.Game.Rulesets.Osu.Tests { StartTime = Time.Current + 1000, Position = new Vector2(-100, 0), - CurveType = CurveType.Catmull, - ControlPoints = new[] + Path = new SliderPath(PathType.Catmull, new[] { Vector2.Zero, new Vector2(50, -50), new Vector2(150, 50), new Vector2(200, 0) - }, - Distance = 300, + }), RepeatCount = repeats, - RepeatSamples = repeatSamples + NodeSamples = repeatSamples }; addSlider(slider, 3, 1); diff --git a/osu.Game.Rulesets.Osu.Tests/TestCaseSliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu.Tests/TestCaseSliderPlacementBlueprint.cs new file mode 100644 index 0000000000..1f693ad9f4 --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests/TestCaseSliderPlacementBlueprint.cs @@ -0,0 +1,19 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Objects.Drawables; +using osu.Game.Tests.Visual; + +namespace osu.Game.Rulesets.Osu.Tests +{ + public class TestCaseSliderPlacementBlueprint : PlacementBlueprintTestCase + { + protected override DrawableHitObject CreateHitObject(HitObject hitObject) => new DrawableSlider((Slider)hitObject); + protected override PlacementBlueprint CreateBlueprint() => new SliderPlacementBlueprint(); + } +} diff --git a/osu.Game.Rulesets.Osu.Tests/TestCaseSliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu.Tests/TestCaseSliderSelectionBlueprint.cs new file mode 100644 index 0000000000..cd07369ccf --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests/TestCaseSliderSelectionBlueprint.cs @@ -0,0 +1,54 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; +using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders; +using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Objects.Drawables; +using osu.Game.Tests.Visual; +using osuTK; + +namespace osu.Game.Rulesets.Osu.Tests +{ + public class TestCaseSliderSelectionBlueprint : SelectionBlueprintTestCase + { + public override IReadOnlyList RequiredTypes => new[] + { + typeof(SliderSelectionBlueprint), + typeof(SliderCircleSelectionBlueprint), + typeof(SliderBodyPiece), + typeof(SliderCircle), + typeof(PathControlPointVisualiser), + typeof(PathControlPointPiece) + }; + + private readonly DrawableSlider drawableObject; + + public TestCaseSliderSelectionBlueprint() + { + var slider = new Slider + { + Position = new Vector2(256, 192), + Path = new SliderPath(PathType.Bezier, new[] + { + Vector2.Zero, + new Vector2(150, 150), + new Vector2(300, 0) + }) + }; + + slider.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty { CircleSize = 2 }); + + Add(drawableObject = new DrawableSlider(slider)); + } + + protected override SelectionBlueprint CreateBlueprint() => new SliderSelectionBlueprint(drawableObject); + } +} diff --git a/osu.Game.Rulesets.Osu.Tests/TestCaseSpinnerPlacementBlueprint.cs b/osu.Game.Rulesets.Osu.Tests/TestCaseSpinnerPlacementBlueprint.cs new file mode 100644 index 0000000000..9a90be2582 --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests/TestCaseSpinnerPlacementBlueprint.cs @@ -0,0 +1,20 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Objects.Drawables; +using osu.Game.Tests.Visual; + +namespace osu.Game.Rulesets.Osu.Tests +{ + public class TestCaseSpinnerPlacementBlueprint : PlacementBlueprintTestCase + { + protected override DrawableHitObject CreateHitObject(HitObject hitObject) => new DrawableSpinner((Spinner)hitObject); + + protected override PlacementBlueprint CreateBlueprint() => new SpinnerPlacementBlueprint(); + } +} diff --git a/osu.Game.Rulesets.Osu.Tests/TestCaseSpinnerSelectionBlueprint.cs b/osu.Game.Rulesets.Osu.Tests/TestCaseSpinnerSelectionBlueprint.cs new file mode 100644 index 0000000000..d7ba5d8105 --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests/TestCaseSpinnerSelectionBlueprint.cs @@ -0,0 +1,50 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners; +using osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners.Components; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Objects.Drawables; +using osu.Game.Tests.Visual; +using osuTK; + +namespace osu.Game.Rulesets.Osu.Tests +{ + public class TestCaseSpinnerSelectionBlueprint : SelectionBlueprintTestCase + { + public override IReadOnlyList RequiredTypes => new[] + { + typeof(SpinnerSelectionBlueprint), + typeof(SpinnerPiece) + }; + + private readonly DrawableSpinner drawableSpinner; + + public TestCaseSpinnerSelectionBlueprint() + { + var spinner = new Spinner + { + Position = new Vector2(256, 256), + StartTime = -1000, + EndTime = 2000 + }; + spinner.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty { CircleSize = 2 }); + + Add(new Container + { + RelativeSizeAxes = Axes.Both, + Size = new Vector2(0.5f), + Child = drawableSpinner = new DrawableSpinner(spinner) + }); + } + + protected override SelectionBlueprint CreateBlueprint() => new SpinnerSelectionBlueprint(drawableSpinner) { Size = new Vector2(0.5f) }; + } +} diff --git a/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj b/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj index 23c6150b6a..6117812f45 100644 --- a/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj +++ b/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj @@ -4,7 +4,7 @@ - + diff --git a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs index b2914d4b82..510c32fd9f 100644 --- a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs +++ b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs @@ -1,7 +1,7 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using OpenTK; +using osuTK; using osu.Game.Beatmaps; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Objects; @@ -35,10 +35,8 @@ namespace osu.Game.Rulesets.Osu.Beatmaps { StartTime = original.StartTime, Samples = original.Samples, - ControlPoints = curveData.ControlPoints, - CurveType = curveData.CurveType, - Distance = curveData.Distance, - RepeatSamples = curveData.RepeatSamples, + Path = curveData.Path, + NodeSamples = curveData.NodeSamples, RepeatCount = curveData.RepeatCount, Position = positionData?.Position ?? Vector2.Zero, NewCombo = comboData?.NewCombo ?? false, diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index b0887ac72b..5e66d43c98 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -10,6 +10,7 @@ using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Scoring; +using osu.Game.Scoring; namespace osu.Game.Rulesets.Osu.Difficulty { @@ -29,7 +30,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty private int countMeh; private int countMiss; - public OsuPerformanceCalculator(Ruleset ruleset, WorkingBeatmap beatmap, Score score) + public OsuPerformanceCalculator(Ruleset ruleset, WorkingBeatmap beatmap, ScoreInfo score) : base(ruleset, beatmap, score) { countHitCircles = Beatmap.HitObjects.Count(h => h is HitCircle); diff --git a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs index d6684f55af..d8e3b340c9 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs @@ -4,7 +4,7 @@ using System; using System.Linq; using osu.Game.Rulesets.Osu.Objects; -using OpenTK; +using osuTK; namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing { @@ -108,7 +108,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing progress = progress % 1; // ReSharper disable once PossibleInvalidOperationException (bugged in current r# version) - var diff = slider.StackedPosition + slider.Curve.PositionAt(progress) - slider.LazyEndPosition.Value; + var diff = slider.StackedPosition + slider.Path.PositionAt(progress) - slider.LazyEndPosition.Value; float dist = diff.Length; if (dist > approxFollowCircleRadius) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/Components/HitCirclePiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/Components/HitCirclePiece.cs new file mode 100644 index 0000000000..de219ed5a5 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/Components/HitCirclePiece.cs @@ -0,0 +1,42 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Game.Graphics; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces; +using osuTK; + +namespace osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components +{ + public class HitCirclePiece : HitObjectPiece + { + private readonly HitCircle hitCircle; + + public HitCirclePiece(HitCircle hitCircle) + : base(hitCircle) + { + this.hitCircle = hitCircle; + Origin = Anchor.Centre; + + Size = new Vector2((float)OsuHitObject.OBJECT_RADIUS * 2); + Scale = new Vector2(hitCircle.Scale); + CornerRadius = Size.X / 2; + + InternalChild = new RingPiece(); + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + Colour = colours.Yellow; + + PositionBindable.BindValueChanged(_ => UpdatePosition(), true); + StackHeightBindable.BindValueChanged(_ => UpdatePosition()); + ScaleBindable.BindValueChanged(v => Scale = new Vector2(v), true); + } + + protected virtual void UpdatePosition() => Position = hitCircle.StackedPosition; + } +} diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/HitCirclePlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/HitCirclePlacementBlueprint.cs new file mode 100644 index 0000000000..eddd399a4a --- /dev/null +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/HitCirclePlacementBlueprint.cs @@ -0,0 +1,42 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Input.Events; +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components; +using osu.Game.Rulesets.Osu.Objects; + +namespace osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles +{ + public class HitCirclePlacementBlueprint : PlacementBlueprint + { + public new HitCircle HitObject => (HitCircle)base.HitObject; + + public HitCirclePlacementBlueprint() + : base(new HitCircle()) + { + InternalChild = new HitCirclePiece(HitObject); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + // Fixes a 1-frame position discrpancy due to the first mouse move event happening in the next frame + HitObject.Position = GetContainingInputManager().CurrentState.Mouse.Position; + } + + protected override bool OnClick(ClickEvent e) + { + HitObject.StartTime = EditorClock.CurrentTime; + EndPlacement(); + return true; + } + + protected override bool OnMouseMove(MouseMoveEvent e) + { + HitObject.Position = e.MousePosition; + return true; + } + } +} diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/HitCircleSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/HitCircleSelectionBlueprint.cs new file mode 100644 index 0000000000..a59dac1834 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/HitCircleSelectionBlueprint.cs @@ -0,0 +1,18 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Objects.Drawables; + +namespace osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles +{ + public class HitCircleSelectionBlueprint : OsuSelectionBlueprint + { + public HitCircleSelectionBlueprint(DrawableHitCircle hitCircle) + : base(hitCircle) + { + InternalChild = new HitCirclePiece((HitCircle)hitCircle.HitObject); + } + } +} diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/HitObjectPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/HitObjectPiece.cs new file mode 100644 index 0000000000..882a9a3d81 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/HitObjectPiece.cs @@ -0,0 +1,36 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Allocation; +using osu.Framework.Configuration; +using osu.Framework.Graphics.Containers; +using osu.Game.Rulesets.Osu.Objects; +using osuTK; + +namespace osu.Game.Rulesets.Osu.Edit.Blueprints +{ + /// + /// A piece of a blueprint which responds to changes in the state of a . + /// + public abstract class HitObjectPiece : CompositeDrawable + { + protected readonly IBindable PositionBindable = new Bindable(); + protected readonly IBindable StackHeightBindable = new Bindable(); + protected readonly IBindable ScaleBindable = new Bindable(); + + private readonly OsuHitObject hitObject; + + protected HitObjectPiece(OsuHitObject hitObject) + { + this.hitObject = hitObject; + } + + [BackgroundDependencyLoader] + private void load() + { + PositionBindable.BindTo(hitObject.PositionBindable); + StackHeightBindable.BindTo(hitObject.StackHeightBindable); + ScaleBindable.BindTo(hitObject.ScaleBindable); + } + } +} diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/OsuSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/OsuSelectionBlueprint.cs new file mode 100644 index 0000000000..fab9c27c6d --- /dev/null +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/OsuSelectionBlueprint.cs @@ -0,0 +1,19 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Osu.Objects; + +namespace osu.Game.Rulesets.Osu.Edit.Blueprints +{ + public class OsuSelectionBlueprint : SelectionBlueprint + { + protected OsuHitObject OsuObject => (OsuHitObject)HitObject.HitObject; + + public OsuSelectionBlueprint(DrawableHitObject hitObject) + : base(hitObject) + { + } + } +} diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/SliderPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/SliderPiece.cs new file mode 100644 index 0000000000..ef7254d9c9 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/SliderPiece.cs @@ -0,0 +1,32 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Allocation; +using osu.Framework.Configuration; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Osu.Objects; + +namespace osu.Game.Rulesets.Osu.Edit.Blueprints +{ + /// + /// A piece of a blueprint which responds to changes in the state of a . + /// + public abstract class SliderPiece : HitObjectPiece + { + protected readonly IBindable PathBindable = new Bindable(); + + private readonly Slider slider; + + protected SliderPiece(Slider slider) + : base(slider) + { + this.slider = slider; + } + + [BackgroundDependencyLoader] + private void load() + { + PathBindable.BindTo(slider.PathBindable); + } + } +} diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs new file mode 100644 index 0000000000..18efe21404 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs @@ -0,0 +1,112 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// 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.Lines; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Input.Events; +using osu.Game.Graphics; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Osu.Objects; +using osuTK; + +namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components +{ + public class PathControlPointPiece : CompositeDrawable + { + private readonly Slider slider; + private readonly int index; + + private readonly Path path; + private readonly CircularContainer marker; + + [Resolved] + private OsuColour colours { get; set; } + + public PathControlPointPiece(Slider slider, int index) + { + this.slider = slider; + this.index = index; + + Origin = Anchor.Centre; + AutoSizeAxes = Axes.Both; + + InternalChildren = new Drawable[] + { + path = new SmoothPath + { + Anchor = Anchor.Centre, + PathWidth = 1 + }, + marker = new CircularContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(10), + Masking = true, + Child = new Box { RelativeSizeAxes = Axes.Both } + } + }; + } + + protected override void Update() + { + base.Update(); + + Position = slider.StackedPosition + slider.Path.ControlPoints[index]; + + marker.Colour = isSegmentSeparator ? colours.Red : colours.Yellow; + + path.ClearVertices(); + + if (index != slider.Path.ControlPoints.Length - 1) + { + path.AddVertex(Vector2.Zero); + path.AddVertex(slider.Path.ControlPoints[index + 1] - slider.Path.ControlPoints[index]); + } + + path.OriginPosition = path.PositionInBoundingBox(Vector2.Zero); + } + + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => marker.ReceivePositionalInputAt(screenSpacePos); + + protected override bool OnDragStart(DragStartEvent e) => true; + + protected override bool OnDrag(DragEvent e) + { + var newControlPoints = slider.Path.ControlPoints.ToArray(); + + if (index == 0) + { + // Special handling for the head - only the position of the slider changes + slider.Position += e.Delta; + + // Since control points are relative to the position of the slider, they all need to be offset backwards by the delta + for (int i = 1; i < newControlPoints.Length; i++) + newControlPoints[i] -= e.Delta; + } + else + newControlPoints[index] += e.Delta; + + if (isSegmentSeparatorWithNext) + newControlPoints[index + 1] = newControlPoints[index]; + + if (isSegmentSeparatorWithPrevious) + newControlPoints[index - 1] = newControlPoints[index]; + + slider.Path = new SliderPath(slider.Path.Type, newControlPoints); + + return true; + } + + protected override bool OnDragEnd(DragEndEvent e) => true; + + private bool isSegmentSeparator => isSegmentSeparatorWithNext || isSegmentSeparatorWithPrevious; + + private bool isSegmentSeparatorWithNext => index < slider.Path.ControlPoints.Length - 1 && slider.Path.ControlPoints[index + 1] == slider.Path.ControlPoints[index]; + + private bool isSegmentSeparatorWithPrevious => index > 0 && slider.Path.ControlPoints[index - 1] == slider.Path.ControlPoints[index]; + } +} diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs new file mode 100644 index 0000000000..0089c2dddd --- /dev/null +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs @@ -0,0 +1,39 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// 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.Game.Rulesets.Osu.Objects; + +namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components +{ + public class PathControlPointVisualiser : SliderPiece + { + private readonly Slider slider; + + private readonly Container pieces; + + public PathControlPointVisualiser(Slider slider) + : base(slider) + { + this.slider = slider; + + InternalChild = pieces = new Container { RelativeSizeAxes = Axes.Both }; + } + + [BackgroundDependencyLoader] + private void load() + { + PathBindable.BindValueChanged(_ => updatePathControlPoints(), true); + } + + private void updatePathControlPoints() + { + while (slider.Path.ControlPoints.Length > pieces.Count) + pieces.Add(new PathControlPointPiece(slider, pieces.Count)); + while (slider.Path.ControlPoints.Length < pieces.Count) + pieces.Remove(pieces[pieces.Count - 1]); + } + } +} diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderBodyPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderBodyPiece.cs new file mode 100644 index 0000000000..5ea85676b5 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderBodyPiece.cs @@ -0,0 +1,55 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Collections.Generic; +using osu.Framework.Allocation; +using osu.Game.Graphics; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components +{ + public class SliderBodyPiece : SliderPiece + { + private readonly Slider slider; + private readonly ManualSliderBody body; + + public SliderBodyPiece(Slider slider) + : base(slider) + { + this.slider = slider; + + InternalChild = body = new ManualSliderBody + { + AccentColour = Color4.Transparent, + PathWidth = slider.Scale * 64 + }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + body.BorderColour = colours.Yellow; + + PositionBindable.BindValueChanged(_ => updatePosition(), true); + ScaleBindable.BindValueChanged(v => body.PathWidth = v * 64, true); + } + + private void updatePosition() => Position = slider.StackedPosition; + + protected override void Update() + { + base.Update(); + + var vertices = new List(); + slider.Path.GetPathToProgress(vertices, 0, 1); + + body.SetVertices(vertices); + + Size = body.Size; + OriginPosition = body.PathOffset; + } + } +} diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderCirclePiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderCirclePiece.cs new file mode 100644 index 0000000000..205ac6bea3 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderCirclePiece.cs @@ -0,0 +1,46 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Allocation; +using osu.Framework.Configuration; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components; +using osu.Game.Rulesets.Osu.Objects; + +namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components +{ + public class SliderCirclePiece : HitCirclePiece + { + private readonly IBindable pathBindable = new Bindable(); + + private readonly Slider slider; + private readonly SliderPosition position; + + public SliderCirclePiece(Slider slider, SliderPosition position) + : base(slider.HeadCircle) + { + this.slider = slider; + this.position = position; + } + + [BackgroundDependencyLoader] + private void load() + { + pathBindable.BindTo(slider.PathBindable); + pathBindable.BindValueChanged(_ => UpdatePosition(), true); + } + + protected override void UpdatePosition() + { + switch (position) + { + case SliderPosition.Start: + Position = slider.StackedPosition + slider.Path.PositionAt(0); + break; + case SliderPosition.End: + Position = slider.StackedPosition + slider.Path.PositionAt(1); + break; + } + } + } +} diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderCircleSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderCircleSelectionBlueprint.cs new file mode 100644 index 0000000000..32258572bf --- /dev/null +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderCircleSelectionBlueprint.cs @@ -0,0 +1,23 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Objects.Drawables; + +namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders +{ + public class SliderCircleSelectionBlueprint : OsuSelectionBlueprint + { + public SliderCircleSelectionBlueprint(DrawableOsuHitObject hitObject, Slider slider, SliderPosition position) + : base(hitObject) + { + InternalChild = new SliderCirclePiece(slider, position); + + Select(); + } + + // Todo: This is temporary, since the slider circle masks don't do anything special yet. In the future they will handle input. + public override bool HandlePositionalInput => false; + } +} diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs new file mode 100644 index 0000000000..ba3501cb2b --- /dev/null +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs @@ -0,0 +1,146 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// 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.Extensions.IEnumerableExtensions; +using osu.Framework.Graphics; +using osu.Framework.Input.Events; +using osu.Game.Graphics; +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; +using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components; +using osuTK; +using osuTK.Input; + +namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders +{ + public class SliderPlacementBlueprint : PlacementBlueprint + { + public new Objects.Slider HitObject => (Objects.Slider)base.HitObject; + + private readonly List segments = new List(); + private Vector2 cursor; + + private PlacementState state; + + public SliderPlacementBlueprint() + : base(new Objects.Slider()) + { + RelativeSizeAxes = Axes.Both; + segments.Add(new Segment(Vector2.Zero)); + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + InternalChildren = new Drawable[] + { + new SliderBodyPiece(HitObject), + new SliderCirclePiece(HitObject, SliderPosition.Start), + new SliderCirclePiece(HitObject, SliderPosition.End), + new PathControlPointVisualiser(HitObject), + }; + + setState(PlacementState.Initial); + } + + protected override bool OnMouseMove(MouseMoveEvent e) + { + switch (state) + { + case PlacementState.Initial: + HitObject.Position = e.MousePosition; + return true; + case PlacementState.Body: + cursor = e.MousePosition - HitObject.Position; + return true; + } + + return false; + } + + protected override bool OnClick(ClickEvent e) + { + switch (state) + { + case PlacementState.Initial: + beginCurve(); + break; + case PlacementState.Body: + switch (e.Button) + { + case MouseButton.Left: + segments.Last().ControlPoints.Add(cursor); + break; + } + + break; + } + + return true; + } + + protected override bool OnMouseUp(MouseUpEvent e) + { + if (state == PlacementState.Body && e.Button == MouseButton.Right) + endCurve(); + return base.OnMouseUp(e); + } + + protected override bool OnDoubleClick(DoubleClickEvent e) + { + segments.Add(new Segment(segments[segments.Count - 1].ControlPoints.Last())); + return true; + } + + private void beginCurve() + { + BeginPlacement(); + + HitObject.StartTime = EditorClock.CurrentTime; + setState(PlacementState.Body); + } + + private void endCurve() + { + updateSlider(); + EndPlacement(); + } + + protected override void Update() + { + base.Update(); + updateSlider(); + } + + private void updateSlider() + { + var newControlPoints = segments.SelectMany(s => s.ControlPoints).Concat(cursor.Yield()).ToArray(); + HitObject.Path = new SliderPath(newControlPoints.Length > 2 ? PathType.Bezier : PathType.Linear, newControlPoints); + } + + private void setState(PlacementState newState) + { + state = newState; + } + + private enum PlacementState + { + Initial, + Body, + } + + private class Segment + { + public readonly List ControlPoints = new List(); + + public Segment(Vector2 offset) + { + ControlPoints.Add(offset); + } + } + } +} diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPosition.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPosition.cs new file mode 100644 index 0000000000..a117a7056e --- /dev/null +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPosition.cs @@ -0,0 +1,11 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders +{ + public enum SliderPosition + { + Start, + End + } +} diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs new file mode 100644 index 0000000000..2206a36738 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs @@ -0,0 +1,32 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Graphics; +using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Objects.Drawables; +using osuTK; + +namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders +{ + public class SliderSelectionBlueprint : OsuSelectionBlueprint + { + private readonly SliderCircleSelectionBlueprint headBlueprint; + + public SliderSelectionBlueprint(DrawableSlider slider) + : base(slider) + { + var sliderObject = (Slider)slider.HitObject; + + InternalChildren = new Drawable[] + { + new SliderBodyPiece(sliderObject), + headBlueprint = new SliderCircleSelectionBlueprint(slider.HeadCircle, sliderObject, SliderPosition.Start), + new SliderCircleSelectionBlueprint(slider.TailCircle, sliderObject, SliderPosition.End), + new PathControlPointVisualiser(sliderObject), + }; + } + + public override Vector2 SelectionPoint => headBlueprint.SelectionPoint; + } +} diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Spinners/Components/SpinnerPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Spinners/Components/SpinnerPiece.cs new file mode 100644 index 0000000000..af648cfa1b --- /dev/null +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Spinners/Components/SpinnerPiece.cs @@ -0,0 +1,65 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// 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.Game.Graphics; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces; +using osuTK; + +namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners.Components +{ + public class SpinnerPiece : HitObjectPiece + { + private readonly Spinner spinner; + private readonly CircularContainer circle; + private readonly RingPiece ring; + + public SpinnerPiece(Spinner spinner) + : base(spinner) + { + this.spinner = spinner; + + Origin = Anchor.Centre; + + RelativeSizeAxes = Axes.Both; + FillMode = FillMode.Fit; + Size = new Vector2(1.3f); + + InternalChildren = new Drawable[] + { + circle = new CircularContainer + { + RelativeSizeAxes = Axes.Both, + Masking = true, + Alpha = 0.5f, + Child = new Box { RelativeSizeAxes = Axes.Both } + }, + ring = new RingPiece + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre + } + }; + + ring.Scale = new Vector2(spinner.Scale); + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + Colour = colours.Yellow; + + PositionBindable.BindValueChanged(_ => updatePosition(), true); + StackHeightBindable.BindValueChanged(_ => updatePosition()); + ScaleBindable.BindValueChanged(v => ring.Scale = new Vector2(v), true); + } + + private void updatePosition() => Position = spinner.Position; + + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => circle.ReceivePositionalInputAt(screenSpacePos); + } +} diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Spinners/SpinnerPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Spinners/SpinnerPlacementBlueprint.cs new file mode 100644 index 0000000000..804a4fcba0 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Spinners/SpinnerPlacementBlueprint.cs @@ -0,0 +1,47 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Graphics; +using osu.Framework.Input.Events; +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners.Components; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.UI; + +namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners +{ + public class SpinnerPlacementBlueprint : PlacementBlueprint + { + public new Spinner HitObject => (Spinner)base.HitObject; + + private readonly SpinnerPiece piece; + + private bool isPlacingEnd; + + public SpinnerPlacementBlueprint() + : base(new Spinner { Position = OsuPlayfield.BASE_SIZE / 2 }) + { + InternalChild = piece = new SpinnerPiece(HitObject) { Alpha = 0.5f }; + } + + protected override bool OnClick(ClickEvent e) + { + if (isPlacingEnd) + { + HitObject.EndTime = EditorClock.CurrentTime; + EndPlacement(); + } + else + { + HitObject.StartTime = EditorClock.CurrentTime; + + isPlacingEnd = true; + piece.FadeTo(1f, 150, Easing.OutQuint); + + BeginPlacement(); + } + + return true; + } + } +} diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Spinners/SpinnerSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Spinners/SpinnerSelectionBlueprint.cs new file mode 100644 index 0000000000..e4bcc20f6a --- /dev/null +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Spinners/SpinnerSelectionBlueprint.cs @@ -0,0 +1,23 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners.Components; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Objects.Drawables; +using osuTK; + +namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners +{ + public class SpinnerSelectionBlueprint : OsuSelectionBlueprint + { + private readonly SpinnerPiece piece; + + public SpinnerSelectionBlueprint(DrawableSpinner spinner) + : base(spinner) + { + InternalChild = piece = new SpinnerPiece((Spinner)spinner.HitObject); + } + + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => piece.ReceivePositionalInputAt(screenSpacePos); + } +} diff --git a/osu.Game.Rulesets.Osu/Edit/HitCircleCompositionTool.cs b/osu.Game.Rulesets.Osu/Edit/HitCircleCompositionTool.cs new file mode 100644 index 0000000000..ddab70d53a --- /dev/null +++ b/osu.Game.Rulesets.Osu/Edit/HitCircleCompositionTool.cs @@ -0,0 +1,20 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Edit.Tools; +using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles; +using osu.Game.Rulesets.Osu.Objects; + +namespace osu.Game.Rulesets.Osu.Edit +{ + public class HitCircleCompositionTool : HitObjectCompositionTool + { + public HitCircleCompositionTool() + : base(nameof(HitCircle)) + { + } + + public override PlacementBlueprint CreatePlacementBlueprint() => new HitCirclePlacementBlueprint(); + } +} diff --git a/osu.Game.Rulesets.Osu/Edit/Layers/Selection/Overlays/HitCircleMask.cs b/osu.Game.Rulesets.Osu/Edit/Layers/Selection/Overlays/HitCircleMask.cs deleted file mode 100644 index a2aa639004..0000000000 --- a/osu.Game.Rulesets.Osu/Edit/Layers/Selection/Overlays/HitCircleMask.cs +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Graphics; -using osu.Framework.Allocation; -using osu.Game.Graphics; -using osu.Game.Rulesets.Edit; -using osu.Game.Rulesets.Osu.Objects.Drawables; -using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces; - -namespace osu.Game.Rulesets.Osu.Edit.Layers.Selection.Overlays -{ - public class HitCircleMask : HitObjectMask - { - public HitCircleMask(DrawableHitCircle hitCircle) - : base(hitCircle) - { - Origin = Anchor.Centre; - - Position = hitCircle.Position; - Size = hitCircle.Size; - Scale = hitCircle.Scale; - - CornerRadius = Size.X / 2; - - AddInternal(new RingPiece()); - - hitCircle.HitObject.PositionChanged += _ => Position = hitCircle.Position; - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - Colour = colours.Yellow; - } - } -} diff --git a/osu.Game.Rulesets.Osu/Edit/Layers/Selection/Overlays/SliderCircleMask.cs b/osu.Game.Rulesets.Osu/Edit/Layers/Selection/Overlays/SliderCircleMask.cs deleted file mode 100644 index 151564a2a8..0000000000 --- a/osu.Game.Rulesets.Osu/Edit/Layers/Selection/Overlays/SliderCircleMask.cs +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Game.Graphics; -using osu.Game.Rulesets.Edit; -using osu.Game.Rulesets.Osu.Objects; -using osu.Game.Rulesets.Osu.Objects.Drawables; -using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces; -using OpenTK; - -namespace osu.Game.Rulesets.Osu.Edit.Layers.Selection.Overlays -{ - public class SliderCircleMask : HitObjectMask - { - public SliderCircleMask(DrawableHitCircle sliderHead, DrawableSlider slider) - : this(sliderHead, Vector2.Zero, slider) - { - } - - public SliderCircleMask(DrawableSliderTail sliderTail, DrawableSlider slider) - : this(sliderTail, ((Slider)slider.HitObject).Curve.PositionAt(1), slider) - { - } - - private readonly DrawableOsuHitObject hitObject; - - private SliderCircleMask(DrawableOsuHitObject hitObject, Vector2 position, DrawableSlider slider) - : base(hitObject) - { - this.hitObject = hitObject; - - Origin = Anchor.Centre; - - Position = position; - Size = slider.HeadCircle.Size; - Scale = slider.HeadCircle.Scale; - - AddInternal(new RingPiece()); - - Select(); - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - Colour = colours.Yellow; - } - - protected override void Update() - { - base.Update(); - - RelativeAnchorPosition = hitObject.RelativeAnchorPosition; - } - - // Todo: This is temporary, since the slider circle masks don't do anything special yet. In the future they will handle input. - public override bool HandlePositionalInput => false; - } -} diff --git a/osu.Game.Rulesets.Osu/Edit/Layers/Selection/Overlays/SliderMask.cs b/osu.Game.Rulesets.Osu/Edit/Layers/Selection/Overlays/SliderMask.cs deleted file mode 100644 index aff42dd233..0000000000 --- a/osu.Game.Rulesets.Osu/Edit/Layers/Selection/Overlays/SliderMask.cs +++ /dev/null @@ -1,67 +0,0 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// 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.Primitives; -using osu.Game.Graphics; -using osu.Game.Rulesets.Edit; -using osu.Game.Rulesets.Osu.Objects; -using osu.Game.Rulesets.Osu.Objects.Drawables; -using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces; -using OpenTK; -using OpenTK.Graphics; - -namespace osu.Game.Rulesets.Osu.Edit.Layers.Selection.Overlays -{ - public class SliderMask : HitObjectMask - { - private readonly SliderBody body; - private readonly DrawableSlider slider; - - public SliderMask(DrawableSlider slider) - : base(slider) - { - this.slider = slider; - - Position = slider.Position; - - var sliderObject = (Slider)slider.HitObject; - - InternalChildren = new Drawable[] - { - body = new SliderBody(sliderObject) - { - AccentColour = Color4.Transparent, - PathWidth = sliderObject.Scale * 64 - }, - new SliderCircleMask(slider.HeadCircle, slider), - new SliderCircleMask(slider.TailCircle, slider), - }; - - sliderObject.PositionChanged += _ => Position = slider.Position; - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - body.BorderColour = colours.Yellow; - } - - protected override void Update() - { - base.Update(); - - Size = slider.Size; - OriginPosition = slider.OriginPosition; - - // Need to cause one update - body.UpdateProgress(0); - } - - public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => body.ReceivePositionalInputAt(screenSpacePos); - - public override Vector2 SelectionPoint => ToScreenSpace(OriginPosition); - public override Quad SelectionQuad => body.PathDrawQuad; - } -} diff --git a/osu.Game.Rulesets.Osu/Edit/OsuEditRulesetContainer.cs b/osu.Game.Rulesets.Osu/Edit/OsuEditRulesetContainer.cs index 8571de39f4..05bc7dada0 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuEditRulesetContainer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuEditRulesetContainer.cs @@ -5,7 +5,7 @@ using osu.Framework.Graphics.Cursor; using osu.Game.Beatmaps; using osu.Game.Rulesets.Osu.UI; using osu.Game.Rulesets.UI; -using OpenTK; +using osuTK; namespace osu.Game.Rulesets.Osu.Edit { diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index d6972d55d2..117af9e853 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -8,43 +8,51 @@ using osu.Game.Beatmaps; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit.Tools; using osu.Game.Rulesets.Objects.Drawables; -using osu.Game.Rulesets.Osu.Edit.Layers.Selection.Overlays; +using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles; +using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders; +using osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Rulesets.Osu.UI; using osu.Game.Rulesets.UI; +using osu.Game.Screens.Edit.Compose.Components; namespace osu.Game.Rulesets.Osu.Edit { - public class OsuHitObjectComposer : HitObjectComposer + public class OsuHitObjectComposer : HitObjectComposer { public OsuHitObjectComposer(Ruleset ruleset) : base(ruleset) { } - protected override RulesetContainer CreateRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap) => new OsuEditRulesetContainer(ruleset, beatmap); + protected override RulesetContainer CreateRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap) + => new OsuEditRulesetContainer(ruleset, beatmap); - protected override IReadOnlyList CompositionTools => new ICompositionTool[] + protected override IReadOnlyList CompositionTools => new HitObjectCompositionTool[] { - new HitObjectCompositionTool(), - new HitObjectCompositionTool(), - new HitObjectCompositionTool() + new HitCircleCompositionTool(), + new SliderCompositionTool(), + new SpinnerCompositionTool() }; + public override SelectionHandler CreateSelectionHandler() => new OsuSelectionHandler(); + protected override Container CreateLayerContainer() => new PlayfieldAdjustmentContainer { RelativeSizeAxes = Axes.Both }; - public override HitObjectMask CreateMaskFor(DrawableHitObject hitObject) + public override SelectionBlueprint CreateBlueprintFor(DrawableHitObject hitObject) { switch (hitObject) { case DrawableHitCircle circle: - return new HitCircleMask(circle); + return new HitCircleSelectionBlueprint(circle); case DrawableSlider slider: - return new SliderMask(slider); + return new SliderSelectionBlueprint(slider); + case DrawableSpinner spinner: + return new SpinnerSelectionBlueprint(spinner); } - return base.CreateMaskFor(hitObject); + return base.CreateBlueprintFor(hitObject); } } } diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs new file mode 100644 index 0000000000..0c7e571ef5 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs @@ -0,0 +1,30 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Linq; +using osu.Framework.Input.Events; +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Screens.Edit.Compose.Components; + +namespace osu.Game.Rulesets.Osu.Edit +{ + public class OsuSelectionHandler : SelectionHandler + { + public override void HandleDrag(SelectionBlueprint blueprint, DragEvent dragEvent) + { + foreach (var h in SelectedHitObjects.OfType()) + { + if (h is Spinner) + { + // Spinners don't support position adjustments + continue; + } + + h.Position += dragEvent.Delta; + } + + base.HandleDrag(blueprint, dragEvent); + } + } +} diff --git a/osu.Game.Rulesets.Osu/Edit/SliderCompositionTool.cs b/osu.Game.Rulesets.Osu/Edit/SliderCompositionTool.cs new file mode 100644 index 0000000000..6d4f67c597 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Edit/SliderCompositionTool.cs @@ -0,0 +1,20 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Edit.Tools; +using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders; +using osu.Game.Rulesets.Osu.Objects; + +namespace osu.Game.Rulesets.Osu.Edit +{ + public class SliderCompositionTool : HitObjectCompositionTool + { + public SliderCompositionTool() + : base(nameof(Slider)) + { + } + + public override PlacementBlueprint CreatePlacementBlueprint() => new SliderPlacementBlueprint(); + } +} diff --git a/osu.Game.Rulesets.Osu/Edit/SpinnerCompositionTool.cs b/osu.Game.Rulesets.Osu/Edit/SpinnerCompositionTool.cs new file mode 100644 index 0000000000..5c19a1bac0 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Edit/SpinnerCompositionTool.cs @@ -0,0 +1,20 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Edit.Tools; +using osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners; +using osu.Game.Rulesets.Osu.Objects; + +namespace osu.Game.Rulesets.Osu.Edit +{ + public class SpinnerCompositionTool : HitObjectCompositionTool + { + public SpinnerCompositionTool() + : base(nameof(Spinner)) + { + } + + public override PlacementBlueprint CreatePlacementBlueprint() => new SpinnerPlacementBlueprint(); + } +} diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs index 37d5f57fcb..4d371c2e16 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs @@ -10,7 +10,7 @@ namespace osu.Game.Rulesets.Osu.Mods public class OsuModAutopilot : Mod { public override string Name => "Autopilot"; - public override string ShortenedName => "AP"; + public override string Acronym => "AP"; public override FontAwesome Icon => FontAwesome.fa_osu_mod_autopilot; public override ModType Type => ModType.Automation; public override string Description => @"Automatic cursor movement - just follow the rhythm."; diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAutoplay.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAutoplay.cs index 1eb2f59010..7ef01e075c 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAutoplay.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAutoplay.cs @@ -7,7 +7,8 @@ using osu.Game.Beatmaps; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Replays; -using osu.Game.Rulesets.Scoring; +using osu.Game.Scoring; +using osu.Game.Users; namespace osu.Game.Rulesets.Osu.Mods { @@ -15,12 +16,10 @@ namespace osu.Game.Rulesets.Osu.Mods { public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(OsuModAutopilot)).Append(typeof(OsuModSpunOut)).ToArray(); - protected override Score CreateReplayScore(Beatmap beatmap) + protected override Score CreateReplayScore(Beatmap beatmap) => new Score { - return new Score - { - Replay = new OsuAutoGenerator(beatmap).Generate() - }; - } + ScoreInfo = new ScoreInfo { User = new User { Username = "Autoplay" } }, + Replay = new OsuAutoGenerator(beatmap).Generate() + }; } } diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs b/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs index a337439593..84a35b04fe 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs @@ -1,12 +1,52 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using osu.Framework.Graphics; +using osu.Framework.Input; +using osu.Framework.Input.Events; using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Osu.Objects; +using osuTK; namespace osu.Game.Rulesets.Osu.Mods { - public class OsuModFlashlight : ModFlashlight + public class OsuModFlashlight : ModFlashlight { public override double ScoreMultiplier => 1.12; + + private const float default_flashlight_size = 180; + + public override Flashlight CreateFlashlight() => new OsuFlashlight(); + + private class OsuFlashlight : Flashlight, IRequireHighFrequencyMousePosition + { + public OsuFlashlight() + { + FlashlightSize = new Vector2(0, getSizeFor(0)); + } + + protected override bool OnMouseMove(MouseMoveEvent e) + { + FlashlightPosition = e.MousePosition; + return base.OnMouseMove(e); + } + + private float getSizeFor(int combo) + { + if (combo > 200) + return default_flashlight_size * 0.8f; + else if (combo > 100) + return default_flashlight_size * 0.9f; + else + return default_flashlight_size; + } + + protected override void OnComboChange(int newCombo) + { + this.TransformTo(nameof(FlashlightSize), new Vector2(0, getSizeFor(newCombo)), FLASHLIGHT_FADE_DURATION); + } + + protected override string FragmentShader => "CircularFlashlight"; + } } } diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModHardRock.cs b/osu.Game.Rulesets.Osu/Mods/OsuModHardRock.cs index 1b3725a15e..32d2d40671 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModHardRock.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModHardRock.cs @@ -7,7 +7,7 @@ using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.UI; -using OpenTK; +using osuTK; namespace osu.Game.Rulesets.Osu.Mods { @@ -32,12 +32,11 @@ namespace osu.Game.Rulesets.Osu.Mods slider.NestedHitObjects.OfType().ForEach(h => h.Position = new Vector2(h.Position.X, OsuPlayfield.BASE_SIZE.Y - h.Position.Y)); slider.NestedHitObjects.OfType().ForEach(h => h.Position = new Vector2(h.Position.X, OsuPlayfield.BASE_SIZE.Y - h.Position.Y)); - var newControlPoints = new Vector2[slider.ControlPoints.Length]; - for (int i = 0; i < slider.ControlPoints.Length; i++) - newControlPoints[i] = new Vector2(slider.ControlPoints[i].X, -slider.ControlPoints[i].Y); + var newControlPoints = new Vector2[slider.Path.ControlPoints.Length]; + for (int i = 0; i < slider.Path.ControlPoints.Length; i++) + newControlPoints[i] = new Vector2(slider.Path.ControlPoints[i].X, -slider.Path.ControlPoints[i].Y); - slider.ControlPoints = newControlPoints; - slider.Curve?.Calculate(); // Recalculate the slider curve + slider.Path = new SliderPath(slider.Path.Type, newControlPoints, slider.Path.ExpectedDistance); } } } diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs b/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs index 6aa864d9b2..cf0d629367 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs @@ -10,7 +10,7 @@ namespace osu.Game.Rulesets.Osu.Mods public class OsuModSpunOut : Mod { public override string Name => "Spun Out"; - public override string ShortenedName => "SO"; + public override string Acronym => "SO"; public override FontAwesome Icon => FontAwesome.fa_osu_mod_spunout; public override ModType Type => ModType.DifficultyReduction; public override string Description => @"Spinners will be automatically completed."; diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs index 139ce4cc4b..968854d8b2 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs @@ -9,7 +9,7 @@ namespace osu.Game.Rulesets.Osu.Mods public class OsuModTarget : Mod { public override string Name => "Target"; - public override string ShortenedName => "TP"; + public override string Acronym => "TP"; public override ModType Type => ModType.Conversion; public override FontAwesome Icon => FontAwesome.fa_osu_mod_target; public override string Description => @"Practice keeping up with the beat of the song."; diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTransform.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTransform.cs index 440b314e5f..6949f44bd1 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModTransform.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModTransform.cs @@ -8,14 +8,14 @@ using osu.Game.Graphics; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects; -using OpenTK; +using osuTK; namespace osu.Game.Rulesets.Osu.Mods { internal class OsuModTransform : Mod, IApplicableToDrawableHitObjects { public override string Name => "Transform"; - public override string ShortenedName => "TR"; + public override string Acronym => "TR"; public override FontAwesome Icon => FontAwesome.fa_arrows; public override ModType Type => ModType.Fun; public override string Description => "Everything rotates. EVERYTHING."; diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs b/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs index e0a93453ce..3f7d43e31f 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs @@ -9,14 +9,14 @@ using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu.Objects; -using OpenTK; +using osuTK; namespace osu.Game.Rulesets.Osu.Mods { internal class OsuModWiggle : Mod, IApplicableToDrawableHitObjects { public override string Name => "Wiggle"; - public override string ShortenedName => "WG"; + public override string Acronym => "WG"; public override FontAwesome Icon => FontAwesome.fa_certificate; public override ModType Type => ModType.Fun; public override string Description => "They just won't stay still..."; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPoint.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPoint.cs index 908b9cb3c6..bd329266aa 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPoint.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPoint.cs @@ -1,8 +1,8 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using OpenTK; -using OpenTK.Graphics; +using osuTK; +using osuTK.Graphics; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs index 61a6e6404a..61219d9bb9 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs @@ -3,7 +3,7 @@ using System; using System.Collections.Generic; -using OpenTK; +using osuTK; using osu.Framework.Graphics; using osu.Game.Rulesets.Objects.Types; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs index 4bdddcef11..9b562745fa 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs @@ -2,12 +2,14 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System; +using osu.Framework.Allocation; +using osu.Framework.Configuration; using osu.Framework.Graphics; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces; -using OpenTK; +using osuTK; using osu.Game.Rulesets.Scoring; -using OpenTK.Graphics; +using osuTK.Graphics; namespace osu.Game.Rulesets.Osu.Objects.Drawables { @@ -21,6 +23,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables private readonly NumberPiece number; private readonly GlowPiece glow; + private readonly IBindable positionBindable = new Bindable(); + private readonly IBindable stackHeightBindable = new Bindable(); + private readonly IBindable scaleBindable = new Bindable(); + public DrawableHitCircle(HitCircle h) : base(h) { @@ -59,8 +65,18 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables //may not be so correct Size = circle.DrawSize; + } - HitObject.PositionChanged += _ => Position = HitObject.StackedPosition; + [BackgroundDependencyLoader] + private void load() + { + positionBindable.BindValueChanged(_ => Position = HitObject.StackedPosition); + stackHeightBindable.BindValueChanged(_ => Position = HitObject.StackedPosition); + scaleBindable.BindValueChanged(v => Scale = new Vector2(v)); + + positionBindable.BindTo(HitObject.PositionBindable); + stackHeightBindable.BindTo(HitObject.StackHeightBindable); + scaleBindable.BindTo(HitObject.ScaleBindable); } public override Color4 AccentColour diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuBlinds.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuBlinds.cs index 2bbd31fc46..7e72c5c7bb 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuBlinds.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuBlinds.cs @@ -1,17 +1,18 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // 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 OpenTK.Graphics; -using osu.Framework.Allocation; -using osu.Game.Skinning; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; -using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces; -using System; using osu.Game.Beatmaps; +using osu.Game.Skinning; +using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces; +using osuTK; +using osuTK.Graphics; +using System; namespace osu.Game.Rulesets.Osu.Objects.Drawables { @@ -178,7 +179,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables // TODO: include OD/CS } - return OpenTK.MathHelper.Clamp(value * multiplier, 0, targetClamp) * targetBreakMultiplier; + return MathHelper.Clamp(value * multiplier, 0, targetClamp) * targetBreakMultiplier; } private static float applyAdjustmentCurve(float value) @@ -262,14 +263,14 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables start = -npc_movement_start; end = npc_movement_start; - randomNpc.Scale = new OpenTK.Vector2(1, 1); + randomNpc.Scale = new Vector2(1, 1); } else { start = npc_movement_start; end = -npc_movement_start; - randomNpc.Scale = new OpenTK.Vector2(-1, 1); + randomNpc.Scale = new Vector2(-1, 1); } // depths for exit from the left and entry from the right diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs index 10cd246172..2ac46a14f2 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs @@ -9,15 +9,13 @@ using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu.Judgements; using osu.Game.Rulesets.Scoring; using osu.Game.Skinning; -using OpenTK.Graphics; +using osuTK.Graphics; using osu.Game.Graphics.Containers; namespace osu.Game.Rulesets.Osu.Objects.Drawables { public class DrawableOsuHitObject : DrawableHitObject { - public override bool IsPresent => base.IsPresent || State.Value == ArmedState.Idle && Clock?.CurrentTime >= HitObject.StartTime - HitObject.TimePreempt; - private readonly ShakeContainer shakeContainer; protected DrawableOsuHitObject(OsuHitObject hitObject) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs index 04ec3f13c7..17f6ddd211 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs @@ -2,7 +2,7 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using osu.Framework.Graphics; -using OpenTK; +using osuTK; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Scoring; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs index dfe7937e81..8c9252a2da 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs @@ -6,7 +6,7 @@ using System.Collections.Generic; using osu.Framework.Graphics; using osu.Framework.MathUtils; using osu.Game.Rulesets.Objects.Drawables; -using OpenTK; +using osuTK; using osu.Game.Graphics; using osu.Game.Rulesets.Scoring; using osu.Game.Skinning; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index 89f380db4e..eed9a53ad7 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -1,17 +1,19 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using OpenTK; +using osuTK; using osu.Framework.Graphics; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces; using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; +using osu.Framework.Configuration; using osu.Framework.Graphics.Containers; using osu.Game.Configuration; +using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Scoring; -using OpenTK.Graphics; +using osuTK.Graphics; namespace osu.Game.Rulesets.Osu.Objects.Drawables { @@ -23,9 +25,13 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables public readonly DrawableHitCircle HeadCircle; public readonly DrawableSliderTail TailCircle; - public readonly SliderBody Body; + public readonly SnakingSliderBody Body; public readonly SliderBall Ball; + private readonly IBindable positionBindable = new Bindable(); + private readonly IBindable scaleBindable = new Bindable(); + private readonly IBindable pathBindable = new Bindable(); + public DrawableSlider(Slider s) : base(s) { @@ -38,7 +44,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables InternalChildren = new Drawable[] { - Body = new SliderBody(s) + Body = new SnakingSliderBody(s) { PathWidth = s.Scale * 64, }, @@ -83,8 +89,26 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables components.Add(drawableRepeatPoint); AddNested(drawableRepeatPoint); } + } - HitObject.PositionChanged += _ => Position = HitObject.StackedPosition; + [BackgroundDependencyLoader] + private void load(OsuConfigManager config) + { + config.BindWith(OsuSetting.SnakingInSliders, Body.SnakingIn); + config.BindWith(OsuSetting.SnakingOutSliders, Body.SnakingOut); + + positionBindable.BindValueChanged(_ => Position = HitObject.StackedPosition); + scaleBindable.BindValueChanged(v => + { + Body.PathWidth = HitObject.Scale * 64; + Ball.Scale = new Vector2(HitObject.Scale); + }); + + positionBindable.BindTo(HitObject.PositionBindable); + scaleBindable.BindTo(HitObject.ScaleBindable); + pathBindable.BindTo(slider.PathBindable); + + pathBindable.BindValueChanged(_ => Body.Refresh()); } public override Color4 AccentColour @@ -101,13 +125,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables } } - [BackgroundDependencyLoader] - private void load(OsuConfigManager config) - { - config.BindWith(OsuSetting.SnakingInSliders, Body.SnakingIn); - config.BindWith(OsuSetting.SnakingOutSliders, Body.SnakingOut); - } - public bool Tracking; protected override void Update() @@ -119,7 +136,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables double completionProgress = MathHelper.Clamp((Time.Current - slider.StartTime) / slider.Duration, 0, 1); foreach (var c in components.OfType()) c.UpdateProgress(completionProgress); - foreach (var c in components.OfType()) c.UpdateSnakingPosition(slider.Curve.PositionAt(Body.SnakedStart ?? 0), slider.Curve.PositionAt(Body.SnakedEnd ?? 0)); + foreach (var c in components.OfType()) c.UpdateSnakingPosition(slider.Path.PositionAt(Body.SnakedStart ?? 0), slider.Path.PositionAt(Body.SnakedEnd ?? 0)); foreach (var t in components.OfType()) t.Tracking = Ball.Tracking; Size = Body.Size; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs index 6d6cba4936..f4962f6113 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs @@ -2,21 +2,35 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System; +using osu.Framework.Allocation; +using osu.Framework.Configuration; +using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; -using OpenTK; +using osuTK; namespace osu.Game.Rulesets.Osu.Objects.Drawables { public class DrawableSliderHead : DrawableHitCircle { + private readonly IBindable positionBindable = new Bindable(); + private readonly IBindable pathBindable = new Bindable(); + private readonly Slider slider; public DrawableSliderHead(Slider slider, HitCircle h) : base(h) { this.slider = slider; + } - Position = HitObject.Position - slider.Position; + [BackgroundDependencyLoader] + private void load() + { + positionBindable.BindTo(HitObject.PositionBindable); + pathBindable.BindTo(slider.PathBindable); + + positionBindable.BindValueChanged(_ => updatePosition()); + pathBindable.BindValueChanged(_ => updatePosition(), true); } protected override void Update() @@ -33,5 +47,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables public Action OnShake; protected override void Shake(double maximumLength) => OnShake?.Invoke(maximumLength); + + private void updatePosition() => Position = HitObject.Position - slider.Position; } } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs index 45c925b87a..4ff05d8995 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs @@ -1,13 +1,18 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using osu.Framework.Configuration; using osu.Framework.Graphics; +using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Scoring; +using osuTK; namespace osu.Game.Rulesets.Osu.Objects.Drawables { public class DrawableSliderTail : DrawableOsuHitObject, IRequireTracking { + private readonly Slider slider; + /// /// The judgement text is provided by the . /// @@ -15,9 +20,14 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables public bool Tracking { get; set; } + private readonly IBindable positionBindable = new Bindable(); + private readonly IBindable pathBindable = new Bindable(); + public DrawableSliderTail(Slider slider, SliderTailCircle hitCircle) : base(hitCircle) { + this.slider = slider; + Origin = Anchor.Centre; RelativeSizeAxes = Axes.Both; @@ -25,7 +35,11 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables AlwaysPresent = true; - Position = HitObject.Position - slider.Position; + positionBindable.BindTo(hitCircle.PositionBindable); + pathBindable.BindTo(slider.PathBindable); + + positionBindable.BindValueChanged(_ => updatePosition()); + pathBindable.BindValueChanged(_ => updatePosition(), true); } protected override void CheckForResult(bool userTriggered, double timeOffset) @@ -33,5 +47,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables if (!userTriggered && timeOffset >= 0) ApplyResult(r => r.Type = Tracking ? HitResult.Great : HitResult.Miss); } + + private void updatePosition() => Position = HitObject.Position - slider.Position; } } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs index 964c75131a..531e6a37af 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs @@ -3,8 +3,8 @@ using osu.Framework.Graphics; using osu.Game.Rulesets.Objects.Drawables; -using OpenTK; -using OpenTK.Graphics; +using osuTK; +using osuTK.Graphics; using osu.Framework.Graphics.Shapes; using osu.Game.Rulesets.Scoring; using osu.Game.Skinning; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index 51b1990a21..8e809306a4 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -6,11 +6,12 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces; -using OpenTK; -using OpenTK.Graphics; +using osuTK; +using osuTK.Graphics; using osu.Game.Graphics; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Allocation; +using osu.Framework.Configuration; using osu.Game.Screens.Ranking; using osu.Game.Rulesets.Scoring; @@ -36,6 +37,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables private readonly Color4 baseColour = OsuColour.FromHex(@"002c3c"); private readonly Color4 fillColour = OsuColour.FromHex(@"005b7c"); + private readonly IBindable positionBindable = new Bindable(); + private Color4 normalColour; private Color4 completeColour; @@ -114,6 +117,23 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables }; } + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + normalColour = baseColour; + + Background.AccentColour = normalColour; + + completeColour = colours.YellowLight.Opacity(0.75f); + + Disc.AccentColour = fillColour; + circle.Colour = colours.BlueDark; + glow.Colour = colours.BlueDark; + + positionBindable.BindValueChanged(v => Position = v); + positionBindable.BindTo(HitObject.PositionBindable); + } + public float Progress => MathHelper.Clamp(Disc.RotationAbsolute / 360 / Spinner.SpinsRequired, 0, 1); protected override void CheckForResult(bool userTriggered, double timeOffset) @@ -151,23 +171,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables }); } - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - normalColour = baseColour; - - Background.AccentColour = normalColour; - - completeColour = colours.YellowLight.Opacity(0.75f); - - Disc.AccentColour = fillColour; - circle.Colour = colours.BlueDark; - glow.Colour = colours.BlueDark; - } - protected override void Update() { - Disc.Tracking = OsuActionInputManager.PressedActions.Any(x => x == OsuAction.LeftButton || x == OsuAction.RightButton); + Disc.Tracking = OsuActionInputManager?.PressedActions.Any(x => x == OsuAction.LeftButton || x == OsuAction.RightButton) ?? false; if (!spmCounter.IsPresent && Disc.Tracking) spmCounter.FadeIn(HitObject.TimeFadeIn); @@ -211,6 +217,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables switch (state) { + case ArmedState.Idle: + Expire(true); + break; case ArmedState.Hit: sequence.ScaleTo(Scale * 1.2f, 320, Easing.Out); break; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/ITrackSnaking.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/ITrackSnaking.cs index a05e51a460..23903e1b5e 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/ITrackSnaking.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/ITrackSnaking.cs @@ -1,7 +1,7 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using OpenTK; +using osuTK; namespace osu.Game.Rulesets.Osu.Objects.Drawables { diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/CirclePiece.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/CirclePiece.cs index 6bb6991cc0..1a9acf708a 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/CirclePiece.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/CirclePiece.cs @@ -6,7 +6,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input.Bindings; using osu.Game.Skinning; -using OpenTK; +using osuTK; namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces { diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ExplodePiece.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ExplodePiece.cs index ed1b042939..2a2c845143 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ExplodePiece.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ExplodePiece.cs @@ -4,7 +4,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Skinning; -using OpenTK; +using osuTK; namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces { diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/FlashPiece.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/FlashPiece.cs index 0a1339a6ca..8880c96e3c 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/FlashPiece.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/FlashPiece.cs @@ -3,7 +3,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using OpenTK; +using osuTK; using osu.Framework.Graphics.Shapes; using osu.Game.Skinning; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ManualSliderBody.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ManualSliderBody.cs new file mode 100644 index 0000000000..6f4ccd60a9 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ManualSliderBody.cs @@ -0,0 +1,20 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Collections.Generic; +using osuTK; + +namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces +{ + /// + /// A with the ability to set the drawn vertices manually. + /// + public class ManualSliderBody : SliderBody + { + public new void SetVertices(IReadOnlyList vertices) + { + base.SetVertices(vertices); + Size = Path.Size; + } + } +} diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/NumberPiece.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/NumberPiece.cs index acb3ee92ff..87ca2f05e1 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/NumberPiece.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/NumberPiece.cs @@ -5,7 +5,7 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Graphics.Sprites; -using OpenTK.Graphics; +using osuTK.Graphics; using osu.Framework.Graphics.Shapes; using osu.Game.Skinning; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/RingPiece.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/RingPiece.cs index f3e0a0ef43..75b34959bd 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/RingPiece.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/RingPiece.cs @@ -3,8 +3,8 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using OpenTK; -using OpenTK.Graphics; +using osuTK; +using osuTK.Graphics; using osu.Framework.Graphics.Shapes; using osu.Game.Skinning; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs index 3081ae49fc..5144ce4e7a 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs @@ -7,9 +7,9 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; using osu.Game.Rulesets.Objects.Types; -using OpenTK.Graphics; +using osuTK.Graphics; using osu.Game.Skinning; -using OpenTK; +using osuTK; namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces { diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBody.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBody.cs index f4ccf673e9..4604a00fdd 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBody.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBody.cs @@ -1,24 +1,22 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . +// Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using System; using System.Collections.Generic; -using osu.Framework.Allocation; -using osu.Framework.Configuration; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Lines; -using OpenTK.Graphics.ES30; -using OpenTK.Graphics; using osu.Framework.Graphics.Primitives; -using osu.Game.Rulesets.Objects.Types; -using OpenTK; +using osuTK; +using osuTK.Graphics; +using osuTK.Graphics.ES30; namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces { - public class SliderBody : Container, ISliderProgress + public abstract class SliderBody : CompositeDrawable { private readonly SliderPath path; + protected Path Path => path; + private readonly BufferedContainer container; public float PathWidth @@ -30,15 +28,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces /// /// Offset in absolute coordinates from the start of the curve. /// - public Vector2 PathOffset { get; private set; } - - public readonly List CurrentCurve = new List(); - - public readonly Bindable SnakingIn = new Bindable(); - public readonly Bindable SnakingOut = new Bindable(); - - public double? SnakedStart { get; private set; } - public double? SnakedEnd { get; private set; } + public virtual Vector2 PathOffset => path.PositionInBoundingBox(path.Vertices[0]); /// /// Used to colour the path. @@ -74,28 +64,13 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces public Quad PathDrawQuad => container.ScreenSpaceDrawQuad; - private Vector2 topLeftOffset; - - private readonly Slider slider; - - public SliderBody(Slider s) + protected SliderBody() { - slider = s; - - Children = new Drawable[] + InternalChild = container = new BufferedContainer { - container = new BufferedContainer - { - RelativeSizeAxes = Axes.Both, - CacheDrawnFrameBuffer = true, - Children = new Drawable[] - { - path = new SliderPath - { - Blending = BlendingMode.None, - }, - } - }, + RelativeSizeAxes = Axes.Both, + CacheDrawnFrameBuffer = true, + Child = path = new SliderPath { Blending = BlendingMode.None } }; container.Attach(RenderbufferInternalFormat.DepthComponent16); @@ -103,80 +78,14 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => path.ReceivePositionalInputAt(screenSpacePos); - public void SetRange(double p0, double p1) + /// + /// Sets the vertices of the path which should be drawn by this . + /// + /// The vertices + protected void SetVertices(IReadOnlyList vertices) { - if (p0 > p1) - MathHelper.Swap(ref p0, ref p1); - - if (updateSnaking(p0, p1)) - { - // The path is generated such that its size encloses it. This change of size causes the path - // to move around while snaking, so we need to offset it to make sure it maintains the - // same position as when it is fully snaked. - var newTopLeftOffset = path.PositionInBoundingBox(Vector2.Zero); - path.Position = topLeftOffset - newTopLeftOffset; - - container.ForceRedraw(); - } - } - - [BackgroundDependencyLoader] - private void load() - { - computeSize(); - } - - private void computeSize() - { - // Generate the entire curve - slider.Curve.GetPathToProgress(CurrentCurve, 0, 1); - foreach (Vector2 p in CurrentCurve) - path.AddVertex(p); - - Size = path.Size; - - topLeftOffset = path.PositionInBoundingBox(Vector2.Zero); - PathOffset = path.PositionInBoundingBox(CurrentCurve[0]); - } - - private bool updateSnaking(double p0, double p1) - { - if (SnakedStart == p0 && SnakedEnd == p1) return false; - - SnakedStart = p0; - SnakedEnd = p1; - - slider.Curve.GetPathToProgress(CurrentCurve, p0, p1); - - path.ClearVertices(); - foreach (Vector2 p in CurrentCurve) - path.AddVertex(p); - - return true; - } - - public void UpdateProgress(double completionProgress) - { - var span = slider.SpanAt(completionProgress); - var spanProgress = slider.ProgressAt(completionProgress); - - double start = 0; - double end = SnakingIn ? MathHelper.Clamp((Time.Current - (slider.StartTime - slider.TimePreempt)) / slider.TimeFadeIn, 0, 1) : 1; - - if (span >= slider.SpanCount() - 1) - { - if (Math.Min(span, slider.SpanCount() - 1) % 2 == 1) - { - start = 0; - end = SnakingOut ? spanProgress : 1; - } - else - { - start = SnakingOut ? spanProgress : 0; - } - } - - SetRange(start, end); + path.Vertices = vertices; + container.ForceRedraw(); } private class SliderPath : SmoothPath diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SnakingSliderBody.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SnakingSliderBody.cs new file mode 100644 index 0000000000..49c66c4063 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SnakingSliderBody.cs @@ -0,0 +1,118 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using osu.Framework.Allocation; +using osu.Framework.Configuration; +using osu.Game.Rulesets.Objects.Types; +using osuTK; + +namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces +{ + /// + /// A which changes its curve depending on the snaking progress. + /// + public class SnakingSliderBody : SliderBody, ISliderProgress + { + public readonly List CurrentCurve = new List(); + + public readonly Bindable SnakingIn = new Bindable(); + public readonly Bindable SnakingOut = new Bindable(); + + public double? SnakedStart { get; private set; } + public double? SnakedEnd { get; private set; } + + public override Vector2 PathOffset => snakedPathOffset; + + /// + /// The top-left position of the path when fully snaked. + /// + private Vector2 snakedPosition; + + /// + /// The offset of the path from when fully snaked. + /// + private Vector2 snakedPathOffset; + + private readonly Slider slider; + + public SnakingSliderBody(Slider slider) + { + this.slider = slider; + } + + [BackgroundDependencyLoader] + private void load() + { + Refresh(); + } + + public void UpdateProgress(double completionProgress) + { + var span = slider.SpanAt(completionProgress); + var spanProgress = slider.ProgressAt(completionProgress); + + double start = 0; + double end = SnakingIn ? MathHelper.Clamp((Time.Current - (slider.StartTime - slider.TimePreempt)) / slider.TimeFadeIn, 0, 1) : 1; + + if (span >= slider.SpanCount() - 1) + { + if (Math.Min(span, slider.SpanCount() - 1) % 2 == 1) + { + start = 0; + end = SnakingOut ? spanProgress : 1; + } + else + { + start = SnakingOut ? spanProgress : 0; + } + } + + setRange(start, end); + } + + public void Refresh() + { + // Generate the entire curve + slider.Path.GetPathToProgress(CurrentCurve, 0, 1); + SetVertices(CurrentCurve); + + // The body is sized to the full path size to avoid excessive autosize computations + Size = Path.Size; + + snakedPosition = Path.PositionInBoundingBox(Vector2.Zero); + snakedPathOffset = Path.PositionInBoundingBox(Path.Vertices[0]); + + var lastSnakedStart = SnakedStart ?? 0; + var lastSnakedEnd = SnakedEnd ?? 0; + + SnakedStart = null; + SnakedEnd = null; + + setRange(lastSnakedStart, lastSnakedEnd); + } + + private void setRange(double p0, double p1) + { + if (p0 > p1) + MathHelper.Swap(ref p0, ref p1); + + if (SnakedStart == p0 && SnakedEnd == p1) return; + + SnakedStart = p0; + SnakedEnd = p1; + + slider.Path.GetPathToProgress(CurrentCurve, p0, p1); + + SetVertices(CurrentCurve); + + // The bounding box of the path expands as it snakes, which in turn shifts the position of the path. + // Depending on the direction of expansion, it may appear as if the path is expanding towards the position of the slider + // rather than expanding out from the position of the slider. + // To remove this effect, the path's position is shifted towards its final snaked position + + Path.Position = snakedPosition - Path.PositionInBoundingBox(Vector2.Zero); + } + } +} diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerBackground.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerBackground.cs index 584fd93a70..e2d67f16a9 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerBackground.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerBackground.cs @@ -1,7 +1,7 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using OpenTK.Graphics; +using osuTK.Graphics; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs index 4dd1c5f218..59400a71ea 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs @@ -6,8 +6,8 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input.Events; using osu.Game.Graphics; -using OpenTK; -using OpenTK.Graphics; +using osuTK; +using osuTK.Graphics; namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces { diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerTicks.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerTicks.cs index 61387d796e..cc9bb1975d 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerTicks.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerTicks.cs @@ -5,8 +5,8 @@ using System; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using OpenTK; -using OpenTK.Graphics; +using osuTK; +using osuTK.Graphics; using osu.Framework.Graphics.Shapes; namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces diff --git a/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs index fdf5aaffa8..462df43b65 100644 --- a/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs +++ b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs @@ -1,38 +1,28 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using System; +using osu.Framework.Configuration; using osu.Game.Beatmaps; using osu.Game.Rulesets.Objects; -using OpenTK; +using osuTK; using osu.Game.Rulesets.Objects.Types; using osu.Game.Beatmaps.ControlPoints; -using osu.Game.Rulesets.Edit.Types; namespace osu.Game.Rulesets.Osu.Objects { - public abstract class OsuHitObject : HitObject, IHasComboInformation, IHasEditablePosition + public abstract class OsuHitObject : HitObject, IHasComboInformation, IHasPosition { public const double OBJECT_RADIUS = 64; - public event Action PositionChanged; - public double TimePreempt = 600; public double TimeFadeIn = 400; - private Vector2 position; + public readonly Bindable PositionBindable = new Bindable(); - public Vector2 Position + public virtual Vector2 Position { - get => position; - set - { - if (position == value) - return; - position = value; - - PositionChanged?.Invoke(value); - } + get => PositionBindable; + set => PositionBindable.Value = value; } public float X => Position.X; @@ -44,13 +34,25 @@ namespace osu.Game.Rulesets.Osu.Objects public Vector2 StackedEndPosition => EndPosition + StackOffset; - public virtual int StackHeight { get; set; } + public readonly Bindable StackHeightBindable = new Bindable(); + + public int StackHeight + { + get => StackHeightBindable; + set => StackHeightBindable.Value = value; + } public Vector2 StackOffset => new Vector2(StackHeight * Scale * -6.4f); public double Radius => OBJECT_RADIUS * Scale; - public float Scale { get; set; } = 1; + public readonly Bindable ScaleBindable = new Bindable(1); + + public float Scale + { + get => ScaleBindable; + set => ScaleBindable.Value = value; + } public virtual bool NewCombo { get; set; } @@ -72,8 +74,6 @@ namespace osu.Game.Rulesets.Osu.Objects Scale = (1.0f - 0.7f * (difficulty.CircleSize - 5) / 5) / 2; } - public virtual void OffsetPosition(Vector2 offset) => Position += offset; - protected override HitWindows CreateHitWindows() => new OsuHitWindows(); } } diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index 80be192b77..d508ec2636 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -2,11 +2,12 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System; -using OpenTK; +using osuTK; using osu.Game.Rulesets.Objects.Types; using System.Collections.Generic; using osu.Game.Rulesets.Objects; using System.Linq; +using osu.Framework.Configuration; using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; @@ -22,7 +23,7 @@ namespace osu.Game.Rulesets.Osu.Objects /// private const float base_scoring_distance = 100; - public double EndTime => StartTime + this.SpanCount() * Curve.Distance / Velocity; + public double EndTime => StartTime + this.SpanCount() * Path.Distance / Velocity; public double Duration => EndTime - StartTime; public Vector2 StackedPositionAt(double t) => StackedPosition + this.CurvePositionAt(t); @@ -50,24 +51,29 @@ namespace osu.Game.Rulesets.Osu.Objects } } - public SliderCurve Curve { get; } = new SliderCurve(); + public readonly Bindable PathBindable = new Bindable(); - public Vector2[] ControlPoints + public SliderPath Path { - get { return Curve.ControlPoints; } - set { Curve.ControlPoints = value; } + get => PathBindable.Value; + set => PathBindable.Value = value; } - public CurveType CurveType - { - get { return Curve.CurveType; } - set { Curve.CurveType = value; } - } + public double Distance => Path.Distance; - public double Distance + public override Vector2 Position { - get { return Curve.Distance; } - set { Curve.Distance = value; } + get => base.Position; + set + { + base.Position = value; + + if (HeadCircle != null) + HeadCircle.Position = value; + + if (TailCircle != null) + TailCircle.Position = EndPosition; + } } public double? LegacyLastTickOffset { get; set; } @@ -84,7 +90,8 @@ namespace osu.Game.Rulesets.Osu.Objects /// internal float LazyTravelDistance; - public List> RepeatSamples { get; set; } = new List>(); + public List> NodeSamples { get; set; } = new List>(); + public int RepeatCount { get; set; } /// @@ -138,11 +145,11 @@ namespace osu.Game.Rulesets.Osu.Objects private void createSliderEnds() { - HeadCircle = new SliderCircle(this) + HeadCircle = new SliderCircle { StartTime = StartTime, Position = Position, - Samples = Samples, + Samples = getNodeSamples(0), SampleControlPoint = SampleControlPoint, IndexInCurrentCombo = IndexInCurrentCombo, ComboIndex = ComboIndex, @@ -162,7 +169,7 @@ namespace osu.Game.Rulesets.Osu.Objects private void createTicks() { - var length = Curve.Distance; + var length = Path.Distance; var tickDistance = MathHelper.Clamp(TickDistance, 0, length); if (tickDistance == 0) return; @@ -201,7 +208,7 @@ namespace osu.Game.Rulesets.Osu.Objects SpanIndex = span, SpanStartTime = spanStartTime, StartTime = spanStartTime + timeProgress * SpanDuration, - Position = Position + Curve.PositionAt(distanceProgress), + Position = Position + Path.PositionAt(distanceProgress), StackHeight = StackHeight, Scale = Scale, Samples = sampleList @@ -219,14 +226,21 @@ namespace osu.Game.Rulesets.Osu.Objects RepeatIndex = repeatIndex, SpanDuration = SpanDuration, StartTime = StartTime + repeat * SpanDuration, - Position = Position + Curve.PositionAt(repeat % 2), + Position = Position + Path.PositionAt(repeat % 2), StackHeight = StackHeight, Scale = Scale, - Samples = new List(RepeatSamples[repeatIndex]) + Samples = getNodeSamples(1 + repeatIndex) }); } } + private List getNodeSamples(int nodeIndex) + { + if (nodeIndex < NodeSamples.Count) + return NodeSamples[nodeIndex]; + return Samples; + } + public override Judgement CreateJudgement() => new OsuJudgement(); } } diff --git a/osu.Game.Rulesets.Osu/Objects/SliderCircle.cs b/osu.Game.Rulesets.Osu/Objects/SliderCircle.cs index 1bdd16c9df..deda951378 100644 --- a/osu.Game.Rulesets.Osu/Objects/SliderCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/SliderCircle.cs @@ -1,19 +1,9 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using OpenTK; - namespace osu.Game.Rulesets.Osu.Objects { public class SliderCircle : HitCircle { - private readonly Slider slider; - - public SliderCircle(Slider slider) - { - this.slider = slider; - } - - public override void OffsetPosition(Vector2 offset) => slider.OffsetPosition(offset); } } diff --git a/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs b/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs index 23616ea005..74a7a8d446 100644 --- a/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs @@ -1,16 +1,21 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using osu.Framework.Configuration; using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Judgements; namespace osu.Game.Rulesets.Osu.Objects { public class SliderTailCircle : SliderCircle { + private readonly IBindable pathBindable = new Bindable(); + public SliderTailCircle(Slider slider) - : base(slider) { + pathBindable.BindTo(slider.PathBindable); + pathBindable.BindValueChanged(_ => Position = slider.EndPosition); } public override Judgement CreateJudgement() => new OsuSliderTailJudgement(); diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index b417cbe68d..ecb0443f33 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -11,7 +11,6 @@ using System.Collections.Generic; using osu.Framework.Graphics; using osu.Game.Overlays.Settings; using osu.Framework.Input.Bindings; -using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Osu.Edit; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Osu.Replays; @@ -20,6 +19,7 @@ using osu.Game.Beatmaps.Legacy; using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Osu.Beatmaps; using osu.Game.Rulesets.Osu.Difficulty; +using osu.Game.Scoring; namespace osu.Game.Rulesets.Osu { @@ -131,7 +131,7 @@ namespace osu.Game.Rulesets.Osu public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new OsuDifficultyCalculator(this, beatmap); - public override PerformanceCalculator CreatePerformanceCalculator(WorkingBeatmap beatmap, Score score) => new OsuPerformanceCalculator(this, beatmap, score); + public override PerformanceCalculator CreatePerformanceCalculator(WorkingBeatmap beatmap, ScoreInfo score) => new OsuPerformanceCalculator(this, beatmap, score); public override HitObjectComposer CreateHitObjectComposer() => new OsuHitObjectComposer(this); diff --git a/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs b/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs index 7322f65066..eda5be1f0f 100644 --- a/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs +++ b/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs @@ -1,15 +1,15 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using OpenTK; +using osuTK; using osu.Framework.MathUtils; using osu.Game.Beatmaps; using osu.Game.Rulesets.Osu.Objects; using System; using System.Linq; using osu.Framework.Graphics; +using osu.Game.Replays; using osu.Game.Rulesets.Objects.Types; -using osu.Game.Rulesets.Replays; using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Osu.Replays diff --git a/osu.Game.Rulesets.Osu/Replays/OsuAutoGeneratorBase.cs b/osu.Game.Rulesets.Osu/Replays/OsuAutoGeneratorBase.cs index bb0bd891b3..6dc5e42258 100644 --- a/osu.Game.Rulesets.Osu/Replays/OsuAutoGeneratorBase.cs +++ b/osu.Game.Rulesets.Osu/Replays/OsuAutoGeneratorBase.cs @@ -1,14 +1,14 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using OpenTK; +using osuTK; using osu.Game.Beatmaps; using osu.Game.Rulesets.Osu.Objects; using System; using System.Collections.Generic; +using osu.Game.Replays; using osu.Game.Rulesets.Osu.UI; using osu.Game.Rulesets.Replays; -using osu.Game.Users; namespace osu.Game.Rulesets.Osu.Replays { @@ -37,13 +37,7 @@ namespace osu.Game.Rulesets.Osu.Replays protected OsuAutoGeneratorBase(Beatmap beatmap) : base(beatmap) { - Replay = new Replay - { - User = new User - { - Username = @"Autoplay", - } - }; + Replay = new Replay(); // We are using ApplyModsToRate and not ApplyModsToTime to counteract the speed up / slow down from HalfTime / DoubleTime so that we remain at a constant framerate of 60 fps. FrameDelay = ApplyModsToRate(1000.0 / 60.0); diff --git a/osu.Game.Rulesets.Osu/Replays/OsuReplayFrame.cs b/osu.Game.Rulesets.Osu/Replays/OsuReplayFrame.cs index 4412b6efab..74250fb92d 100644 --- a/osu.Game.Rulesets.Osu/Replays/OsuReplayFrame.cs +++ b/osu.Game.Rulesets.Osu/Replays/OsuReplayFrame.cs @@ -3,10 +3,10 @@ using System.Collections.Generic; using osu.Game.Beatmaps; +using osu.Game.Replays.Legacy; using osu.Game.Rulesets.Replays; -using osu.Game.Rulesets.Replays.Legacy; using osu.Game.Rulesets.Replays.Types; -using OpenTK; +using osuTK; namespace osu.Game.Rulesets.Osu.Replays { diff --git a/osu.Game.Rulesets.Osu/Replays/OsuReplayInputHandler.cs b/osu.Game.Rulesets.Osu/Replays/OsuReplayInputHandler.cs index 5c07860b19..7806e88b5c 100644 --- a/osu.Game.Rulesets.Osu/Replays/OsuReplayInputHandler.cs +++ b/osu.Game.Rulesets.Osu/Replays/OsuReplayInputHandler.cs @@ -5,8 +5,9 @@ using System.Collections.Generic; using System.Linq; using osu.Framework.Input.StateChanges; using osu.Framework.MathUtils; +using osu.Game.Replays; using osu.Game.Rulesets.Replays; -using OpenTK; +using osuTK; namespace osu.Game.Rulesets.Osu.Replays { diff --git a/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs b/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs index a9d39e88b4..9b3da1b8a8 100644 --- a/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs +++ b/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs @@ -9,6 +9,7 @@ using osu.Game.Rulesets.Osu.Judgements; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; +using osu.Game.Scoring; namespace osu.Game.Rulesets.Osu.Scoring { @@ -39,7 +40,7 @@ namespace osu.Game.Rulesets.Osu.Scoring comboResultCounts.Clear(); } - public override void PopulateScore(Score score) + public override void PopulateScore(ScoreInfo score) { base.PopulateScore(score); diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs index 4b5513ff9c..b25b3e5cb8 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs @@ -14,9 +14,9 @@ using osu.Framework.Graphics.Textures; using osu.Framework.Input; using osu.Framework.Input.Events; using osu.Framework.Timing; -using OpenTK; -using OpenTK.Graphics; -using OpenTK.Graphics.ES30; +using osuTK; +using osuTK.Graphics; +using osuTK.Graphics.ES30; namespace osu.Game.Rulesets.Osu.UI.Cursor { diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/GameplayCursor.cs b/osu.Game.Rulesets.Osu/UI/Cursor/GameplayCursor.cs index 4a45d4fb31..4aa30777e9 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/GameplayCursor.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/GameplayCursor.cs @@ -12,8 +12,8 @@ using osu.Framework.Input.Bindings; using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Skinning; -using OpenTK; -using OpenTK.Graphics; +using osuTK; +using osuTK.Graphics; namespace osu.Game.Rulesets.Osu.UI.Cursor { diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs index 398680cb8d..3399fdb9a0 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs @@ -1,7 +1,7 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using OpenTK; +using osuTK; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Objects.Drawables; @@ -84,5 +84,7 @@ namespace osu.Game.Rulesets.Osu.UI judgementLayer.Add(explosion); } + + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => HitObjectContainer.ReceivePositionalInputAt(screenSpacePos); } } diff --git a/osu.Game.Rulesets.Osu/UI/OsuRulesetContainer.cs b/osu.Game.Rulesets.Osu/UI/OsuRulesetContainer.cs index 0ea8d3ede8..c0e6eae494 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuRulesetContainer.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuRulesetContainer.cs @@ -6,6 +6,7 @@ using osu.Framework.Graphics.Cursor; using osu.Framework.Input; using osu.Game.Beatmaps; using osu.Game.Input.Handlers; +using osu.Game.Replays; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects.Drawables; @@ -14,7 +15,6 @@ using osu.Game.Rulesets.Osu.Scoring; using osu.Game.Rulesets.Osu.UI.Cursor; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; -using osu.Game.Rulesets.Replays; namespace osu.Game.Rulesets.Osu.UI { @@ -31,7 +31,7 @@ namespace osu.Game.Rulesets.Osu.UI public override PassThroughInputManager CreateInputManager() => new OsuInputManager(Ruleset.RulesetInfo); - protected override DrawableHitObject GetVisualRepresentation(OsuHitObject h) + public override DrawableHitObject GetVisualRepresentation(OsuHitObject h) { switch (h) { diff --git a/osu.Game.Rulesets.Osu/UI/PlayfieldAdjustmentContainer.cs b/osu.Game.Rulesets.Osu/UI/PlayfieldAdjustmentContainer.cs index 00d5692fda..df5bd84e75 100644 --- a/osu.Game.Rulesets.Osu/UI/PlayfieldAdjustmentContainer.cs +++ b/osu.Game.Rulesets.Osu/UI/PlayfieldAdjustmentContainer.cs @@ -3,7 +3,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using OpenTK; +using osuTK; namespace osu.Game.Rulesets.Osu.UI { diff --git a/osu.Game.Rulesets.Taiko.Tests/TestCaseInputDrum.cs b/osu.Game.Rulesets.Taiko.Tests/TestCaseInputDrum.cs index 322e9b6bd4..b88aea0bbd 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TestCaseInputDrum.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TestCaseInputDrum.cs @@ -4,7 +4,7 @@ using System; using System.Collections.Generic; using NUnit.Framework; -using OpenTK; +using osuTK; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Audio; diff --git a/osu.Game.Rulesets.Taiko.Tests/TestCaseTaikoPlayfield.cs b/osu.Game.Rulesets.Taiko.Tests/TestCaseTaikoPlayfield.cs index fc103e4c72..dad7f34e71 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TestCaseTaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TestCaseTaikoPlayfield.cs @@ -19,7 +19,7 @@ using osu.Game.Rulesets.Taiko.Objects.Drawables; using osu.Game.Rulesets.Taiko.UI; using osu.Game.Tests.Beatmaps; using osu.Game.Tests.Visual; -using OpenTK; +using osuTK; using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Taiko.Tests diff --git a/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj b/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj index 6ae9a018c5..3ba64398f3 100644 --- a/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj +++ b/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj @@ -4,7 +4,7 @@ - + diff --git a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs index c2cde332e8..c4a84f416e 100644 --- a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs +++ b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs @@ -117,7 +117,7 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps if (!isForCurrentRuleset && tickSpacing > 0 && osuDuration < 2 * speedAdjustedBeatLength) { - List> allSamples = curveData != null ? curveData.RepeatSamples : new List>(new[] { samples }); + List> allSamples = curveData != null ? curveData.NodeSamples : new List>(new[] { samples }); int i = 0; for (double j = obj.StartTime; j <= obj.StartTime + taikoDuration + tickSpacing / 8; j += tickSpacing) diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs index f530b6725c..e4960c9680 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs @@ -8,6 +8,8 @@ using osu.Game.Beatmaps; using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Scoring; +using osu.Game.Rulesets.Taiko.Objects; +using osu.Game.Scoring; namespace osu.Game.Rulesets.Taiko.Difficulty { @@ -21,7 +23,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty private int countMeh; private int countMiss; - public TaikoPerformanceCalculator(Ruleset ruleset, WorkingBeatmap beatmap, Score score) + public TaikoPerformanceCalculator(Ruleset ruleset, WorkingBeatmap beatmap, ScoreInfo score) : base(ruleset, beatmap, score) { } @@ -82,7 +84,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty if (mods.Any(m => m is ModHidden)) strainValue *= 1.025; - if (mods.Any(m => m is ModFlashlight)) + if (mods.Any(m => m is ModFlashlight)) // Apply length bonus again if flashlight is on simply because it becomes a lot harder on longer maps. strainValue *= 1.05 * lengthBonus; diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModAutoplay.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModAutoplay.cs index c63bf10542..62111ae74a 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModAutoplay.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModAutoplay.cs @@ -3,22 +3,19 @@ using osu.Game.Beatmaps; using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Taiko.Objects; using osu.Game.Rulesets.Taiko.Replays; +using osu.Game.Scoring; using osu.Game.Users; namespace osu.Game.Rulesets.Taiko.Mods { public class TaikoModAutoplay : ModAutoplay { - protected override Score CreateReplayScore(Beatmap beatmap) + protected override Score CreateReplayScore(Beatmap beatmap) => new Score { - return new Score - { - User = new User { Username = "mekkadosu!" }, - Replay = new TaikoAutoGenerator(beatmap).Generate(), - }; - } + ScoreInfo = new ScoreInfo { User = new User { Username = "mekkadosu!" } }, + Replay = new TaikoAutoGenerator(beatmap).Generate(), + }; } } diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs index 49f7786f59..79dad84696 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs @@ -1,12 +1,80 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using osu.Framework.Caching; +using osu.Framework.Graphics; using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Taiko.Objects; +using osu.Game.Rulesets.Taiko.UI; +using osu.Game.Rulesets.UI; +using osuTK; namespace osu.Game.Rulesets.Taiko.Mods { - public class TaikoModFlashlight : ModFlashlight + public class TaikoModFlashlight : ModFlashlight { public override double ScoreMultiplier => 1.12; + + private const float default_flashlight_size = 250; + + public override Flashlight CreateFlashlight() => new TaikoFlashlight(playfield); + + private TaikoPlayfield playfield; + + public override void ApplyToRulesetContainer(RulesetContainer rulesetContainer) + { + playfield = (TaikoPlayfield)rulesetContainer.Playfield; + base.ApplyToRulesetContainer(rulesetContainer); + } + + private class TaikoFlashlight : Flashlight + { + private readonly Cached flashlightProperties = new Cached(); + private readonly TaikoPlayfield taikoPlayfield; + + public TaikoFlashlight(TaikoPlayfield taikoPlayfield) + { + this.taikoPlayfield = taikoPlayfield; + FlashlightSize = new Vector2(0, getSizeFor(0)); + } + + private float getSizeFor(int combo) + { + if (combo > 200) + return default_flashlight_size * 0.8f; + else if (combo > 100) + return default_flashlight_size * 0.9f; + else + return default_flashlight_size; + } + + protected override void OnComboChange(int newCombo) + { + this.TransformTo(nameof(FlashlightSize), new Vector2(0, getSizeFor(newCombo)), FLASHLIGHT_FADE_DURATION); + } + + protected override string FragmentShader => "CircularFlashlight"; + + public override bool Invalidate(Invalidation invalidation = Invalidation.All, Drawable source = null, bool shallPropagate = true) + { + if ((invalidation & Invalidation.DrawSize) > 0) + { + flashlightProperties.Invalidate(); + } + + return base.Invalidate(invalidation, source, shallPropagate); + } + + protected override void Update() + { + base.Update(); + + if (!flashlightProperties.IsValid) + { + FlashlightPosition = taikoPlayfield.HitTarget.ToSpaceOfOtherDrawable(taikoPlayfield.HitTarget.OriginPosition, this); + flashlightProperties.Validate(); + } + } + } } } diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableBarLine.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableBarLine.cs index db5f418e18..d6f8522b77 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableBarLine.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableBarLine.cs @@ -3,7 +3,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Shapes; -using OpenTK; +using osuTK; using osu.Game.Rulesets.Objects.Drawables; namespace osu.Game.Rulesets.Taiko.Objects.Drawables diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableBarLineMajor.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableBarLineMajor.cs index 300edd2e76..3a1b4e33d2 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableBarLineMajor.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableBarLineMajor.cs @@ -3,7 +3,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using OpenTK; +using osuTK; using osu.Framework.Graphics.Shapes; namespace osu.Game.Rulesets.Taiko.Objects.Drawables diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs index 5142f125ac..632dff4525 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs @@ -6,8 +6,8 @@ using osu.Framework.Allocation; using osu.Framework.MathUtils; using osu.Game.Graphics; using osu.Game.Rulesets.Objects.Drawables; -using OpenTK; -using OpenTK.Graphics; +using osuTK; +using osuTK.Graphics; using osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs index 5059734663..1412bd3cea 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs @@ -11,8 +11,8 @@ using osu.Framework.Graphics.Containers; using osu.Game.Graphics; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces; -using OpenTK; -using OpenTK.Graphics; +using osuTK; +using osuTK.Graphics; using osu.Framework.Graphics.Shapes; using osu.Game.Rulesets.Scoring; diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs index 51e39dc648..cee9b40730 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs @@ -5,7 +5,7 @@ using osu.Framework.Graphics; using osu.Framework.Input.Bindings; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces; -using OpenTK; +using osuTK; using System.Linq; using osu.Game.Audio; using System.Collections.Generic; @@ -31,7 +31,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables RelativeSizeAxes = Axes.Both, Child = Content = new Container { RelativeSizeAxes = Axes.Both } }, - proxiedContent = new Container { RelativeSizeAxes = Axes.Both } + proxiedContent = new ProxiedContentContainer { RelativeSizeAxes = Axes.Both } }; } @@ -75,6 +75,12 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables public abstract bool OnPressed(TaikoAction action); public virtual bool OnReleased(TaikoAction action) => false; + + private class ProxiedContentContainer : Container + { + public override double LifetimeStart => Parent?.LifetimeStart ?? base.LifetimeStart; + public override double LifetimeEnd => Parent?.LifetimeEnd ?? base.LifetimeEnd; + } } public abstract class DrawableTaikoHitObject : DrawableTaikoHitObject diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/CentreHitSymbolPiece.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/CentreHitSymbolPiece.cs index 27e2b3c762..b4cb3a35f6 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/CentreHitSymbolPiece.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/CentreHitSymbolPiece.cs @@ -3,7 +3,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using OpenTK; +using osuTK; using osu.Framework.Graphics.Shapes; namespace osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/CirclePiece.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/CirclePiece.cs index 7a4931dc71..fdb73235de 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/CirclePiece.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/CirclePiece.cs @@ -6,7 +6,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics.Backgrounds; -using OpenTK.Graphics; +using osuTK.Graphics; using osu.Game.Beatmaps.ControlPoints; using osu.Framework.Audio.Track; diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/RimHitSymbolPiece.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/RimHitSymbolPiece.cs index d3487fb65c..613d8debb5 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/RimHitSymbolPiece.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/RimHitSymbolPiece.cs @@ -3,8 +3,8 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using OpenTK; -using OpenTK.Graphics; +using osuTK; +using osuTK.Graphics; using osu.Framework.Graphics.Shapes; namespace osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/SwellSymbolPiece.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/SwellSymbolPiece.cs index d70ac64a27..8455f4af58 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/SwellSymbolPiece.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/SwellSymbolPiece.cs @@ -1,7 +1,7 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using OpenTK; +using osuTK; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Graphics; diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/TaikoPiece.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/TaikoPiece.cs index e630847aec..6795338a83 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/TaikoPiece.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/TaikoPiece.cs @@ -2,7 +2,7 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using osu.Game.Graphics; -using OpenTK.Graphics; +using osuTK.Graphics; using osu.Game.Graphics.Containers; using osu.Framework.Graphics; diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/TickPiece.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/TickPiece.cs index 976836a5ed..7327b9c565 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/TickPiece.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/TickPiece.cs @@ -3,8 +3,8 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using OpenTK; -using OpenTK.Graphics; +using osuTK; +using osuTK.Graphics; using osu.Framework.Graphics.Shapes; namespace osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces diff --git a/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs b/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs index e7b2789010..2794a3c166 100644 --- a/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs +++ b/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs @@ -5,10 +5,10 @@ using System; using System.Collections.Generic; using System.Linq; using osu.Game.Beatmaps; +using osu.Game.Replays; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Taiko.Objects; using osu.Game.Rulesets.Replays; -using osu.Game.Users; namespace osu.Game.Rulesets.Taiko.Replays { @@ -19,13 +19,7 @@ namespace osu.Game.Rulesets.Taiko.Replays public TaikoAutoGenerator(Beatmap beatmap) : base(beatmap) { - Replay = new Replay - { - User = new User - { - Username = @"Autoplay", - } - }; + Replay = new Replay(); } protected Replay Replay; diff --git a/osu.Game.Rulesets.Taiko/Replays/TaikoFramedReplayInputHandler.cs b/osu.Game.Rulesets.Taiko/Replays/TaikoFramedReplayInputHandler.cs index ab7856eb8f..9748cb08be 100644 --- a/osu.Game.Rulesets.Taiko/Replays/TaikoFramedReplayInputHandler.cs +++ b/osu.Game.Rulesets.Taiko/Replays/TaikoFramedReplayInputHandler.cs @@ -5,6 +5,7 @@ using osu.Game.Rulesets.Replays; using System.Collections.Generic; using System.Linq; using osu.Framework.Input.StateChanges; +using osu.Game.Replays; namespace osu.Game.Rulesets.Taiko.Replays { diff --git a/osu.Game.Rulesets.Taiko/Replays/TaikoReplayFrame.cs b/osu.Game.Rulesets.Taiko/Replays/TaikoReplayFrame.cs index 2177a3cbdc..44fd43d660 100644 --- a/osu.Game.Rulesets.Taiko/Replays/TaikoReplayFrame.cs +++ b/osu.Game.Rulesets.Taiko/Replays/TaikoReplayFrame.cs @@ -3,8 +3,8 @@ using System.Collections.Generic; using osu.Game.Beatmaps; +using osu.Game.Replays.Legacy; using osu.Game.Rulesets.Replays; -using osu.Game.Rulesets.Replays.Legacy; using osu.Game.Rulesets.Replays.Types; namespace osu.Game.Rulesets.Taiko.Replays diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs index 7d9bc98957..7094f9d71b 100644 --- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs @@ -14,9 +14,9 @@ using osu.Game.Rulesets.Replays.Types; using osu.Game.Rulesets.Taiko.Replays; using osu.Game.Beatmaps.Legacy; using osu.Game.Rulesets.Difficulty; -using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Taiko.Beatmaps; using osu.Game.Rulesets.Taiko.Difficulty; +using osu.Game.Scoring; namespace osu.Game.Rulesets.Taiko { @@ -112,7 +112,7 @@ namespace osu.Game.Rulesets.Taiko public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new TaikoDifficultyCalculator(this, beatmap); - public override PerformanceCalculator CreatePerformanceCalculator(WorkingBeatmap beatmap, Score score) => new TaikoPerformanceCalculator(this, beatmap, score); + public override PerformanceCalculator CreatePerformanceCalculator(WorkingBeatmap beatmap, ScoreInfo score) => new TaikoPerformanceCalculator(this, beatmap, score); public override int? LegacyID => 1; diff --git a/osu.Game.Rulesets.Taiko/UI/HitExplosion.cs b/osu.Game.Rulesets.Taiko/UI/HitExplosion.cs index 4dd0ba4d3d..84e40427f2 100644 --- a/osu.Game.Rulesets.Taiko/UI/HitExplosion.cs +++ b/osu.Game.Rulesets.Taiko/UI/HitExplosion.cs @@ -1,8 +1,8 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using OpenTK; -using OpenTK.Graphics; +using osuTK; +using osuTK.Graphics; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; diff --git a/osu.Game.Rulesets.Taiko/UI/HitTarget.cs b/osu.Game.Rulesets.Taiko/UI/HitTarget.cs index 9d824ca5b7..ab5bc841a6 100644 --- a/osu.Game.Rulesets.Taiko/UI/HitTarget.cs +++ b/osu.Game.Rulesets.Taiko/UI/HitTarget.cs @@ -1,8 +1,8 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using OpenTK; -using OpenTK.Graphics; +using osuTK; +using osuTK.Graphics; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; diff --git a/osu.Game.Rulesets.Taiko/UI/InputDrum.cs b/osu.Game.Rulesets.Taiko/UI/InputDrum.cs index 524535bfde..6d84b69638 100644 --- a/osu.Game.Rulesets.Taiko/UI/InputDrum.cs +++ b/osu.Game.Rulesets.Taiko/UI/InputDrum.cs @@ -2,7 +2,7 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System; -using OpenTK; +using osuTK; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; diff --git a/osu.Game.Rulesets.Taiko/UI/KiaiHitExplosion.cs b/osu.Game.Rulesets.Taiko/UI/KiaiHitExplosion.cs index 287d59972a..ad9b967ac6 100644 --- a/osu.Game.Rulesets.Taiko/UI/KiaiHitExplosion.cs +++ b/osu.Game.Rulesets.Taiko/UI/KiaiHitExplosion.cs @@ -1,7 +1,7 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using OpenTK; +using osuTK; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; diff --git a/osu.Game.Rulesets.Taiko/UI/PlayfieldAdjustmentContainer.cs b/osu.Game.Rulesets.Taiko/UI/PlayfieldAdjustmentContainer.cs index 661a4e135c..73751e845e 100644 --- a/osu.Game.Rulesets.Taiko/UI/PlayfieldAdjustmentContainer.cs +++ b/osu.Game.Rulesets.Taiko/UI/PlayfieldAdjustmentContainer.cs @@ -2,7 +2,7 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using osu.Framework.Graphics.Containers; -using OpenTK; +using osuTK; namespace osu.Game.Rulesets.Taiko.UI { diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs index 40ed659bd6..333b1ba83a 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs @@ -8,7 +8,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Game.Beatmaps.ControlPoints; -using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Judgements; @@ -17,8 +16,8 @@ using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Rulesets.Taiko.Objects; using osu.Game.Rulesets.Taiko.Objects.Drawables; using osu.Game.Rulesets.Taiko.Judgements; -using OpenTK; -using OpenTK.Graphics; +using osuTK; +using osuTK.Graphics; namespace osu.Game.Rulesets.Taiko.UI { @@ -39,13 +38,10 @@ namespace osu.Game.Rulesets.Taiko.UI /// private const float left_area_size = 240; - protected override bool UserScrollSpeedAdjustment => false; - - protected override SpeedChangeVisualisationMethod VisualisationMethod => SpeedChangeVisualisationMethod.Overlapping; - private readonly Container hitExplosionContainer; private readonly Container kiaiExplosionContainer; private readonly JudgementContainer judgementContainer; + internal readonly HitTarget HitTarget; private readonly Container topLevelHitContainer; @@ -59,8 +55,6 @@ namespace osu.Game.Rulesets.Taiko.UI public TaikoPlayfield(ControlPointInfo controlPoints) { - Direction.Value = ScrollingDirection.Left; - InternalChild = new PlayfieldAdjustmentContainer { Anchor = Anchor.CentreLeft, @@ -109,7 +103,7 @@ namespace osu.Game.Rulesets.Taiko.UI FillMode = FillMode.Fit, Blending = BlendingMode.Additive, }, - new HitTarget + HitTarget = new HitTarget { Anchor = Anchor.CentreLeft, Origin = Anchor.Centre, @@ -200,8 +194,6 @@ namespace osu.Game.Rulesets.Taiko.UI } } }; - - VisibleTimeRange.Value = 7000; } [BackgroundDependencyLoader] diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoRulesetContainer.cs b/osu.Game.Rulesets.Taiko/UI/TaikoRulesetContainer.cs index 3d08bffe0f..8f6e55c0d1 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoRulesetContainer.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoRulesetContainer.cs @@ -5,7 +5,6 @@ using osu.Framework.Allocation; using osu.Game.Beatmaps; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Types; -using osu.Game.Rulesets.Replays; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Taiko.Objects; using osu.Game.Rulesets.Taiko.Objects.Drawables; @@ -14,16 +13,24 @@ using osu.Game.Rulesets.UI; using osu.Game.Rulesets.Taiko.Replays; using System.Linq; using osu.Framework.Input; +using osu.Game.Configuration; using osu.Game.Input.Handlers; +using osu.Game.Replays; using osu.Game.Rulesets.UI.Scrolling; namespace osu.Game.Rulesets.Taiko.UI { public class TaikoRulesetContainer : ScrollingRulesetContainer { + protected override ScrollVisualisationMethod VisualisationMethod => ScrollVisualisationMethod.Overlapping; + + protected override bool UserScrollSpeedAdjustment => false; + public TaikoRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap) : base(ruleset, beatmap) { + Direction.Value = ScrollingDirection.Left; + TimeRange.Value = 7000; } [BackgroundDependencyLoader] @@ -78,7 +85,7 @@ namespace osu.Game.Rulesets.Taiko.UI protected override Playfield CreatePlayfield() => new TaikoPlayfield(Beatmap.ControlPointInfo); - protected override DrawableHitObject GetVisualRepresentation(TaikoHitObject h) + public override DrawableHitObject GetVisualRepresentation(TaikoHitObject h) { switch (h) { diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs index f1ae366ee1..f0211e1ead 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs @@ -3,8 +3,8 @@ using System.IO; using NUnit.Framework; -using OpenTK; -using OpenTK.Graphics; +using osuTK; +using osuTK.Graphics; using osu.Game.Tests.Resources; using System.Linq; using osu.Game.Audio; @@ -14,6 +14,7 @@ using osu.Game.Beatmaps.Formats; using osu.Game.Beatmaps.Timing; using osu.Game.Rulesets.Catch.Beatmaps; using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Legacy; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Beatmaps; using osu.Game.Skinning; @@ -294,6 +295,9 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.AreEqual("normal-hitnormal", getTestableSampleInfo(hitObjects[1]).LookupNames.First()); Assert.AreEqual("normal-hitnormal2", getTestableSampleInfo(hitObjects[2]).LookupNames.First()); Assert.AreEqual("normal-hitnormal", getTestableSampleInfo(hitObjects[3]).LookupNames.First()); + + // The control point at the end time of the slider should be applied + Assert.AreEqual("soft-hitnormal8", getTestableSampleInfo(hitObjects[4]).LookupNames.First()); } SampleInfo getTestableSampleInfo(HitObject hitObject) => hitObject.SampleControlPoint.ApplyTo(hitObject.Samples[0]); @@ -333,5 +337,61 @@ namespace osu.Game.Tests.Beatmaps.Formats SampleInfo getTestableSampleInfo(HitObject hitObject) => hitObject.SampleControlPoint.ApplyTo(hitObject.Samples[0]); } + + [Test] + public void TestDecodeSliderSamples() + { + var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false }; + using (var resStream = Resource.OpenResource("slider-samples.osu")) + using (var stream = new StreamReader(resStream)) + { + var hitObjects = decoder.Decode(stream).HitObjects; + + var slider1 = (ConvertSlider)hitObjects[0]; + + Assert.AreEqual(1, slider1.NodeSamples[0].Count); + Assert.AreEqual(SampleInfo.HIT_NORMAL, slider1.NodeSamples[0][0].Name); + Assert.AreEqual(1, slider1.NodeSamples[1].Count); + Assert.AreEqual(SampleInfo.HIT_NORMAL, slider1.NodeSamples[1][0].Name); + Assert.AreEqual(1, slider1.NodeSamples[2].Count); + Assert.AreEqual(SampleInfo.HIT_NORMAL, slider1.NodeSamples[2][0].Name); + + var slider2 = (ConvertSlider)hitObjects[1]; + + Assert.AreEqual(2, slider2.NodeSamples[0].Count); + Assert.AreEqual(SampleInfo.HIT_NORMAL, slider2.NodeSamples[0][0].Name); + Assert.AreEqual(SampleInfo.HIT_CLAP, slider2.NodeSamples[0][1].Name); + Assert.AreEqual(2, slider2.NodeSamples[1].Count); + Assert.AreEqual(SampleInfo.HIT_NORMAL, slider2.NodeSamples[1][0].Name); + Assert.AreEqual(SampleInfo.HIT_CLAP, slider2.NodeSamples[1][1].Name); + Assert.AreEqual(2, slider2.NodeSamples[2].Count); + Assert.AreEqual(SampleInfo.HIT_NORMAL, slider2.NodeSamples[2][0].Name); + Assert.AreEqual(SampleInfo.HIT_CLAP, slider2.NodeSamples[2][1].Name); + + var slider3 = (ConvertSlider)hitObjects[2]; + + Assert.AreEqual(2, slider3.NodeSamples[0].Count); + Assert.AreEqual(SampleInfo.HIT_NORMAL, slider3.NodeSamples[0][0].Name); + Assert.AreEqual(SampleInfo.HIT_WHISTLE, slider3.NodeSamples[0][1].Name); + Assert.AreEqual(1, slider3.NodeSamples[1].Count); + Assert.AreEqual(SampleInfo.HIT_NORMAL, slider3.NodeSamples[1][0].Name); + Assert.AreEqual(2, slider3.NodeSamples[2].Count); + Assert.AreEqual(SampleInfo.HIT_NORMAL, slider3.NodeSamples[2][0].Name); + Assert.AreEqual(SampleInfo.HIT_CLAP, slider3.NodeSamples[2][1].Name); + } + } + + [Test] + public void TestDecodeHitObjectNullAdditionBank() + { + var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false }; + using (var resStream = Resource.OpenResource("hitobject-no-addition-bank.osu")) + using (var stream = new StreamReader(resStream)) + { + var hitObjects = decoder.Decode(stream).HitObjects; + + Assert.AreEqual(hitObjects[0].Samples[0].Bank, hitObjects[0].Samples[1].Bank); + } + } } } diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs index 82adc88c6b..40857198f1 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs @@ -4,7 +4,7 @@ using System.IO; using System.Linq; using NUnit.Framework; -using OpenTK; +using osuTK; using osu.Framework.Graphics; using osu.Game.Beatmaps.Formats; using osu.Game.Storyboards; diff --git a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs index 64bd563897..f7e1653cdd 100644 --- a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs @@ -12,7 +12,7 @@ using osu.Game.IO.Serialization; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; using osu.Game.Tests.Resources; -using OpenTK; +using osuTK; namespace osu.Game.Tests.Beatmaps.Formats { diff --git a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs index 616ba132fd..26167cb24a 100644 --- a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs +++ b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs @@ -102,7 +102,7 @@ namespace osu.Game.Tests.Beatmaps.IO int fireCount = 0; // ReSharper disable once AccessToModifiedClosure - manager.ItemAdded += _ => fireCount++; + manager.ItemAdded += (_, __, ___) => fireCount++; manager.ItemRemoved += _ => fireCount++; var imported = loadOszIntoOsu(osu); diff --git a/osu.Game.Tests/NonVisual/DifficultyAdjustmentModCombinationsTest.cs b/osu.Game.Tests/NonVisual/DifficultyAdjustmentModCombinationsTest.cs index 49494b65b9..42fe4065c7 100644 --- a/osu.Game.Tests/NonVisual/DifficultyAdjustmentModCombinationsTest.cs +++ b/osu.Game.Tests/NonVisual/DifficultyAdjustmentModCombinationsTest.cs @@ -18,7 +18,7 @@ namespace osu.Game.Tests.NonVisual var combinations = new TestDifficultyCalculator().CreateDifficultyAdjustmentModCombinations(); Assert.AreEqual(1, combinations.Length); - Assert.IsTrue(combinations[0] is NoModMod); + Assert.IsTrue(combinations[0] is ModNoMod); } [Test] @@ -27,7 +27,7 @@ namespace osu.Game.Tests.NonVisual var combinations = new TestDifficultyCalculator(new ModA()).CreateDifficultyAdjustmentModCombinations(); Assert.AreEqual(2, combinations.Length); - Assert.IsTrue(combinations[0] is NoModMod); + Assert.IsTrue(combinations[0] is ModNoMod); Assert.IsTrue(combinations[1] is ModA); } @@ -37,7 +37,7 @@ namespace osu.Game.Tests.NonVisual var combinations = new TestDifficultyCalculator(new ModA(), new ModB()).CreateDifficultyAdjustmentModCombinations(); Assert.AreEqual(4, combinations.Length); - Assert.IsTrue(combinations[0] is NoModMod); + Assert.IsTrue(combinations[0] is ModNoMod); Assert.IsTrue(combinations[1] is ModA); Assert.IsTrue(combinations[2] is MultiMod); Assert.IsTrue(combinations[3] is ModB); @@ -52,7 +52,7 @@ namespace osu.Game.Tests.NonVisual var combinations = new TestDifficultyCalculator(new ModA(), new ModIncompatibleWithA()).CreateDifficultyAdjustmentModCombinations(); Assert.AreEqual(3, combinations.Length); - Assert.IsTrue(combinations[0] is NoModMod); + Assert.IsTrue(combinations[0] is ModNoMod); Assert.IsTrue(combinations[1] is ModA); Assert.IsTrue(combinations[2] is ModIncompatibleWithA); } @@ -63,7 +63,7 @@ namespace osu.Game.Tests.NonVisual var combinations = new TestDifficultyCalculator(new ModA(), new ModB(), new ModIncompatibleWithA(), new ModIncompatibleWithAAndB()).CreateDifficultyAdjustmentModCombinations(); Assert.AreEqual(8, combinations.Length); - Assert.IsTrue(combinations[0] is NoModMod); + Assert.IsTrue(combinations[0] is ModNoMod); Assert.IsTrue(combinations[1] is ModA); Assert.IsTrue(combinations[2] is MultiMod); Assert.IsTrue(combinations[3] is ModB); @@ -86,7 +86,7 @@ namespace osu.Game.Tests.NonVisual var combinations = new TestDifficultyCalculator(new ModAofA(), new ModIncompatibleWithAofA()).CreateDifficultyAdjustmentModCombinations(); Assert.AreEqual(3, combinations.Length); - Assert.IsTrue(combinations[0] is NoModMod); + Assert.IsTrue(combinations[0] is ModNoMod); Assert.IsTrue(combinations[1] is ModAofA); Assert.IsTrue(combinations[2] is ModIncompatibleWithAofA); } @@ -94,7 +94,7 @@ namespace osu.Game.Tests.NonVisual private class ModA : Mod { public override string Name => nameof(ModA); - public override string ShortenedName => nameof(ModA); + public override string Acronym => nameof(ModA); public override double ScoreMultiplier => 1; public override Type[] IncompatibleMods => new[] { typeof(ModIncompatibleWithA), typeof(ModIncompatibleWithAAndB) }; @@ -103,7 +103,7 @@ namespace osu.Game.Tests.NonVisual private class ModB : Mod { public override string Name => nameof(ModB); - public override string ShortenedName => nameof(ModB); + public override string Acronym => nameof(ModB); public override double ScoreMultiplier => 1; public override Type[] IncompatibleMods => new[] { typeof(ModIncompatibleWithAAndB) }; @@ -112,7 +112,7 @@ namespace osu.Game.Tests.NonVisual private class ModIncompatibleWithA : Mod { public override string Name => $"Incompatible With {nameof(ModA)}"; - public override string ShortenedName => $"Incompatible With {nameof(ModA)}"; + public override string Acronym => $"Incompatible With {nameof(ModA)}"; public override double ScoreMultiplier => 1; public override Type[] IncompatibleMods => new[] { typeof(ModA) }; @@ -130,7 +130,7 @@ namespace osu.Game.Tests.NonVisual private class ModIncompatibleWithAAndB : Mod { public override string Name => $"Incompatible With {nameof(ModA)} and {nameof(ModB)}"; - public override string ShortenedName => $"Incompatible With {nameof(ModA)} and {nameof(ModB)}"; + public override string Acronym => $"Incompatible With {nameof(ModA)} and {nameof(ModB)}"; public override double ScoreMultiplier => 1; public override Type[] IncompatibleMods => new[] { typeof(ModA), typeof(ModB) }; diff --git a/osu.Game.Tests/Resources/controlpoint-custom-samplebank.osu b/osu.Game.Tests/Resources/controlpoint-custom-samplebank.osu index 1e0e6f558e..8e7c504109 100644 --- a/osu.Game.Tests/Resources/controlpoint-custom-samplebank.osu +++ b/osu.Game.Tests/Resources/controlpoint-custom-samplebank.osu @@ -8,9 +8,12 @@ SampleSet: Normal 2638,-100,4,1,1,40,0,0 3107,-100,4,1,2,40,0,0 3576,-100,4,1,0,40,0,0 +18287,-100,4,2,11,80,0,1 +18595,-100,4,2,8,80,0,1 [HitObjects] 255,193,2170,1,0,0:0:0:0: 256,191,2638,5,0,0:0:0:0: 255,193,3107,1,0,0:0:0:0: 256,191,3576,1,0,0:0:0:0: +112,200,18493,6,0,L|104:248,1,35,8|0,0:0|0:0,0:0:0:0: \ No newline at end of file diff --git a/osu.Game.Tests/Resources/hitobject-no-addition-bank.osu b/osu.Game.Tests/Resources/hitobject-no-addition-bank.osu new file mode 100644 index 0000000000..43d0b8cc16 --- /dev/null +++ b/osu.Game.Tests/Resources/hitobject-no-addition-bank.osu @@ -0,0 +1,4 @@ +osu file format v14 + +[HitObjects] +444,320,1000,5,2,3:0:1:0: \ No newline at end of file diff --git a/osu.Game.Tests/Resources/slider-samples.osu b/osu.Game.Tests/Resources/slider-samples.osu new file mode 100644 index 0000000000..7759a2e35f --- /dev/null +++ b/osu.Game.Tests/Resources/slider-samples.osu @@ -0,0 +1,23 @@ +osu file format v14 + +[General] +SampleSet: Normal + +[Difficulty] + +SliderMultiplier:1.6 +SliderTickRate:2 + +[TimingPoints] +24735,389.61038961039,4,1,1,25,1,0 + +[HitObjects] +// Unified: Normal, Normal, Normal +168,256,30579,6,0,B|248:320|320:248,2,160 + +// Unified: Normal+Clap, Normal+Clap, Normal+Clap +168,256,32137,6,8,B|248:320|320:248,2,160 + +// Nodal: Normal+Whistle, Normal, Normal+Clap +// Nodal sounds should override the unified clap sound +168,256,33696,6,8,B|248:320|320:248,2,160,2|0|8 \ No newline at end of file diff --git a/osu.Game.Tests/Scores/IO/ImportScoreTest.cs b/osu.Game.Tests/Scores/IO/ImportScoreTest.cs new file mode 100644 index 0000000000..7ee7acd539 --- /dev/null +++ b/osu.Game.Tests/Scores/IO/ImportScoreTest.cs @@ -0,0 +1,166 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Platform; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Osu.Mods; +using osu.Game.Rulesets.Scoring; +using osu.Game.Scoring; +using osu.Game.Users; + +namespace osu.Game.Tests.Scores.IO +{ + public class ImportScoreTest + { + public const string TEST_OSZ_PATH = @"../../../../osu-resources/osu.Game.Resources/Beatmaps/241526 Soleily - Renatus.osz"; + + [Test] + public void TestBasicImport() + { + using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestBasicImport")) + { + try + { + var osu = loadOsu(host); + + var toImport = new ScoreInfo + { + Rank = ScoreRank.B, + TotalScore = 987654, + Accuracy = 0.8, + MaxCombo = 500, + Combo = 250, + User = new User { Username = "Test user" }, + Date = DateTimeOffset.Now, + OnlineScoreID = 12345, + }; + + var imported = loadIntoOsu(osu, toImport); + + Assert.AreEqual(toImport.Rank, imported.Rank); + Assert.AreEqual(toImport.TotalScore, imported.TotalScore); + Assert.AreEqual(toImport.Accuracy, imported.Accuracy); + Assert.AreEqual(toImport.MaxCombo, imported.MaxCombo); + Assert.AreEqual(toImport.Combo, imported.Combo); + Assert.AreEqual(toImport.User.Username, imported.User.Username); + Assert.AreEqual(toImport.Date, imported.Date); + Assert.AreEqual(toImport.OnlineScoreID, imported.OnlineScoreID); + } + finally + { + host.Exit(); + } + } + } + + [Test] + public void TestImportMods() + { + using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportMods")) + { + try + { + var osu = loadOsu(host); + + var toImport = new ScoreInfo + { + Mods = new Mod[] { new OsuModHardRock(), new OsuModDoubleTime() }, + }; + + var imported = loadIntoOsu(osu, toImport); + + Assert.IsTrue(imported.Mods.Any(m => m is OsuModHardRock)); + Assert.IsTrue(imported.Mods.Any(m => m is OsuModDoubleTime)); + } + finally + { + host.Exit(); + } + } + } + + [Test] + public void TestImportStatistics() + { + using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportStatistics")) + { + try + { + var osu = loadOsu(host); + + var toImport = new ScoreInfo + { + Statistics = new Dictionary + { + { HitResult.Perfect, 100 }, + { HitResult.Miss, 50 } + } + }; + + var imported = loadIntoOsu(osu, toImport); + + Assert.AreEqual(toImport.Statistics[HitResult.Perfect], imported.Statistics[HitResult.Perfect]); + Assert.AreEqual(toImport.Statistics[HitResult.Miss], imported.Statistics[HitResult.Miss]); + } + finally + { + host.Exit(); + } + } + } + + private ScoreInfo loadIntoOsu(OsuGameBase osu, ScoreInfo score) + { + var beatmapManager = osu.Dependencies.Get(); + + score.Beatmap = beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps.First(); + score.Ruleset = new OsuRuleset().RulesetInfo; + + var scoreManager = osu.Dependencies.Get(); + scoreManager.Import(score); + + return scoreManager.GetAllUsableScores().First(); + } + + private string createTemporaryBeatmap() + { + var temp = Path.GetTempFileName() + ".osz"; + File.Copy(TEST_OSZ_PATH, temp, true); + Assert.IsTrue(File.Exists(temp)); + return temp; + } + + private OsuGameBase loadOsu(GameHost host) + { + var osu = new OsuGameBase(); + Task.Run(() => host.Run(osu)); + waitForOrAssert(() => osu.IsLoaded, @"osu! failed to start in a reasonable amount of time"); + + var beatmapFile = createTemporaryBeatmap(); + var beatmapManager = osu.Dependencies.Get(); + beatmapManager.Import(beatmapFile); + + return osu; + } + + private void waitForOrAssert(Func result, string failureMessage, int timeout = 60000) + { + Task task = Task.Run(() => + { + while (!result()) Thread.Sleep(200); + }); + + Assert.IsTrue(task.Wait(timeout), failureMessage); + } + } +} diff --git a/osu.Game.Tests/ScrollAlgorithms/ConstantScrollTest.cs b/osu.Game.Tests/ScrollAlgorithms/ConstantScrollTest.cs new file mode 100644 index 0000000000..5e01213a48 --- /dev/null +++ b/osu.Game.Tests/ScrollAlgorithms/ConstantScrollTest.cs @@ -0,0 +1,54 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using NUnit.Framework; +using osu.Game.Rulesets.UI.Scrolling.Algorithms; + +namespace osu.Game.Tests.ScrollAlgorithms +{ + [TestFixture] + public class ConstantScrollTest + { + private IScrollAlgorithm algorithm; + + [SetUp] + public void Setup() + { + algorithm = new ConstantScrollAlgorithm(); + } + + [Test] + public void TestDisplayStartTime() + { + Assert.AreEqual(-8000, algorithm.GetDisplayStartTime(2000, 10000)); + Assert.AreEqual(-3000, algorithm.GetDisplayStartTime(2000, 5000)); + Assert.AreEqual(2000, algorithm.GetDisplayStartTime(7000, 5000)); + Assert.AreEqual(7000, algorithm.GetDisplayStartTime(17000, 10000)); + } + + [Test] + public void TestLength() + { + Assert.AreEqual(1f / 5, algorithm.GetLength(0, 1000, 5000, 1)); + Assert.AreEqual(1f / 5, algorithm.GetLength(6000, 7000, 5000, 1)); + } + + [Test] + public void TestPosition() + { + Assert.AreEqual(1f / 5, algorithm.PositionAt(1000, 0, 5000, 1)); + Assert.AreEqual(1f / 5, algorithm.PositionAt(6000, 5000, 5000, 1)); + } + + [TestCase(1000)] + [TestCase(10000)] + [TestCase(15000)] + [TestCase(20000)] + [TestCase(25000)] + public void TestTime(double time) + { + Assert.AreEqual(time, algorithm.TimeAt(algorithm.PositionAt(time, 0, 5000, 1), 0, 5000, 1), 0.001); + Assert.AreEqual(time, algorithm.TimeAt(algorithm.PositionAt(time, 5000, 5000, 1), 5000, 5000, 1), 0.001); + } + } +} diff --git a/osu.Game.Tests/ScrollAlgorithms/OverlappingScrollTest.cs b/osu.Game.Tests/ScrollAlgorithms/OverlappingScrollTest.cs new file mode 100644 index 0000000000..c1a5a0f3c9 --- /dev/null +++ b/osu.Game.Tests/ScrollAlgorithms/OverlappingScrollTest.cs @@ -0,0 +1,67 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using NUnit.Framework; +using osu.Framework.Lists; +using osu.Game.Rulesets.Timing; +using osu.Game.Rulesets.UI.Scrolling.Algorithms; + +namespace osu.Game.Tests.ScrollAlgorithms +{ + [TestFixture] + public class OverlappingScrollTest + { + private IScrollAlgorithm algorithm; + + [SetUp] + public void Setup() + { + var controlPoints = new SortedList + { + new MultiplierControlPoint(0) { Velocity = 1 }, + new MultiplierControlPoint(10000) { Velocity = 2f }, + new MultiplierControlPoint(20000) { Velocity = 0.5f } + }; + + algorithm = new OverlappingScrollAlgorithm(controlPoints); + } + + [Test] + public void TestDisplayStartTime() + { + Assert.AreEqual(1000, algorithm.GetDisplayStartTime(2000, 1000)); // Like constant + Assert.AreEqual(10000, algorithm.GetDisplayStartTime(10500, 1000)); // 10500 - (1000 * 0.5) + Assert.AreEqual(20000, algorithm.GetDisplayStartTime(22000, 1000)); // 23000 - (1000 / 0.5) + } + + [Test] + public void TestLength() + { + Assert.AreEqual(1f / 5, algorithm.GetLength(0, 1000, 5000, 1)); // Like constant + Assert.AreEqual(1f / 5, algorithm.GetLength(10000, 10500, 5000, 1)); // (10500 - 10000) / 0.5 / 5000 + Assert.AreEqual(1f / 5, algorithm.GetLength(20000, 22000, 5000, 1)); // (22000 - 20000) * 0.5 / 5000 + } + + [Test] + public void TestPosition() + { + // Basically same calculations as TestLength() + Assert.AreEqual(1f / 5, algorithm.PositionAt(1000, 0, 5000, 1)); + Assert.AreEqual(1f / 5, algorithm.PositionAt(10500, 10000, 5000, 1)); + Assert.AreEqual(1f / 5, algorithm.PositionAt(22000, 20000, 5000, 1)); + } + + [TestCase(1000)] + [TestCase(10000)] + [TestCase(15000)] + [TestCase(20000)] + [TestCase(25000)] + [Ignore("Disabled for now because overlapping control points have multiple time values under the same position." + + "Ideally, scrolling should be changed to constant or sequential during editing of hitobjects.")] + public void TestTime(double time) + { + Assert.AreEqual(time, algorithm.TimeAt(algorithm.PositionAt(time, 0, 5000, 1), 0, 5000, 1), 0.001); + Assert.AreEqual(time, algorithm.TimeAt(algorithm.PositionAt(time, 5000, 5000, 1), 5000, 5000, 1), 0.001); + } + } +} diff --git a/osu.Game.Tests/ScrollAlgorithms/SequentialScrollTest.cs b/osu.Game.Tests/ScrollAlgorithms/SequentialScrollTest.cs new file mode 100644 index 0000000000..990fb92e6c --- /dev/null +++ b/osu.Game.Tests/ScrollAlgorithms/SequentialScrollTest.cs @@ -0,0 +1,64 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using NUnit.Framework; +using osu.Framework.Lists; +using osu.Game.Rulesets.Timing; +using osu.Game.Rulesets.UI.Scrolling.Algorithms; + +namespace osu.Game.Tests.ScrollAlgorithms +{ + [TestFixture] + public class SequentialScrollTest + { + private IScrollAlgorithm algorithm; + + [SetUp] + public void Setup() + { + var controlPoints = new SortedList + { + new MultiplierControlPoint(0) { Velocity = 1 }, + new MultiplierControlPoint(10000) { Velocity = 2f }, + new MultiplierControlPoint(20000) { Velocity = 0.5f } + }; + + algorithm = new SequentialScrollAlgorithm(controlPoints); + } + + [Test] + public void TestDisplayStartTime() + { + // Sequential scroll algorithm approximates the start time + // This should be fixed in the future + } + + [Test] + public void TestLength() + { + Assert.AreEqual(1f / 5, algorithm.GetLength(0, 1000, 5000, 1)); // Like constant + Assert.AreEqual(1f / 5, algorithm.GetLength(10000, 10500, 5000, 1)); // (10500 - 10000) / 0.5 / 5000 + Assert.AreEqual(1f / 5, algorithm.GetLength(20000, 22000, 5000, 1)); // (22000 - 20000) * 0.5 / 5000 + } + + [Test] + public void TestPosition() + { + // Basically same calculations as TestLength() + Assert.AreEqual(1f / 5, algorithm.PositionAt(1000, 0, 5000, 1)); + Assert.AreEqual(1f / 5, algorithm.PositionAt(10500, 10000, 5000, 1)); + Assert.AreEqual(1f / 5, algorithm.PositionAt(22000, 20000, 5000, 1)); + } + + [TestCase(1000)] + [TestCase(10000)] + [TestCase(15000)] + [TestCase(20000)] + [TestCase(25000)] + public void TestTime(double time) + { + Assert.AreEqual(time, algorithm.TimeAt(algorithm.PositionAt(time, 0, 5000, 1), 0, 5000, 1), 0.001); + Assert.AreEqual(time, algorithm.TimeAt(algorithm.PositionAt(time, 5000, 5000, 1), 5000, 5000, 1), 0.001); + } + } +} diff --git a/osu.Game.Tests/Visual/TestCaseBeatDivisorControl.cs b/osu.Game.Tests/Visual/TestCaseBeatDivisorControl.cs index 1effa14e76..c11bb5d17b 100644 --- a/osu.Game.Tests/Visual/TestCaseBeatDivisorControl.cs +++ b/osu.Game.Tests/Visual/TestCaseBeatDivisorControl.cs @@ -5,8 +5,9 @@ using System; using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Graphics; -using osu.Game.Screens.Edit.Screens.Compose; -using OpenTK; +using osu.Game.Screens.Edit; +using osu.Game.Screens.Edit.Compose.Components; +using osuTK; namespace osu.Game.Tests.Visual { diff --git a/osu.Game.Tests/Visual/TestCaseBeatSyncedContainer.cs b/osu.Game.Tests/Visual/TestCaseBeatSyncedContainer.cs index 596b7839e0..ff383b6723 100644 --- a/osu.Game.Tests/Visual/TestCaseBeatSyncedContainer.cs +++ b/osu.Game.Tests/Visual/TestCaseBeatSyncedContainer.cs @@ -13,7 +13,7 @@ using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Overlays; -using OpenTK.Graphics; +using osuTK.Graphics; using osu.Framework.Lists; namespace osu.Game.Tests.Visual diff --git a/osu.Game.Tests/Visual/TestCaseBeatmapDetailArea.cs b/osu.Game.Tests/Visual/TestCaseBeatmapDetailArea.cs index 41d85673f8..4d19667926 100644 --- a/osu.Game.Tests/Visual/TestCaseBeatmapDetailArea.cs +++ b/osu.Game.Tests/Visual/TestCaseBeatmapDetailArea.cs @@ -4,7 +4,7 @@ using NUnit.Framework; using osu.Framework.Graphics; using osu.Game.Screens.Select; -using OpenTK; +using osuTK; namespace osu.Game.Tests.Visual { diff --git a/osu.Game.Tests/Visual/TestCaseBeatmapInfoWedge.cs b/osu.Game.Tests/Visual/TestCaseBeatmapInfoWedge.cs index 175db7d246..35cdfda92c 100644 --- a/osu.Game.Tests/Visual/TestCaseBeatmapInfoWedge.cs +++ b/osu.Game.Tests/Visual/TestCaseBeatmapInfoWedge.cs @@ -5,7 +5,7 @@ using System.Collections.Generic; using System.Linq; using JetBrains.Annotations; using NUnit.Framework; -using OpenTK; +using osuTK; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; diff --git a/osu.Game.Tests/Visual/TestCaseBeatmapOptionsOverlay.cs b/osu.Game.Tests/Visual/TestCaseBeatmapOptionsOverlay.cs index ffa433ef11..a56b82d887 100644 --- a/osu.Game.Tests/Visual/TestCaseBeatmapOptionsOverlay.cs +++ b/osu.Game.Tests/Visual/TestCaseBeatmapOptionsOverlay.cs @@ -4,8 +4,8 @@ using System.ComponentModel; using osu.Game.Graphics; using osu.Game.Screens.Select.Options; -using OpenTK.Graphics; -using OpenTK.Input; +using osuTK.Graphics; +using osuTK.Input; namespace osu.Game.Tests.Visual { diff --git a/osu.Game.Tests/Visual/TestCaseBeatmapScoresContainer.cs b/osu.Game.Tests/Visual/TestCaseBeatmapScoresContainer.cs index d3098864f4..ec85b33919 100644 --- a/osu.Game.Tests/Visual/TestCaseBeatmapScoresContainer.cs +++ b/osu.Game.Tests/Visual/TestCaseBeatmapScoresContainer.cs @@ -16,15 +16,16 @@ using osu.Framework.Graphics.Containers; using osu.Game.Beatmaps; using osu.Game.Online.API.Requests.Responses; using osu.Game.Rulesets.Osu; +using osu.Game.Scoring; namespace osu.Game.Tests.Visual { [System.ComponentModel.Description("in BeatmapOverlay")] public class TestCaseBeatmapScoresContainer : OsuTestCase { - private readonly IEnumerable scores; - private readonly IEnumerable anotherScores; - private readonly APIScore topScore; + private readonly IEnumerable scores; + private readonly IEnumerable anotherScores; + private readonly APIScoreInfo topScoreInfo; private readonly Box background; public TestCaseBeatmapScoresContainer() @@ -48,7 +49,7 @@ namespace osu.Game.Tests.Visual AddStep("scores pack 1", () => scoresContainer.Scores = scores); AddStep("scores pack 2", () => scoresContainer.Scores = anotherScores); - AddStep("only top score", () => scoresContainer.Scores = new[] { topScore }); + AddStep("only top score", () => scoresContainer.Scores = new[] { topScoreInfo }); AddStep("remove scores", () => scoresContainer.Scores = null); AddStep("resize to big", () => container.ResizeWidthTo(1, 300)); AddStep("resize to normal", () => container.ResizeWidthTo(0.8f, 300)); @@ -57,7 +58,7 @@ namespace osu.Game.Tests.Visual scores = new[] { - new APIScore + new APIScoreInfo { User = new User { @@ -80,7 +81,7 @@ namespace osu.Game.Tests.Visual TotalScore = 1234567890, Accuracy = 1, }, - new APIScore + new APIScoreInfo { User = new User { @@ -102,7 +103,7 @@ namespace osu.Game.Tests.Visual TotalScore = 1234789, Accuracy = 0.9997, }, - new APIScore + new APIScoreInfo { User = new User { @@ -123,7 +124,7 @@ namespace osu.Game.Tests.Visual TotalScore = 12345678, Accuracy = 0.9854, }, - new APIScore + new APIScoreInfo { User = new User { @@ -143,7 +144,7 @@ namespace osu.Game.Tests.Visual TotalScore = 1234567, Accuracy = 0.8765, }, - new APIScore + new APIScoreInfo { User = new User { @@ -169,7 +170,7 @@ namespace osu.Game.Tests.Visual anotherScores = new[] { - new APIScore + new APIScoreInfo { User = new User { @@ -191,7 +192,7 @@ namespace osu.Game.Tests.Visual TotalScore = 1234789, Accuracy = 0.9997, }, - new APIScore + new APIScoreInfo { User = new User { @@ -214,7 +215,7 @@ namespace osu.Game.Tests.Visual TotalScore = 1234567890, Accuracy = 1, }, - new APIScore + new APIScoreInfo { User = new User { @@ -230,7 +231,7 @@ namespace osu.Game.Tests.Visual TotalScore = 123456, Accuracy = 0.6543, }, - new APIScore + new APIScoreInfo { User = new User { @@ -251,7 +252,7 @@ namespace osu.Game.Tests.Visual TotalScore = 12345678, Accuracy = 0.9854, }, - new APIScore + new APIScoreInfo { User = new User { @@ -279,7 +280,7 @@ namespace osu.Game.Tests.Visual s.Statistics.Add(HitResult.Meh, RNG.Next(2000)); } - topScore = new APIScore + topScoreInfo = new APIScoreInfo { User = new User { @@ -301,9 +302,9 @@ namespace osu.Game.Tests.Visual TotalScore = 987654321, Accuracy = 0.8487, }; - topScore.Statistics.Add(HitResult.Great, RNG.Next(2000)); - topScore.Statistics.Add(HitResult.Good, RNG.Next(2000)); - topScore.Statistics.Add(HitResult.Meh, RNG.Next(2000)); + topScoreInfo.Statistics.Add(HitResult.Great, RNG.Next(2000)); + topScoreInfo.Statistics.Add(HitResult.Good, RNG.Next(2000)); + topScoreInfo.Statistics.Add(HitResult.Meh, RNG.Next(2000)); } [BackgroundDependencyLoader] diff --git a/osu.Game.Tests/Visual/TestCaseButtonSystem.cs b/osu.Game.Tests/Visual/TestCaseButtonSystem.cs index 7f8133d638..0506783310 100644 --- a/osu.Game.Tests/Visual/TestCaseButtonSystem.cs +++ b/osu.Game.Tests/Visual/TestCaseButtonSystem.cs @@ -9,7 +9,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Shapes; using osu.Game.Screens.Menu; -using OpenTK.Graphics; +using osuTK.Graphics; namespace osu.Game.Tests.Visual { diff --git a/osu.Game.Tests/Visual/TestCaseChannelTabControl.cs b/osu.Game.Tests/Visual/TestCaseChannelTabControl.cs new file mode 100644 index 0000000000..447337bef0 --- /dev/null +++ b/osu.Game.Tests/Visual/TestCaseChannelTabControl.cs @@ -0,0 +1,123 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; +using osu.Framework.MathUtils; +using osu.Game.Online.API; +using osu.Game.Online.API.Requests; +using osu.Game.Online.Chat; +using osu.Game.Overlays.Chat.Tabs; +using osu.Game.Users; +using osuTK.Graphics; + +namespace osu.Game.Tests.Visual +{ + public class TestCaseChannelTabControl : OsuTestCase + { + public override IReadOnlyList RequiredTypes => new[] + { + typeof(ChannelTabControl), + }; + + private readonly ChannelTabControl channelTabControl; + + public TestCaseChannelTabControl() + { + SpriteText currentText; + Add(new Container + { + RelativeSizeAxes = Axes.X, + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + Children = new Drawable[] + { + channelTabControl = new ChannelTabControl + { + RelativeSizeAxes = Axes.X, + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + Height = 50 + }, + new Box + { + Colour = Color4.Black.Opacity(0.1f), + RelativeSizeAxes = Axes.X, + Height = 50, + Depth = -1, + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + } + } + }); + + Add(new Container + { + Origin = Anchor.TopLeft, + Anchor = Anchor.TopLeft, + Children = new Drawable[] + { + currentText = new SpriteText + { + Text = "Currently selected channel:" + } + } + }); + + channelTabControl.OnRequestLeave += channel => channelTabControl.RemoveChannel(channel); + channelTabControl.Current.ValueChanged += channel => currentText.Text = "Currently selected channel: " + channel.ToString(); + + AddStep("Add random private channel", addRandomUser); + AddAssert("There is only one channels", () => channelTabControl.Items.Count() == 2); + AddRepeatStep("Add 3 random private channels", addRandomUser, 3); + AddAssert("There are four channels", () => channelTabControl.Items.Count() == 5); + AddStep("Add random public channel", () => addChannel(RNG.Next().ToString())); + + AddRepeatStep("Select a random channel", () => channelTabControl.Current.Value = channelTabControl.Items.ElementAt(RNG.Next(channelTabControl.Items.Count())), 20); + } + + private List users; + + private void addRandomUser() + { + channelTabControl.AddChannel(new Channel + { + Users = + { + users?.Count > 0 + ? users[RNG.Next(0, users.Count - 1)] + : new User + { + Id = RNG.Next(), + Username = "testuser" + RNG.Next(1000) + } + } + }); + } + + private void addChannel(string name) + { + channelTabControl.AddChannel(new Channel + { + Type = ChannelType.Public, + Name = name + }); + } + + [BackgroundDependencyLoader] + private void load(IAPIProvider api) + { + GetUsersRequest req = new GetUsersRequest(); + req.Success += list => users = list.Select(e => e.User).ToList(); + + api.Queue(req); + } + } +} diff --git a/osu.Game.Tests/Visual/TestCaseChatDisplay.cs b/osu.Game.Tests/Visual/TestCaseChatDisplay.cs index c03b12bdc1..e3bd4026b3 100644 --- a/osu.Game.Tests/Visual/TestCaseChatDisplay.cs +++ b/osu.Game.Tests/Visual/TestCaseChatDisplay.cs @@ -1,21 +1,45 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using System; +using System.Collections.Generic; using System.ComponentModel; +using osu.Framework.Allocation; +using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Game.Online.Chat; using osu.Game.Overlays; +using osu.Game.Overlays.Chat; +using osu.Game.Overlays.Chat.Tabs; namespace osu.Game.Tests.Visual { [Description("Testing chat api and overlay")] public class TestCaseChatDisplay : OsuTestCase { - public TestCaseChatDisplay() + public override IReadOnlyList RequiredTypes => new[] { - Add(new ChatOverlay + typeof(ChatOverlay), + typeof(ChatLine), + typeof(DrawableChannel), + typeof(ChannelSelectorTabItem), + typeof(ChannelTabControl), + typeof(ChannelTabItem), + typeof(PrivateChannelTabItem), + typeof(TabCloseButton) + }; + + [Cached] + private readonly ChannelManager channelManager = new ChannelManager(); + + [BackgroundDependencyLoader] + private void load() + { + Children = new Drawable[] { - State = Visibility.Visible - }); + channelManager, + new ChatOverlay { State = Visibility.Visible } + }; } } } diff --git a/osu.Game.Tests/Visual/TestCaseChatLink.cs b/osu.Game.Tests/Visual/TestCaseChatLink.cs index 12a7ee9c12..61c2f47e7d 100644 --- a/osu.Game.Tests/Visual/TestCaseChatLink.cs +++ b/osu.Game.Tests/Visual/TestCaseChatLink.cs @@ -1,7 +1,7 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using OpenTK.Graphics; +using osuTK.Graphics; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -16,6 +16,7 @@ using NUnit.Framework; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Overlays; +using osu.Framework.Configuration; namespace osu.Game.Tests.Visual { @@ -23,6 +24,7 @@ namespace osu.Game.Tests.Visual public class TestCaseChatLink : OsuTestCase { private readonly TestChatLineContainer textContainer; + private readonly DialogOverlay dialogOverlay; private Color4 linkColour; public override IReadOnlyList RequiredTypes => new[] @@ -37,6 +39,7 @@ namespace osu.Game.Tests.Visual public TestCaseChatLink() { + Add(dialogOverlay = new DialogOverlay { Depth = float.MinValue }); Add(textContainer = new TestChatLineContainer { Padding = new MarginPadding { Left = 20, Right = 20 }, @@ -50,14 +53,15 @@ namespace osu.Game.Tests.Visual private void load(OsuColour colours) { linkColour = colours.Blue; - Dependencies.Cache(new ChatOverlay - { - AvailableChannels = - { - new Channel { Name = "#english" }, - new Channel { Name = "#japanese" } - } - }); + + var chatManager = new ChannelManager(); + BindableCollection availableChannels = (BindableCollection)chatManager.AvailableChannels; + availableChannels.Add(new Channel { Name = "#english"}); + availableChannels.Add(new Channel { Name = "#japanese" }); + Dependencies.Cache(chatManager); + + Dependencies.Cache(new ChatOverlay()); + Dependencies.Cache(dialogOverlay); testLinksGeneral(); testEcho(); diff --git a/osu.Game.Tests/Visual/TestCaseContextMenu.cs b/osu.Game.Tests/Visual/TestCaseContextMenu.cs index 80505b219b..a0d0eaf015 100644 --- a/osu.Game.Tests/Visual/TestCaseContextMenu.cs +++ b/osu.Game.Tests/Visual/TestCaseContextMenu.cs @@ -8,8 +8,8 @@ using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.UserInterface; using osu.Game.Graphics.UserInterface; -using OpenTK; -using OpenTK.Graphics; +using osuTK; +using osuTK.Graphics; using osu.Game.Graphics.Cursor; namespace osu.Game.Tests.Visual diff --git a/osu.Game.Tests/Visual/TestCaseCursors.cs b/osu.Game.Tests/Visual/TestCaseCursors.cs index 1f409f043e..293ca392ab 100644 --- a/osu.Game.Tests/Visual/TestCaseCursors.cs +++ b/osu.Game.Tests/Visual/TestCaseCursors.cs @@ -11,8 +11,8 @@ using osu.Framework.Input.Events; using osu.Framework.MathUtils; using osu.Game.Graphics.Cursor; using osu.Game.Graphics.Sprites; -using OpenTK; -using OpenTK.Graphics; +using osuTK; +using osuTK.Graphics; namespace osu.Game.Tests.Visual { diff --git a/osu.Game.Tests/Visual/TestCaseDisclaimer.cs b/osu.Game.Tests/Visual/TestCaseDisclaimer.cs index a8253a991a..b6b9a7adb4 100644 --- a/osu.Game.Tests/Visual/TestCaseDisclaimer.cs +++ b/osu.Game.Tests/Visual/TestCaseDisclaimer.cs @@ -5,7 +5,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Shapes; using osu.Game.Screens.Menu; -using OpenTK.Graphics; +using osuTK.Graphics; namespace osu.Game.Tests.Visual { diff --git a/osu.Game.Tests/Visual/TestCaseEditorCompose.cs b/osu.Game.Tests/Visual/TestCaseEditorCompose.cs index e7bcfbf500..5c53fdfac4 100644 --- a/osu.Game.Tests/Visual/TestCaseEditorCompose.cs +++ b/osu.Game.Tests/Visual/TestCaseEditorCompose.cs @@ -6,7 +6,7 @@ using System.Collections.Generic; using NUnit.Framework; using osu.Framework.Allocation; using osu.Game.Rulesets.Osu; -using osu.Game.Screens.Edit.Screens.Compose; +using osu.Game.Screens.Edit.Compose; using osu.Game.Tests.Beatmaps; namespace osu.Game.Tests.Visual @@ -14,13 +14,13 @@ namespace osu.Game.Tests.Visual [TestFixture] public class TestCaseEditorCompose : EditorClockTestCase { - public override IReadOnlyList RequiredTypes => new[] { typeof(Compose) }; + public override IReadOnlyList RequiredTypes => new[] { typeof(ComposeScreen) }; [BackgroundDependencyLoader] private void load() { Beatmap.Value = new TestWorkingBeatmap(new OsuRuleset().RulesetInfo); - Child = new Compose(); + Child = new ComposeScreen(); } } } diff --git a/osu.Game.Tests/Visual/TestCaseEditorComposeRadioButtons.cs b/osu.Game.Tests/Visual/TestCaseEditorComposeRadioButtons.cs index 09f390ab74..9df36b0bc1 100644 --- a/osu.Game.Tests/Visual/TestCaseEditorComposeRadioButtons.cs +++ b/osu.Game.Tests/Visual/TestCaseEditorComposeRadioButtons.cs @@ -5,7 +5,7 @@ using System; using System.Collections.Generic; using NUnit.Framework; using osu.Framework.Graphics; -using osu.Game.Screens.Edit.Screens.Compose.RadioButtons; +using osu.Game.Screens.Edit.Components.RadioButtons; namespace osu.Game.Tests.Visual { diff --git a/osu.Game.Tests/Visual/TestCaseEditorComposeTimeline.cs b/osu.Game.Tests/Visual/TestCaseEditorComposeTimeline.cs index 9ad8bf7b92..f548dfe666 100644 --- a/osu.Game.Tests/Visual/TestCaseEditorComposeTimeline.cs +++ b/osu.Game.Tests/Visual/TestCaseEditorComposeTimeline.cs @@ -6,15 +6,15 @@ using System.Collections.Generic; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Configuration; -using OpenTK; +using osuTK; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.UserInterface; using osu.Framework.Timing; using osu.Game.Beatmaps; -using osu.Game.Screens.Edit.Screens.Compose.Timeline; -using OpenTK.Graphics; +using osu.Game.Screens.Edit.Compose.Components.Timeline; +using osuTK.Graphics; namespace osu.Game.Tests.Visual { diff --git a/osu.Game.Tests/Visual/TestCaseEditorMenuBar.cs b/osu.Game.Tests/Visual/TestCaseEditorMenuBar.cs index cb4438b2ba..eab799011d 100644 --- a/osu.Game.Tests/Visual/TestCaseEditorMenuBar.cs +++ b/osu.Game.Tests/Visual/TestCaseEditorMenuBar.cs @@ -8,7 +8,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.UserInterface; using osu.Game.Graphics.UserInterface; -using osu.Game.Screens.Edit.Menus; +using osu.Game.Screens.Edit.Components.Menus; namespace osu.Game.Tests.Visual { diff --git a/osu.Game.Tests/Visual/TestCaseEditorSeekSnapping.cs b/osu.Game.Tests/Visual/TestCaseEditorSeekSnapping.cs index dace6e20ef..8bbbe6b918 100644 --- a/osu.Game.Tests/Visual/TestCaseEditorSeekSnapping.cs +++ b/osu.Game.Tests/Visual/TestCaseEditorSeekSnapping.cs @@ -11,8 +11,8 @@ using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Tests.Beatmaps; -using OpenTK; -using OpenTK.Graphics; +using osuTK; +using osuTK.Graphics; namespace osu.Game.Tests.Visual { diff --git a/osu.Game.Tests/Visual/TestCaseEditorSummaryTimeline.cs b/osu.Game.Tests/Visual/TestCaseEditorSummaryTimeline.cs index cafd1b6f1a..ff28a0aad2 100644 --- a/osu.Game.Tests/Visual/TestCaseEditorSummaryTimeline.cs +++ b/osu.Game.Tests/Visual/TestCaseEditorSummaryTimeline.cs @@ -6,7 +6,7 @@ using System.Collections.Generic; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; -using OpenTK; +using osuTK; using osu.Game.Screens.Edit.Components.Timelines.Summary; using osu.Game.Rulesets.Osu; using osu.Game.Tests.Beatmaps; diff --git a/osu.Game.Tests/Visual/TestCaseExternalLinkButton.cs b/osu.Game.Tests/Visual/TestCaseExternalLinkButton.cs index 7d8535f428..07bc59b29c 100644 --- a/osu.Game.Tests/Visual/TestCaseExternalLinkButton.cs +++ b/osu.Game.Tests/Visual/TestCaseExternalLinkButton.cs @@ -4,7 +4,7 @@ using System; using System.Collections.Generic; using osu.Game.Graphics.UserInterface; -using OpenTK; +using osuTK; namespace osu.Game.Tests.Visual { diff --git a/osu.Game.Tests/Visual/TestCaseGameplayMenuOverlay.cs b/osu.Game.Tests/Visual/TestCaseGameplayMenuOverlay.cs index 417b0f94d7..ee97ff7af7 100644 --- a/osu.Game.Tests/Visual/TestCaseGameplayMenuOverlay.cs +++ b/osu.Game.Tests/Visual/TestCaseGameplayMenuOverlay.cs @@ -5,12 +5,12 @@ using System; using System.Collections.Generic; using System.ComponentModel; using System.Linq; -using OpenTK.Input; +using osuTK.Input; using osu.Framework.Allocation; using osu.Framework.Graphics.Containers; using osu.Framework.Logging; using osu.Game.Screens.Play; -using OpenTK; +using osuTK; namespace osu.Game.Tests.Visual { diff --git a/osu.Game.Tests/Visual/TestCaseGraph.cs b/osu.Game.Tests/Visual/TestCaseGraph.cs index 40b05d51e6..0b0a80648b 100644 --- a/osu.Game.Tests/Visual/TestCaseGraph.cs +++ b/osu.Game.Tests/Visual/TestCaseGraph.cs @@ -5,7 +5,7 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Graphics; using osu.Game.Graphics.UserInterface; -using OpenTK; +using osuTK; namespace osu.Game.Tests.Visual { diff --git a/osu.Game.Tests/Visual/TestCaseHitObjectComposer.cs b/osu.Game.Tests/Visual/TestCaseHitObjectComposer.cs index 825aef75c9..6620fbeef3 100644 --- a/osu.Game.Tests/Visual/TestCaseHitObjectComposer.cs +++ b/osu.Game.Tests/Visual/TestCaseHitObjectComposer.cs @@ -7,31 +7,41 @@ using JetBrains.Annotations; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Timing; -using OpenTK; +using osuTK; using osu.Game.Beatmaps; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Edit; +using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles; +using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components; using osu.Game.Rulesets.Osu.Objects; -using osu.Game.Screens.Edit.Screens.Compose.Layers; +using osu.Game.Screens.Edit.Compose; +using osu.Game.Screens.Edit.Compose.Components; using osu.Game.Tests.Beatmaps; namespace osu.Game.Tests.Visual { [TestFixture] - public class TestCaseHitObjectComposer : OsuTestCase + [Cached(Type = typeof(IPlacementHandler))] + public class TestCaseHitObjectComposer : OsuTestCase, IPlacementHandler { public override IReadOnlyList RequiredTypes => new[] { - typeof(MaskSelection), - typeof(DragLayer), + typeof(SelectionHandler), + typeof(DragBox), typeof(HitObjectComposer), typeof(OsuHitObjectComposer), - typeof(HitObjectMaskLayer), - typeof(NotNullAttribute) + typeof(BlueprintContainer), + typeof(NotNullAttribute), + typeof(HitCirclePiece), + typeof(HitCircleSelectionBlueprint), + typeof(HitCirclePlacementBlueprint), }; + private HitObjectComposer composer; + [BackgroundDependencyLoader] private void load() { @@ -44,12 +54,11 @@ namespace osu.Game.Tests.Visual new Slider { Position = new Vector2(128, 256), - ControlPoints = new[] + Path = new SliderPath(PathType.Linear, new[] { Vector2.Zero, new Vector2(216, 0), - }, - Distance = 216, + }), Scale = 0.5f, } }, @@ -59,7 +68,15 @@ namespace osu.Game.Tests.Visual Dependencies.CacheAs(clock); Dependencies.CacheAs(clock); - Child = new OsuHitObjectComposer(new OsuRuleset()); + Child = composer = new OsuHitObjectComposer(new OsuRuleset()); } + + public void BeginPlacement(HitObject hitObject) + { + } + + public void EndPlacement(HitObject hitObject) => composer.Add(hitObject); + + public void Delete(HitObject hitObject) => composer.Remove(hitObject); } } diff --git a/osu.Game.Tests/Visual/TestCaseQuitButton.cs b/osu.Game.Tests/Visual/TestCaseHoldForMenuButton.cs similarity index 75% rename from osu.Game.Tests/Visual/TestCaseQuitButton.cs rename to osu.Game.Tests/Visual/TestCaseHoldForMenuButton.cs index a427b7a20a..944921bc2e 100644 --- a/osu.Game.Tests/Visual/TestCaseQuitButton.cs +++ b/osu.Game.Tests/Visual/TestCaseHoldForMenuButton.cs @@ -7,31 +7,31 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Game.Screens.Play.HUD; -using OpenTK; -using OpenTK.Input; +using osuTK; +using osuTK.Input; namespace osu.Game.Tests.Visual { [Description("'Hold to Quit' UI element")] - public class TestCaseQuitButton : ManualInputManagerTestCase + public class TestCaseHoldForMenuButton : ManualInputManagerTestCase { private bool exitAction; [BackgroundDependencyLoader] private void load() { - QuitButton quitButton; + HoldForMenuButton holdForMenuButton; - Add(quitButton = new QuitButton + Add(holdForMenuButton = new HoldForMenuButton { Origin = Anchor.BottomRight, Anchor = Anchor.BottomRight, Action = () => exitAction = true }); - var text = quitButton.Children.OfType().First(); + var text = holdForMenuButton.Children.OfType().First(); - AddStep("Trigger text fade in", () => InputManager.MoveMouseTo(quitButton)); + AddStep("Trigger text fade in", () => InputManager.MoveMouseTo(holdForMenuButton)); AddUntilStep(() => text.IsPresent && !exitAction, "Text visible"); AddStep("Trigger text fade out", () => InputManager.MoveMouseTo(Vector2.One)); AddUntilStep(() => !text.IsPresent && !exitAction, "Text is not visible"); @@ -39,7 +39,7 @@ namespace osu.Game.Tests.Visual AddStep("Trigger exit action", () => { exitAction = false; - InputManager.MoveMouseTo(quitButton); + InputManager.MoveMouseTo(holdForMenuButton); InputManager.PressButton(MouseButton.Left); }); @@ -47,7 +47,7 @@ namespace osu.Game.Tests.Visual AddAssert("action not triggered", () => !exitAction); AddStep("Trigger exit action", () => InputManager.PressButton(MouseButton.Left)); - AddUntilStep(() => exitAction, $"{nameof(quitButton.Action)} was triggered"); + AddUntilStep(() => exitAction, $"{nameof(holdForMenuButton.Action)} was triggered"); } } } diff --git a/osu.Game.Tests/Visual/TestCaseIconButton.cs b/osu.Game.Tests/Visual/TestCaseIconButton.cs index 14cba71ec8..d1f0727af5 100644 --- a/osu.Game.Tests/Visual/TestCaseIconButton.cs +++ b/osu.Game.Tests/Visual/TestCaseIconButton.cs @@ -2,8 +2,8 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using NUnit.Framework; -using OpenTK; -using OpenTK.Graphics; +using osuTK; +using osuTK.Graphics; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; diff --git a/osu.Game.Tests/Visual/TestCaseIdleTracker.cs b/osu.Game.Tests/Visual/TestCaseIdleTracker.cs new file mode 100644 index 0000000000..33e2df9ff0 --- /dev/null +++ b/osu.Game.Tests/Visual/TestCaseIdleTracker.cs @@ -0,0 +1,135 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using NUnit.Framework; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Input; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Tests.Visual +{ + [TestFixture] + public class TestCaseIdleTracker : ManualInputManagerTestCase + { + private readonly IdleTrackingBox box1; + private readonly IdleTrackingBox box2; + private readonly IdleTrackingBox box3; + private readonly IdleTrackingBox box4; + + public TestCaseIdleTracker() + { + Children = new Drawable[] + { + box1 = new IdleTrackingBox(1000) + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Red, + Anchor = Anchor.TopLeft, + Origin = Anchor.TopLeft, + }, + box2 = new IdleTrackingBox(2000) + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Green, + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + }, + box3 = new IdleTrackingBox(3000) + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Blue, + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + }, + box4 = new IdleTrackingBox(4000) + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Orange, + Anchor = Anchor.BottomRight, + Origin = Anchor.BottomRight, + }, + }; + } + + [Test] + public void TestNudge() + { + AddStep("move mouse to top left", () => InputManager.MoveMouseTo(box1.ScreenSpaceDrawQuad.Centre)); + + AddUntilStep(() => box1.IsIdle && box2.IsIdle && box3.IsIdle && box4.IsIdle, "Wait for all idle"); + + AddStep("nudge mouse", () => InputManager.MoveMouseTo(box1.ScreenSpaceDrawQuad.Centre + new Vector2(1))); + + AddAssert("check not idle", () => !box1.IsIdle); + AddAssert("check idle", () => box2.IsIdle); + AddAssert("check idle", () => box3.IsIdle); + AddAssert("check idle", () => box4.IsIdle); + } + + [Test] + public void TestMovement() + { + AddStep("move mouse", () => InputManager.MoveMouseTo(box2.ScreenSpaceDrawQuad.Centre)); + + AddAssert("check not idle", () => box1.IsIdle); + AddAssert("check not idle", () => !box2.IsIdle); + AddAssert("check idle", () => box3.IsIdle); + AddAssert("check idle", () => box4.IsIdle); + + AddStep("move mouse", () => InputManager.MoveMouseTo(box3.ScreenSpaceDrawQuad.Centre)); + AddStep("move mouse", () => InputManager.MoveMouseTo(box4.ScreenSpaceDrawQuad.Centre)); + + AddAssert("check not idle", () => box1.IsIdle); + AddAssert("check not idle", () => !box2.IsIdle); + AddAssert("check idle", () => !box3.IsIdle); + AddAssert("check idle", () => !box4.IsIdle); + + AddUntilStep(() => box1.IsIdle && box2.IsIdle && box3.IsIdle && box4.IsIdle, "Wait for all idle"); + } + + [Test] + public void TestTimings() + { + AddStep("move mouse", () => InputManager.MoveMouseTo(ScreenSpaceDrawQuad.Centre)); + + AddAssert("check not idle", () => !box1.IsIdle && !box2.IsIdle && !box3.IsIdle && !box4.IsIdle); + AddUntilStep(() => box1.IsIdle, "Wait for idle"); + AddAssert("check not idle", () => !box2.IsIdle && !box3.IsIdle && !box4.IsIdle); + AddUntilStep(() => box2.IsIdle, "Wait for idle"); + AddAssert("check not idle", () => !box3.IsIdle && !box4.IsIdle); + AddUntilStep(() => box3.IsIdle, "Wait for idle"); + + AddUntilStep(() => box1.IsIdle && box2.IsIdle && box3.IsIdle && box4.IsIdle, "Wait for all idle"); + } + + private class IdleTrackingBox : CompositeDrawable + { + private readonly IdleTracker idleTracker; + + public bool IsIdle => idleTracker.IsIdle.Value; + + public IdleTrackingBox(double timeToIdle) + { + Box box; + + Alpha = 0.6f; + Scale = new Vector2(0.6f); + + InternalChildren = new Drawable[] + { + idleTracker = new IdleTracker(timeToIdle), + box = new Box + { + Colour = Color4.White, + RelativeSizeAxes = Axes.Both, + }, + }; + + idleTracker.IsIdle.BindValueChanged(idle => box.Colour = idle ? Color4.White : Color4.Black, true); + } + } + } +} diff --git a/osu.Game.Tests/Visual/TestCaseIntroSequence.cs b/osu.Game.Tests/Visual/TestCaseIntroSequence.cs index 4982686505..9ce8a4ddd6 100644 --- a/osu.Game.Tests/Visual/TestCaseIntroSequence.cs +++ b/osu.Game.Tests/Visual/TestCaseIntroSequence.cs @@ -4,7 +4,7 @@ using System; using System.Collections.Generic; using NUnit.Framework; -using OpenTK.Graphics; +using osuTK.Graphics; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; diff --git a/osu.Game.Tests/Visual/TestCaseKeyCounter.cs b/osu.Game.Tests/Visual/TestCaseKeyCounter.cs index 178b47ed33..465b943651 100644 --- a/osu.Game.Tests/Visual/TestCaseKeyCounter.cs +++ b/osu.Game.Tests/Visual/TestCaseKeyCounter.cs @@ -9,7 +9,7 @@ using osu.Framework.Graphics; using osu.Framework.MathUtils; using osu.Framework.Timing; using osu.Game.Screens.Play; -using OpenTK.Input; +using osuTK.Input; namespace osu.Game.Tests.Visual { diff --git a/osu.Game.Tests/Visual/TestCaseLabelledTextBox.cs b/osu.Game.Tests/Visual/TestCaseLabelledTextBox.cs index d41739bfb5..e1470a860e 100644 --- a/osu.Game.Tests/Visual/TestCaseLabelledTextBox.cs +++ b/osu.Game.Tests/Visual/TestCaseLabelledTextBox.cs @@ -5,9 +5,9 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Game.Screens.Edit.Screens.Setup.Components.LabelledComponents; using System; using System.Collections.Generic; +using osu.Game.Screens.Edit.Setup.Components.LabelledComponents; namespace osu.Game.Tests.Visual { diff --git a/osu.Game.Tests/Visual/TestCaseLeaderboard.cs b/osu.Game.Tests/Visual/TestCaseLeaderboard.cs index e8ac19e7fc..f7630f0902 100644 --- a/osu.Game.Tests/Visual/TestCaseLeaderboard.cs +++ b/osu.Game.Tests/Visual/TestCaseLeaderboard.cs @@ -5,14 +5,14 @@ using System; using System.Collections.Generic; using System.ComponentModel; using osu.Framework.Graphics; -using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Select.Leaderboards; using osu.Game.Users; using osu.Framework.Allocation; -using OpenTK; +using osuTK; using System.Linq; using osu.Game.Beatmaps; using osu.Game.Rulesets; +using osu.Game.Scoring; namespace osu.Game.Tests.Visual { @@ -58,7 +58,7 @@ namespace osu.Game.Tests.Visual { var scores = new[] { - new Score + new ScoreInfo { Rank = ScoreRank.XH, Accuracy = 1, @@ -76,7 +76,7 @@ namespace osu.Game.Tests.Visual }, }, }, - new Score + new ScoreInfo { Rank = ScoreRank.X, Accuracy = 1, @@ -94,7 +94,7 @@ namespace osu.Game.Tests.Visual }, }, }, - new Score + new ScoreInfo { Rank = ScoreRank.SH, Accuracy = 1, @@ -112,7 +112,7 @@ namespace osu.Game.Tests.Visual }, }, }, - new Score + new ScoreInfo { Rank = ScoreRank.S, Accuracy = 1, @@ -130,7 +130,7 @@ namespace osu.Game.Tests.Visual }, }, }, - new Score + new ScoreInfo { Rank = ScoreRank.A, Accuracy = 1, @@ -148,7 +148,7 @@ namespace osu.Game.Tests.Visual }, }, }, - new Score + new ScoreInfo { Rank = ScoreRank.B, Accuracy = 0.9826, @@ -166,7 +166,7 @@ namespace osu.Game.Tests.Visual }, }, }, - new Score + new ScoreInfo { Rank = ScoreRank.C, Accuracy = 0.9654, @@ -184,7 +184,7 @@ namespace osu.Game.Tests.Visual }, }, }, - new Score + new ScoreInfo { Rank = ScoreRank.F, Accuracy = 0.6025, @@ -202,7 +202,7 @@ namespace osu.Game.Tests.Visual }, }, }, - new Score + new ScoreInfo { Rank = ScoreRank.F, Accuracy = 0.5140, @@ -220,7 +220,7 @@ namespace osu.Game.Tests.Visual }, }, }, - new Score + new ScoreInfo { Rank = ScoreRank.F, Accuracy = 0.4222, diff --git a/osu.Game.Tests/Visual/TestCaseLoaderAnimation.cs b/osu.Game.Tests/Visual/TestCaseLoaderAnimation.cs index 600784f8db..da2427ff6f 100644 --- a/osu.Game.Tests/Visual/TestCaseLoaderAnimation.cs +++ b/osu.Game.Tests/Visual/TestCaseLoaderAnimation.cs @@ -6,7 +6,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Shapes; using osu.Game.Screens; using osu.Game.Screens.Menu; -using OpenTK.Graphics; +using osuTK.Graphics; namespace osu.Game.Tests.Visual { diff --git a/osu.Game.Tests/Visual/TestCaseLoadingAnimation.cs b/osu.Game.Tests/Visual/TestCaseLoadingAnimation.cs index ebbc673d36..e52564c40d 100644 --- a/osu.Game.Tests/Visual/TestCaseLoadingAnimation.cs +++ b/osu.Game.Tests/Visual/TestCaseLoadingAnimation.cs @@ -5,7 +5,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Shapes; using osu.Framework.Testing; using osu.Game.Graphics.UserInterface; -using OpenTK.Graphics; +using osuTK.Graphics; namespace osu.Game.Tests.Visual { diff --git a/osu.Game.Tests/Visual/TestCaseLounge.cs b/osu.Game.Tests/Visual/TestCaseLounge.cs index 174873b011..e0e6332ef0 100644 --- a/osu.Game.Tests/Visual/TestCaseLounge.cs +++ b/osu.Game.Tests/Visual/TestCaseLounge.cs @@ -13,7 +13,7 @@ using osu.Game.Screens.Backgrounds; using osu.Game.Screens.Multi.Components; using osu.Game.Screens.Multi.Screens.Lounge; using osu.Game.Users; -using OpenTK.Input; +using osuTK.Input; namespace osu.Game.Tests.Visual { diff --git a/osu.Game.Tests/Visual/TestCaseMods.cs b/osu.Game.Tests/Visual/TestCaseMods.cs index ab53dbd968..029f7da5a9 100644 --- a/osu.Game.Tests/Visual/TestCaseMods.cs +++ b/osu.Game.Tests/Visual/TestCaseMods.cs @@ -7,7 +7,7 @@ using osu.Framework.Graphics; using osu.Game.Overlays.Mods; using osu.Game.Rulesets; using osu.Game.Screens.Play.HUD; -using OpenTK; +using osuTK; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Mods; using System.Linq; @@ -19,7 +19,7 @@ using osu.Game.Graphics.Sprites; using osu.Game.Overlays.Mods.Sections; using osu.Game.Rulesets.Mania.Mods; using osu.Game.Rulesets.UI; -using OpenTK.Graphics; +using osuTK.Graphics; namespace osu.Game.Tests.Visual { diff --git a/osu.Game.Tests/Visual/TestCaseOsuGame.cs b/osu.Game.Tests/Visual/TestCaseOsuGame.cs index 7a4e4c1210..924a2b5e15 100644 --- a/osu.Game.Tests/Visual/TestCaseOsuGame.cs +++ b/osu.Game.Tests/Visual/TestCaseOsuGame.cs @@ -8,7 +8,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Shapes; using osu.Game.Screens; using osu.Game.Screens.Menu; -using OpenTK.Graphics; +using osuTK.Graphics; namespace osu.Game.Tests.Visual { diff --git a/osu.Game.Tests/Visual/TestCasePlaySongSelect.cs b/osu.Game.Tests/Visual/TestCasePlaySongSelect.cs index 888bf6250f..d87a8d0056 100644 --- a/osu.Game.Tests/Visual/TestCasePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/TestCasePlaySongSelect.cs @@ -98,8 +98,11 @@ namespace osu.Game.Tests.Visual [SetUp] public virtual void SetUp() { - manager?.Delete(manager.GetAllUsableBeatmapSets()); - Child = songSelect = new TestSongSelect(); + Schedule(() => + { + manager?.Delete(manager.GetAllUsableBeatmapSets()); + Child = songSelect = new TestSongSelect(); + }); } [Test] @@ -187,7 +190,7 @@ namespace osu.Game.Tests.Visual private static int importId; private int getImportId() => ++importId; - private void changeMods(params Mod[] mods) => AddStep($"change mods to {string.Join(", ", mods.Select(m => m.ShortenedName))}", () => selectedMods.Value = mods); + private void changeMods(params Mod[] mods) => AddStep($"change mods to {string.Join(", ", mods.Select(m => m.Acronym))}", () => selectedMods.Value = mods); private void changeRuleset(int id) => AddStep($"change ruleset to {id}", () => Ruleset.Value = rulesets.AvailableRulesets.First(r => r.ID == id)); diff --git a/osu.Game.Tests/Visual/TestCasePlaybackControl.cs b/osu.Game.Tests/Visual/TestCasePlaybackControl.cs index 36fb1bcedd..4352b2bc3a 100644 --- a/osu.Game.Tests/Visual/TestCasePlaybackControl.cs +++ b/osu.Game.Tests/Visual/TestCasePlaybackControl.cs @@ -8,7 +8,7 @@ using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Screens.Edit.Components; using osu.Game.Tests.Beatmaps; -using OpenTK; +using osuTK; namespace osu.Game.Tests.Visual { diff --git a/osu.Game.Tests/Visual/TestCaseRankGraph.cs b/osu.Game.Tests/Visual/TestCaseRankGraph.cs index f5558620ad..f8eea7d63c 100644 --- a/osu.Game.Tests/Visual/TestCaseRankGraph.cs +++ b/osu.Game.Tests/Visual/TestCaseRankGraph.cs @@ -3,7 +3,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using OpenTK; +using osuTK; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; using System.Collections.Generic; diff --git a/osu.Game.Tests/Visual/TestCaseReplay.cs b/osu.Game.Tests/Visual/TestCaseReplay.cs index 1f2d99a7d8..e0ea613534 100644 --- a/osu.Game.Tests/Visual/TestCaseReplay.cs +++ b/osu.Game.Tests/Visual/TestCaseReplay.cs @@ -5,6 +5,7 @@ using System.ComponentModel; using System.Linq; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; +using osu.Game.Scoring; using osu.Game.Screens.Play; namespace osu.Game.Tests.Visual @@ -19,13 +20,10 @@ namespace osu.Game.Tests.Visual Beatmap.Value.Mods.Value = Beatmap.Value.Mods.Value.Concat(new[] { ruleset.GetAutoplayMod() }); var dummyRulesetContainer = ruleset.CreateRulesetContainerWith(Beatmap.Value); - // We have the replay - var replay = dummyRulesetContainer.Replay; - // Reset the mods Beatmap.Value.Mods.Value = Beatmap.Value.Mods.Value.Where(m => !(m is ModAutoplay)); - return new ReplayPlayer(replay); + return new ReplayPlayer(new Score { Replay = dummyRulesetContainer.Replay }); } } } diff --git a/osu.Game.Tests/Visual/TestCaseResults.cs b/osu.Game.Tests/Visual/TestCaseResults.cs index ee36fb0afc..6a20a808b6 100644 --- a/osu.Game.Tests/Visual/TestCaseResults.cs +++ b/osu.Game.Tests/Visual/TestCaseResults.cs @@ -7,6 +7,7 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Game.Beatmaps; using osu.Game.Rulesets.Scoring; +using osu.Game.Scoring; using osu.Game.Screens.Ranking; using osu.Game.Users; @@ -19,7 +20,7 @@ namespace osu.Game.Tests.Visual public override IReadOnlyList RequiredTypes => new[] { - typeof(Score), + typeof(ScoreInfo), typeof(Results), typeof(ResultsPage), typeof(ResultsPageScore), @@ -40,14 +41,14 @@ namespace osu.Game.Tests.Visual if (beatmapInfo != null) Beatmap.Value = beatmaps.GetWorkingBeatmap(beatmapInfo); - Add(new Results(new Score + Add(new Results(new ScoreInfo { TotalScore = 2845370, Accuracy = 0.98, MaxCombo = 123, Rank = ScoreRank.A, Date = DateTimeOffset.Now, - Statistics = new Dictionary + Statistics = new Dictionary { { HitResult.Great, 50 }, { HitResult.Good, 20 }, diff --git a/osu.Game.Tests/Visual/TestCaseRoomSettings.cs b/osu.Game.Tests/Visual/TestCaseRoomSettings.cs new file mode 100644 index 0000000000..40742ce709 --- /dev/null +++ b/osu.Game.Tests/Visual/TestCaseRoomSettings.cs @@ -0,0 +1,119 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using NUnit.Framework; +using osu.Framework.Graphics; +using osu.Framework.Testing.Input; +using osu.Game.Online.Multiplayer; +using osu.Game.Screens.Multi.Screens.Match.Settings; +using osuTK.Input; + +namespace osu.Game.Tests.Visual +{ + [TestFixture] + public class TestCaseRoomSettings : ManualInputManagerTestCase + { + private readonly Room room; + private readonly TestRoomSettingsOverlay overlay; + + public TestCaseRoomSettings() + { + room = new Room + { + Name = { Value = "One Testing Room" }, + Availability = { Value = RoomAvailability.Public }, + Type = { Value = new GameTypeTeamVersus() }, + MaxParticipants = { Value = 10 }, + }; + + Add(overlay = new TestRoomSettingsOverlay(room) + { + RelativeSizeAxes = Axes.Both, + Height = 0.75f, + }); + + AddStep(@"show", overlay.Show); + assertAll(); + AddStep(@"set name", () => overlay.CurrentName = @"Two Testing Room"); + AddStep(@"set max", () => overlay.CurrentMaxParticipants = null); + AddStep(@"set availability", () => overlay.CurrentAvailability = RoomAvailability.InviteOnly); + AddStep(@"set type", () => overlay.CurrentType = new GameTypeTagTeam()); + apply(); + assertAll(); + AddStep(@"show", overlay.Show); + AddStep(@"set room name", () => room.Name.Value = @"Room Changed Name!"); + AddStep(@"set room availability", () => room.Availability.Value = RoomAvailability.Public); + AddStep(@"set room type", () => room.Type.Value = new GameTypeTag()); + AddStep(@"set room max", () => room.MaxParticipants.Value = 100); + assertAll(); + AddStep(@"set name", () => overlay.CurrentName = @"Unsaved Testing Room"); + AddStep(@"set max", () => overlay.CurrentMaxParticipants = 20); + AddStep(@"set availability", () => overlay.CurrentAvailability = RoomAvailability.FriendsOnly); + AddStep(@"set type", () => overlay.CurrentType = new GameTypeVersus()); + AddStep(@"hide", overlay.Hide); + AddWaitStep(5); + AddStep(@"show", overlay.Show); + assertAll(); + AddStep(@"hide", overlay.Hide); + } + + private void apply() + { + AddStep(@"apply", () => + { + overlay.ClickApplyButton(InputManager); + }); + } + + private void assertAll() + { + AddAssert(@"name == room name", () => overlay.CurrentName == room.Name.Value); + AddAssert(@"max == room max", () => overlay.CurrentMaxParticipants == room.MaxParticipants.Value); + AddAssert(@"availability == room availability", () => overlay.CurrentAvailability == room.Availability.Value); + AddAssert(@"type == room type", () => Equals(overlay.CurrentType, room.Type.Value)); + } + + private class TestRoomSettingsOverlay : RoomSettingsOverlay + { + public string CurrentName + { + get => NameField.Text; + set => NameField.Text = value; + } + + public int? CurrentMaxParticipants + { + get + { + if (int.TryParse(MaxParticipantsField.Text, out int max)) + return max; + + return null; + } + set => MaxParticipantsField.Text = value?.ToString(); + } + + public RoomAvailability CurrentAvailability + { + get => AvailabilityPicker.Current.Value; + set => AvailabilityPicker.Current.Value = value; + } + + public GameType CurrentType + { + get => TypePicker.Current.Value; + set => TypePicker.Current.Value = value; + } + + public TestRoomSettingsOverlay(Room room) : base(room) + { + } + + public void ClickApplyButton(ManualInputManager inputManager) + { + inputManager.MoveMouseTo(ApplyButton); + inputManager.Click(MouseButton.Left); + } + } + } +} diff --git a/osu.Game.Tests/Visual/TestCaseScoreCounter.cs b/osu.Game.Tests/Visual/TestCaseScoreCounter.cs index 4dff7547d9..c32ca104ed 100644 --- a/osu.Game.Tests/Visual/TestCaseScoreCounter.cs +++ b/osu.Game.Tests/Visual/TestCaseScoreCounter.cs @@ -7,7 +7,7 @@ using osu.Framework.Graphics.Sprites; using osu.Framework.MathUtils; using osu.Game.Graphics.UserInterface; using osu.Game.Screens.Play.HUD; -using OpenTK; +using osuTK; namespace osu.Game.Tests.Visual { diff --git a/osu.Game.Tests/Visual/TestCaseScreenBreadcrumbControl.cs b/osu.Game.Tests/Visual/TestCaseScreenBreadcrumbControl.cs index b70117a3ad..82da609e49 100644 --- a/osu.Game.Tests/Visual/TestCaseScreenBreadcrumbControl.cs +++ b/osu.Game.Tests/Visual/TestCaseScreenBreadcrumbControl.cs @@ -11,7 +11,7 @@ using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Screens; -using OpenTK; +using osuTK; namespace osu.Game.Tests.Visual { diff --git a/osu.Game.Tests/Visual/TestCaseScrollingHitObjects.cs b/osu.Game.Tests/Visual/TestCaseScrollingHitObjects.cs index b254325472..3ac85cd846 100644 --- a/osu.Game.Tests/Visual/TestCaseScrollingHitObjects.cs +++ b/osu.Game.Tests/Visual/TestCaseScrollingHitObjects.cs @@ -5,10 +5,11 @@ using System; using System.Collections.Generic; using NUnit.Framework; using osu.Framework.Extensions.IEnumerableExtensions; -using OpenTK; +using osuTK; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Game.Configuration; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Timing; @@ -22,6 +23,7 @@ namespace osu.Game.Tests.Visual { public override IReadOnlyList RequiredTypes => new[] { typeof(Playfield) }; + private readonly ScrollingTestContainer[] scrollContainers = new ScrollingTestContainer[4]; private readonly TestPlayfield[] playfields = new TestPlayfield[4]; public TestCaseScrollingHitObjects() @@ -33,18 +35,38 @@ namespace osu.Game.Tests.Visual { new Drawable[] { - playfields[0] = new TestPlayfield(ScrollingDirection.Up), - playfields[1] = new TestPlayfield(ScrollingDirection.Down) + scrollContainers[0] = new ScrollingTestContainer(ScrollingDirection.Up) + { + RelativeSizeAxes = Axes.Both, + Child = playfields[0] = new TestPlayfield() + }, + scrollContainers[1] = new ScrollingTestContainer(ScrollingDirection.Up) + { + RelativeSizeAxes = Axes.Both, + Child = playfields[1] = new TestPlayfield() + }, }, new Drawable[] { - playfields[2] = new TestPlayfield(ScrollingDirection.Left), - playfields[3] = new TestPlayfield(ScrollingDirection.Right) + scrollContainers[2] = new ScrollingTestContainer(ScrollingDirection.Up) + { + RelativeSizeAxes = Axes.Both, + Child = playfields[2] = new TestPlayfield() + }, + scrollContainers[3] = new ScrollingTestContainer(ScrollingDirection.Up) + { + RelativeSizeAxes = Axes.Both, + Child = playfields[3] = new TestPlayfield() + } } } }); - AddSliderStep("Time range", 100, 10000, 5000, v => playfields.ForEach(p => p.VisibleTimeRange.Value = v)); + AddStep("Constant scroll", () => setScrollAlgorithm(ScrollVisualisationMethod.Constant)); + AddStep("Overlapping scroll", () => setScrollAlgorithm(ScrollVisualisationMethod.Overlapping)); + AddStep("Sequential scroll", () => setScrollAlgorithm(ScrollVisualisationMethod.Sequential)); + + AddSliderStep("Time range", 100, 10000, 5000, v => scrollContainers.ForEach(c => c.TimeRange = v)); AddStep("Add control point", () => addControlPoint(Time.Current + 5000)); } @@ -52,7 +74,7 @@ namespace osu.Game.Tests.Visual { base.LoadComplete(); - playfields.ForEach(p => p.HitObjects.AddControlPoint(new MultiplierControlPoint(0))); + scrollContainers.ForEach(c => c.ControlPoints.Add(new MultiplierControlPoint(0))); for (int i = 0; i <= 5000; i += 1000) addHitObject(Time.Current + i); @@ -73,12 +95,15 @@ namespace osu.Game.Tests.Visual private void addControlPoint(double time) { + scrollContainers.ForEach(c => + { + c.ControlPoints.Add(new MultiplierControlPoint(time) { DifficultyPoint = { SpeedMultiplier = 3 } }); + c.ControlPoints.Add(new MultiplierControlPoint(time + 2000) { DifficultyPoint = { SpeedMultiplier = 2 } }); + c.ControlPoints.Add(new MultiplierControlPoint(time + 3000) { DifficultyPoint = { SpeedMultiplier = 1 } }); + }); + playfields.ForEach(p => { - p.HitObjects.AddControlPoint(new MultiplierControlPoint(time) { DifficultyPoint = { SpeedMultiplier = 3 } }); - p.HitObjects.AddControlPoint(new MultiplierControlPoint(time + 2000) { DifficultyPoint = { SpeedMultiplier = 2 } }); - p.HitObjects.AddControlPoint(new MultiplierControlPoint(time + 3000) { DifficultyPoint = { SpeedMultiplier = 1 } }); - TestDrawableControlPoint createDrawablePoint(double t) { var obj = new TestDrawableControlPoint(p.Direction, t); @@ -111,15 +136,14 @@ namespace osu.Game.Tests.Visual } } + private void setScrollAlgorithm(ScrollVisualisationMethod algorithm) => scrollContainers.ForEach(c => c.ScrollAlgorithm = algorithm); private class TestPlayfield : ScrollingPlayfield { - public new readonly ScrollingDirection Direction; + public new ScrollingDirection Direction => base.Direction.Value; - public TestPlayfield(ScrollingDirection direction) + public TestPlayfield() { - Direction = direction; - Padding = new MarginPadding(2); InternalChildren = new Drawable[] diff --git a/osu.Game.Tests/Visual/TestCaseStoryboard.cs b/osu.Game.Tests/Visual/TestCaseStoryboard.cs index b63881ffa7..42c0134afa 100644 --- a/osu.Game.Tests/Visual/TestCaseStoryboard.cs +++ b/osu.Game.Tests/Visual/TestCaseStoryboard.cs @@ -10,7 +10,7 @@ using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Overlays; using osu.Game.Storyboards.Drawables; -using OpenTK.Graphics; +using osuTK.Graphics; namespace osu.Game.Tests.Visual { diff --git a/osu.Game.Tests/Visual/TestCaseTabControl.cs b/osu.Game.Tests/Visual/TestCaseTabControl.cs index 63708c5662..00b69fd415 100644 --- a/osu.Game.Tests/Visual/TestCaseTabControl.cs +++ b/osu.Game.Tests/Visual/TestCaseTabControl.cs @@ -6,7 +6,7 @@ using osu.Framework.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Screens.Select.Filter; -using OpenTK; +using osuTK; namespace osu.Game.Tests.Visual { diff --git a/osu.Game.Tests/Visual/TestCaseTextAwesome.cs b/osu.Game.Tests/Visual/TestCaseTextAwesome.cs index 75b149af29..50ebd3a6e5 100644 --- a/osu.Game.Tests/Visual/TestCaseTextAwesome.cs +++ b/osu.Game.Tests/Visual/TestCaseTextAwesome.cs @@ -7,7 +7,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; using osu.Game.Graphics; -using OpenTK; +using osuTK; namespace osu.Game.Tests.Visual { diff --git a/osu.Game.Tests/Visual/TestCaseUserPanel.cs b/osu.Game.Tests/Visual/TestCaseUserPanel.cs index 8e2e708cc7..a53af247f3 100644 --- a/osu.Game.Tests/Visual/TestCaseUserPanel.cs +++ b/osu.Game.Tests/Visual/TestCaseUserPanel.cs @@ -5,7 +5,7 @@ using NUnit.Framework; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Users; -using OpenTK; +using osuTK; namespace osu.Game.Tests.Visual { diff --git a/osu.Game.Tests/Visual/TestCaseVolumePieces.cs b/osu.Game.Tests/Visual/TestCaseVolumePieces.cs index 3c5b91ccd2..5d7b7088e7 100644 --- a/osu.Game.Tests/Visual/TestCaseVolumePieces.cs +++ b/osu.Game.Tests/Visual/TestCaseVolumePieces.cs @@ -5,8 +5,8 @@ using System; using System.Collections.Generic; using osu.Framework.Graphics; using osu.Game.Overlays.Volume; -using OpenTK; -using OpenTK.Graphics; +using osuTK; +using osuTK.Graphics; namespace osu.Game.Tests.Visual { diff --git a/osu.Game.Tests/Visual/TestCaseWaveContainer.cs b/osu.Game.Tests/Visual/TestCaseWaveContainer.cs index 2163d7c3aa..69f1e33579 100644 --- a/osu.Game.Tests/Visual/TestCaseWaveContainer.cs +++ b/osu.Game.Tests/Visual/TestCaseWaveContainer.cs @@ -9,8 +9,8 @@ using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; -using OpenTK; -using OpenTK.Graphics; +using osuTK; +using osuTK.Graphics; namespace osu.Game.Tests.Visual { diff --git a/osu.Game.Tests/Visual/TestCaseWaveform.cs b/osu.Game.Tests/Visual/TestCaseWaveform.cs index 46d46863ad..f97091e58d 100644 --- a/osu.Game.Tests/Visual/TestCaseWaveform.cs +++ b/osu.Game.Tests/Visual/TestCaseWaveform.cs @@ -2,8 +2,8 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using NUnit.Framework; -using OpenTK; -using OpenTK.Graphics; +using osuTK; +using osuTK.Graphics; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Audio; diff --git a/osu.Game.Tests/Visual/TestCaseZoomableScrollContainer.cs b/osu.Game.Tests/Visual/TestCaseZoomableScrollContainer.cs index 8bd1b79a84..7dca99685e 100644 --- a/osu.Game.Tests/Visual/TestCaseZoomableScrollContainer.cs +++ b/osu.Game.Tests/Visual/TestCaseZoomableScrollContainer.cs @@ -10,9 +10,9 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.MathUtils; using osu.Game.Graphics; using osu.Game.Graphics.Cursor; -using osu.Game.Screens.Edit.Screens.Compose.Timeline; -using OpenTK; -using OpenTK.Graphics; +using osu.Game.Screens.Edit.Compose.Components.Timeline; +using osuTK; +using osuTK.Graphics; namespace osu.Game.Tests.Visual { diff --git a/osu.Game.Tests/osu.Game.Tests.csproj b/osu.Game.Tests/osu.Game.Tests.csproj index 520e0b8940..c0f0695ff8 100644 --- a/osu.Game.Tests/osu.Game.Tests.csproj +++ b/osu.Game.Tests/osu.Game.Tests.csproj @@ -5,7 +5,7 @@ - + diff --git a/osu.Game/Beatmaps/BeatmapDifficulty.cs b/osu.Game/Beatmaps/BeatmapDifficulty.cs index 508232dbfe..418bcb5ad1 100644 --- a/osu.Game/Beatmaps/BeatmapDifficulty.cs +++ b/osu.Game/Beatmaps/BeatmapDifficulty.cs @@ -1,20 +1,17 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using System.ComponentModel.DataAnnotations.Schema; -using Newtonsoft.Json; +using osu.Game.Database; namespace osu.Game.Beatmaps { - public class BeatmapDifficulty + public class BeatmapDifficulty : IHasPrimaryKey { /// /// The default value used for all difficulty settings except and . /// public const float DEFAULT_DIFFICULTY = 5; - [DatabaseGenerated(DatabaseGeneratedOption.Identity)] - [JsonIgnore] public int ID { get; set; } public float DrainRate { get; set; } = DEFAULT_DIFFICULTY; diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index 3e1f3bdf54..0534fd9253 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -15,8 +15,6 @@ namespace osu.Game.Beatmaps [Serializable] public class BeatmapInfo : IEquatable, IJsonSerializable, IHasPrimaryKey { - [DatabaseGenerated(DatabaseGeneratedOption.Identity)] - [JsonIgnore] public int ID { get; set; } public int BeatmapVersion; diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index fad94dcdfd..c179821a7c 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -63,6 +63,8 @@ namespace osu.Game.Beatmaps public override string[] HandledExtensions => new[] { ".osz" }; + protected override string[] HashableFileTypes => new[] { ".osu" }; + protected override string ImportFromStablePath => "Songs"; private readonly RulesetStore rulesets; @@ -129,19 +131,6 @@ namespace osu.Game.Beatmaps beatmaps.ForEach(b => b.OnlineBeatmapID = null); } - protected override BeatmapSetInfo CheckForExisting(BeatmapSetInfo model) - { - // check if this beatmap has already been imported and exit early if so - var existingHashMatch = beatmaps.ConsumableItems.FirstOrDefault(b => b.Hash == model.Hash); - if (existingHashMatch != null) - { - Undelete(existingHashMatch); - return existingHashMatch; - } - - return null; - } - /// /// Downloads a beatmap. /// This will post notifications tracking progress. @@ -260,10 +249,13 @@ namespace osu.Game.Beatmaps /// Retrieve a instance for the provided /// /// The beatmap to lookup. - /// The currently loaded . Allows for optimisation where elements are shared with the new beatmap. + /// The currently loaded . Allows for optimisation where elements are shared with the new beatmap. May be returned if beatmapInfo requested matches /// A instance correlating to the provided . public WorkingBeatmap GetWorkingBeatmap(BeatmapInfo beatmapInfo, WorkingBeatmap previous = null) { + if (beatmapInfo?.ID > 0 && previous != null && previous.BeatmapInfo?.ID == beatmapInfo.ID) + return previous; + if (beatmapInfo?.BeatmapSet == null || beatmapInfo == DefaultBeatmap?.BeatmapInfo) return DefaultBeatmap; @@ -317,20 +309,6 @@ namespace osu.Game.Beatmaps /// Results from the provided query. public IQueryable QueryBeatmaps(Expression> query) => beatmaps.Beatmaps.AsNoTracking().Where(query); - /// - /// Create a SHA-2 hash from the provided archive based on contained beatmap (.osu) file content. - /// - private string computeBeatmapSetHash(ArchiveReader reader) - { - // for now, concatenate all .osu files in the set to create a unique hash. - MemoryStream hashable = new MemoryStream(); - foreach (string file in reader.Filenames.Where(f => f.EndsWith(".osu"))) - using (Stream s = reader.GetStream(file)) - s.CopyTo(hashable); - - return hashable.ComputeSHA2Hash(); - } - protected override BeatmapSetInfo CreateModel(ArchiveReader reader) { // let's make sure there are actually .osu files to import. @@ -349,8 +327,7 @@ namespace osu.Game.Beatmaps { OnlineBeatmapSetID = beatmap.BeatmapInfo.BeatmapSet?.OnlineBeatmapSetID, Beatmaps = new List(), - Hash = computeBeatmapSetHash(reader), - Metadata = beatmap.Metadata + Metadata = beatmap.Metadata, }; } diff --git a/osu.Game/Beatmaps/BeatmapMetadata.cs b/osu.Game/Beatmaps/BeatmapMetadata.cs index 57983ec568..7e4dc4d6f5 100644 --- a/osu.Game/Beatmaps/BeatmapMetadata.cs +++ b/osu.Game/Beatmaps/BeatmapMetadata.cs @@ -6,14 +6,14 @@ using System.Collections.Generic; using System.ComponentModel.DataAnnotations.Schema; using System.Linq; using Newtonsoft.Json; +using osu.Game.Database; using osu.Game.Users; namespace osu.Game.Beatmaps { [Serializable] - public class BeatmapMetadata : IEquatable + public class BeatmapMetadata : IEquatable, IHasPrimaryKey { - [DatabaseGenerated(DatabaseGeneratedOption.Identity)] public int ID { get; set; } public string Title { get; set; } diff --git a/osu.Game/Beatmaps/BeatmapSetFileInfo.cs b/osu.Game/Beatmaps/BeatmapSetFileInfo.cs index 4fa286cbec..885fcc54f6 100644 --- a/osu.Game/Beatmaps/BeatmapSetFileInfo.cs +++ b/osu.Game/Beatmaps/BeatmapSetFileInfo.cs @@ -2,15 +2,13 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System.ComponentModel.DataAnnotations; -using System.ComponentModel.DataAnnotations.Schema; using osu.Game.Database; using osu.Game.IO; namespace osu.Game.Beatmaps { - public class BeatmapSetFileInfo : INamedFileInfo + public class BeatmapSetFileInfo : INamedFileInfo, IHasPrimaryKey { - [DatabaseGenerated(DatabaseGeneratedOption.Identity)] public int ID { get; set; } public int BeatmapSetInfoID { get; set; } diff --git a/osu.Game/Beatmaps/BeatmapSetInfo.cs b/osu.Game/Beatmaps/BeatmapSetInfo.cs index 7a7d010a31..8c541e9344 100644 --- a/osu.Game/Beatmaps/BeatmapSetInfo.cs +++ b/osu.Game/Beatmaps/BeatmapSetInfo.cs @@ -10,7 +10,6 @@ namespace osu.Game.Beatmaps { public class BeatmapSetInfo : IHasPrimaryKey, IHasFiles, ISoftDelete { - [DatabaseGenerated(DatabaseGeneratedOption.Identity)] public int ID { get; set; } private int? onlineBeatmapSetID; diff --git a/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs index 526bddf51a..dc981cd934 100644 --- a/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs @@ -1,7 +1,7 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using OpenTK; +using osuTK; namespace osu.Game.Beatmaps.ControlPoints { diff --git a/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs index 81eddaa43a..ecd7ff252c 100644 --- a/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs @@ -1,7 +1,7 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using OpenTK; +using osuTK; using osu.Game.Beatmaps.Timing; namespace osu.Game.Beatmaps.ControlPoints diff --git a/osu.Game/Beatmaps/Drawables/BeatmapSetDownloader.cs b/osu.Game/Beatmaps/Drawables/BeatmapSetDownloader.cs index 5b5dbec9c8..baeeaf81a4 100644 --- a/osu.Game/Beatmaps/Drawables/BeatmapSetDownloader.cs +++ b/osu.Game/Beatmaps/Drawables/BeatmapSetDownloader.cs @@ -78,17 +78,17 @@ namespace osu.Game.Beatmaps.Drawables } } - private void setAdded(BeatmapSetInfo s) + private void setAdded(BeatmapSetInfo s, bool existing, bool silent) => Schedule(() => { if (s.OnlineBeatmapSetID == set.OnlineBeatmapSetID) DownloadState.Value = DownloadStatus.Downloaded; - } + }); - private void setRemoved(BeatmapSetInfo s) + private void setRemoved(BeatmapSetInfo s) => Schedule(() => { if (s.OnlineBeatmapSetID == set.OnlineBeatmapSetID) DownloadState.Value = DownloadStatus.NotDownloaded; - } + }); private void downloadBegan(DownloadBeatmapSetRequest d) { diff --git a/osu.Game/Beatmaps/Drawables/BeatmapSetOnlineStatusPill.cs b/osu.Game/Beatmaps/Drawables/BeatmapSetOnlineStatusPill.cs index da281b4db3..a7dff1aefc 100644 --- a/osu.Game/Beatmaps/Drawables/BeatmapSetOnlineStatusPill.cs +++ b/osu.Game/Beatmaps/Drawables/BeatmapSetOnlineStatusPill.cs @@ -5,7 +5,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics.Sprites; -using OpenTK.Graphics; +using osuTK.Graphics; namespace osu.Game.Beatmaps.Drawables { diff --git a/osu.Game/Beatmaps/Drawables/DifficultyColouredContainer.cs b/osu.Game/Beatmaps/Drawables/DifficultyColouredContainer.cs index 7d00c35862..0d421ff690 100644 --- a/osu.Game/Beatmaps/Drawables/DifficultyColouredContainer.cs +++ b/osu.Game/Beatmaps/Drawables/DifficultyColouredContainer.cs @@ -5,7 +5,7 @@ using System; using osu.Framework.Allocation; using osu.Framework.Graphics.Containers; using osu.Game.Graphics; -using OpenTK.Graphics; +using osuTK.Graphics; namespace osu.Game.Beatmaps.Drawables { diff --git a/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs b/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs index 24604711d4..14162c35c0 100644 --- a/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs +++ b/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs @@ -9,8 +9,8 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; using osu.Game.Graphics.Containers; -using OpenTK; -using OpenTK.Graphics; +using osuTK; +using osuTK.Graphics; namespace osu.Game.Beatmaps.Drawables { diff --git a/osu.Game/Beatmaps/Drawables/UpdateableBeatmapSetCover.cs b/osu.Game/Beatmaps/Drawables/UpdateableBeatmapSetCover.cs index 1976db907c..9a1617b550 100644 --- a/osu.Game/Beatmaps/Drawables/UpdateableBeatmapSetCover.cs +++ b/osu.Game/Beatmaps/Drawables/UpdateableBeatmapSetCover.cs @@ -4,7 +4,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using OpenTK.Graphics; +using osuTK.Graphics; namespace osu.Game.Beatmaps.Drawables { diff --git a/osu.Game/Beatmaps/DummyWorkingBeatmap.cs b/osu.Game/Beatmaps/DummyWorkingBeatmap.cs index 25a76b52a7..5c129f76ec 100644 --- a/osu.Game/Beatmaps/DummyWorkingBeatmap.cs +++ b/osu.Game/Beatmaps/DummyWorkingBeatmap.cs @@ -26,15 +26,7 @@ namespace osu.Game.Beatmaps Title = "no beatmaps available!" }, BeatmapSet = new BeatmapSetInfo(), - BaseDifficulty = new BeatmapDifficulty - { - DrainRate = 0, - CircleSize = 0, - OverallDifficulty = 0, - ApproachRate = 0, - SliderMultiplier = 0, - SliderTickRate = 0, - }, + BaseDifficulty = new BeatmapDifficulty(), Ruleset = new DummyRulesetInfo() }) { diff --git a/osu.Game/Beatmaps/Formats/IHasComboColours.cs b/osu.Game/Beatmaps/Formats/IHasComboColours.cs index d3377c2717..d47377dc8d 100644 --- a/osu.Game/Beatmaps/Formats/IHasComboColours.cs +++ b/osu.Game/Beatmaps/Formats/IHasComboColours.cs @@ -2,7 +2,7 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System.Collections.Generic; -using OpenTK.Graphics; +using osuTK.Graphics; namespace osu.Game.Beatmaps.Formats { diff --git a/osu.Game/Beatmaps/Formats/IHasCustomColours.cs b/osu.Game/Beatmaps/Formats/IHasCustomColours.cs index 32390ac484..a319d0e48e 100644 --- a/osu.Game/Beatmaps/Formats/IHasCustomColours.cs +++ b/osu.Game/Beatmaps/Formats/IHasCustomColours.cs @@ -2,7 +2,7 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System.Collections.Generic; -using OpenTK.Graphics; +using osuTK.Graphics; namespace osu.Game.Beatmaps.Formats { diff --git a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs index 2ef7c5de30..217f16b263 100644 --- a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs @@ -7,7 +7,7 @@ using System.IO; using osu.Framework.Logging; using osu.Game.Audio; using osu.Game.Beatmaps.ControlPoints; -using OpenTK.Graphics; +using osuTK.Graphics; namespace osu.Game.Beatmaps.Formats { diff --git a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs index 375c0b29c0..d30c78cb17 100644 --- a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs @@ -5,8 +5,8 @@ using System; using System.Collections.Generic; using System.Globalization; using System.IO; -using OpenTK; -using OpenTK.Graphics; +using osuTK; +using osuTK.Graphics; using osu.Framework.Graphics; using osu.Framework.IO.File; using osu.Game.Storyboards; diff --git a/osu.Game/Configuration/DatabasedSetting.cs b/osu.Game/Configuration/DatabasedSetting.cs index 62990991b0..6c2822734b 100644 --- a/osu.Game/Configuration/DatabasedSetting.cs +++ b/osu.Game/Configuration/DatabasedSetting.cs @@ -9,7 +9,6 @@ namespace osu.Game.Configuration [Table("Settings")] public class DatabasedSetting : IHasPrimaryKey { - [DatabaseGenerated(DatabaseGeneratedOption.Identity)] public int ID { get; set; } public int? RulesetID { get; set; } diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index 9ac2cabe9f..8975ab8a0e 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -42,6 +42,8 @@ namespace osu.Game.Configuration if (!val) Set(OsuSetting.SavePassword, false); }; + Set(OsuSetting.ExternalLinkWarning, true); + // Audio Set(OsuSetting.VolumeInactive, 0.25, 0, 1, 0.01); @@ -148,6 +150,7 @@ namespace osu.Game.Configuration BeatmapSkins, BeatmapHitsounds, IncreaseFirstObjectVisibility, - ScoreDisplayMode + ScoreDisplayMode, + ExternalLinkWarning } } diff --git a/osu.Game/Configuration/SpeedChangeVisualisationMethod.cs b/osu.Game/Configuration/ScrollVisualisationMethod.cs similarity index 89% rename from osu.Game/Configuration/SpeedChangeVisualisationMethod.cs rename to osu.Game/Configuration/ScrollVisualisationMethod.cs index 39c6e5649c..cc7dcdbc0e 100644 --- a/osu.Game/Configuration/SpeedChangeVisualisationMethod.cs +++ b/osu.Game/Configuration/ScrollVisualisationMethod.cs @@ -5,7 +5,7 @@ using System.ComponentModel; namespace osu.Game.Configuration { - public enum SpeedChangeVisualisationMethod + public enum ScrollVisualisationMethod { [Description("Sequential")] Sequential, diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index 3686b702c4..50767608af 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -8,6 +8,7 @@ using System.Linq; using System.Threading.Tasks; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore; +using osu.Framework.Extensions; using osu.Framework.IO.File; using osu.Framework.Logging; using osu.Framework.Platform; @@ -31,6 +32,8 @@ namespace osu.Game.Database where TModel : class, IHasFiles, IHasPrimaryKey, ISoftDelete where TFileModel : INamedFileInfo, new() { + public delegate void ItemAddedDelegate(TModel model, bool existing, bool silent); + /// /// Set an endpoint for notifications to be posted to. /// @@ -40,7 +43,7 @@ namespace osu.Game.Database /// Fired when a new becomes available in the database. /// This is not guaranteed to run on the update thread. /// - public event Action ItemAdded; + public event ItemAddedDelegate ItemAdded; /// /// Fired when a is removed from the database. @@ -107,7 +110,7 @@ namespace osu.Game.Database ContextFactory = contextFactory; ModelStore = modelStore; - ModelStore.ItemAdded += s => handleEvent(() => ItemAdded?.Invoke(s)); + ModelStore.ItemAdded += (item, silent) => handleEvent(() => ItemAdded?.Invoke(item, false, silent)); ModelStore.ItemRemoved += s => handleEvent(() => ItemRemoved?.Invoke(s)); Files = new FileStore(contextFactory, storage); @@ -203,7 +206,12 @@ namespace osu.Game.Database try { var model = CreateModel(archive); - return model == null ? null : Import(model, archive); + + if (model == null) return null; + + model.Hash = computeHash(archive); + + return Import(model, false, archive); } catch (Exception e) { @@ -212,12 +220,34 @@ namespace osu.Game.Database } } + /// + /// Any file extensions which should be included in hash creation. + /// Generally should include all file types which determine the file's uniqueness. + /// Large files should be avoided if possible. + /// + protected abstract string[] HashableFileTypes { get; } + + /// + /// Create a SHA-2 hash from the provided archive based on file content of all files matching . + /// + private string computeHash(ArchiveReader reader) + { + // for now, concatenate all .osu files in the set to create a unique hash. + MemoryStream hashable = new MemoryStream(); + foreach (string file in reader.Filenames.Where(f => HashableFileTypes.Any(f.EndsWith))) + using (Stream s = reader.GetStream(file)) + s.CopyTo(hashable); + + return hashable.ComputeSHA2Hash(); + } + /// /// Import an item from a . /// /// The model to be imported. + /// Whether the user should be notified fo the import. /// An optional archive to use for model population. - public TModel Import(TModel item, ArchiveReader archive = null) + public TModel Import(TModel item, bool silent = false, ArchiveReader archive = null) { delayEvents(); @@ -235,7 +265,9 @@ namespace osu.Game.Database if (existing != null) { + Undelete(existing); Logger.Log($"Found existing {typeof(TModel)} for {item} (ID {existing.ID}). Skipping import.", LoggingTarget.Database); + handleEvent(() => ItemAdded?.Invoke(existing, true, silent)); return existing; } @@ -245,7 +277,7 @@ namespace osu.Game.Database Populate(item, archive); // import to store - ModelStore.Add(item); + ModelStore.Add(item, silent); } catch (Exception e) { @@ -471,7 +503,12 @@ namespace osu.Game.Database { } - protected virtual TModel CheckForExisting(TModel model) => null; + /// + /// Check whether an existing model already exists for a new import item. + /// + /// The new model proposed for import. Note that has not yet been run on this model. + /// An existing model which matches the criteria to skip importing, else null. + protected virtual TModel CheckForExisting(TModel model) => model.Hash == null ? null : ModelStore.ConsumableItems.FirstOrDefault(b => b.Hash == model.Hash); private DbSet queryModel() => ContextFactory.Get().Set(); @@ -485,7 +522,9 @@ namespace osu.Game.Database if (ZipUtils.IsZipArchive(path)) return new ZipArchiveReader(File.Open(path, FileMode.Open, FileAccess.Read, FileShare.Read), Path.GetFileName(path)); if (Directory.Exists(path)) - return new LegacyFilesystemReader(path); + return new LegacyDirectoryArchiveReader(path); + if (File.Exists(path)) + return new LegacyFileArchiveReader(path); throw new InvalidFormatException($"{path} is not a valid archive"); } } diff --git a/osu.Game/Database/IHasFiles.cs b/osu.Game/Database/IHasFiles.cs index 3aaba37efc..b5ac22efc9 100644 --- a/osu.Game/Database/IHasFiles.cs +++ b/osu.Game/Database/IHasFiles.cs @@ -11,8 +11,9 @@ namespace osu.Game.Database /// The model representing a file. public interface IHasFiles where TFile : INamedFileInfo - { List Files { get; set; } + + string Hash { get; set; } } } diff --git a/osu.Game/Database/IHasPrimaryKey.cs b/osu.Game/Database/IHasPrimaryKey.cs index 2ee356baea..c62e78ea23 100644 --- a/osu.Game/Database/IHasPrimaryKey.cs +++ b/osu.Game/Database/IHasPrimaryKey.cs @@ -1,10 +1,15 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using System.ComponentModel.DataAnnotations.Schema; +using Newtonsoft.Json; + namespace osu.Game.Database { public interface IHasPrimaryKey { + [JsonIgnore] + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] int ID { get; set; } } } diff --git a/osu.Game/Database/INamedFileInfo.cs b/osu.Game/Database/INamedFileInfo.cs index 751024d184..04dcdad285 100644 --- a/osu.Game/Database/INamedFileInfo.cs +++ b/osu.Game/Database/INamedFileInfo.cs @@ -10,7 +10,11 @@ namespace osu.Game.Database /// public interface INamedFileInfo { + // An explicit foreign key property isn't required but is recommended and may be helpful to have + int FileInfoID { get; set; } + FileInfo FileInfo { get; set; } + string Filename { get; set; } } } diff --git a/osu.Game/Database/MutableDatabaseBackedStore.cs b/osu.Game/Database/MutableDatabaseBackedStore.cs index 69a1f57cc4..dc2fe54aac 100644 --- a/osu.Game/Database/MutableDatabaseBackedStore.cs +++ b/osu.Game/Database/MutableDatabaseBackedStore.cs @@ -16,7 +16,9 @@ namespace osu.Game.Database public abstract class MutableDatabaseBackedStore : DatabaseBackedStore where T : class, IHasPrimaryKey, ISoftDelete { - public event Action ItemAdded; + public delegate void ItemAddedDelegate(T model, bool silent); + + public event ItemAddedDelegate ItemAdded; public event Action ItemRemoved; protected MutableDatabaseBackedStore(IDatabaseContextFactory contextFactory, Storage storage = null) @@ -33,7 +35,8 @@ namespace osu.Game.Database /// Add a to the database. /// /// The item to add. - public void Add(T item) + /// Whether the user should be notified of the addition. + public void Add(T item, bool silent) { using (var usage = ContextFactory.GetForWrite()) { @@ -41,7 +44,7 @@ namespace osu.Game.Database context.Attach(item); } - ItemAdded?.Invoke(item); + ItemAdded?.Invoke(item, silent); } /// @@ -54,7 +57,7 @@ namespace osu.Game.Database usage.Context.Update(item); ItemRemoved?.Invoke(item); - ItemAdded?.Invoke(item); + ItemAdded?.Invoke(item, true); } /// @@ -89,7 +92,7 @@ namespace osu.Game.Database item.DeletePending = false; } - ItemAdded?.Invoke(item); + ItemAdded?.Invoke(item, true); return true; } diff --git a/osu.Game/Database/OsuDbContext.cs b/osu.Game/Database/OsuDbContext.cs index 20e144c033..db5a2771d4 100644 --- a/osu.Game/Database/OsuDbContext.cs +++ b/osu.Game/Database/OsuDbContext.cs @@ -10,6 +10,7 @@ using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.IO; using osu.Game.Rulesets; +using osu.Game.Scoring; using DatabasedKeyBinding = osu.Game.Input.Bindings.DatabasedKeyBinding; using LogLevel = Microsoft.Extensions.Logging.LogLevel; using osu.Game.Skinning; @@ -27,6 +28,7 @@ namespace osu.Game.Database public DbSet FileInfo { get; set; } public DbSet RulesetInfo { get; set; } public DbSet SkinInfo { get; set; } + public DbSet ScoreInfo { get; set; } private readonly string connectionString; @@ -105,6 +107,9 @@ namespace osu.Game.Database modelBuilder.Entity().HasIndex(b => b.DeletePending); modelBuilder.Entity().HasIndex(b => b.Hash).IsUnique(); + modelBuilder.Entity().HasIndex(b => b.Hash).IsUnique(); + modelBuilder.Entity().HasIndex(b => b.DeletePending); + modelBuilder.Entity().HasIndex(b => new { b.RulesetID, b.Variant }); modelBuilder.Entity().HasIndex(b => b.IntAction); @@ -117,6 +122,8 @@ namespace osu.Game.Database modelBuilder.Entity().HasIndex(b => b.ShortName).IsUnique(); modelBuilder.Entity().HasOne(b => b.BaseDifficulty); + + modelBuilder.Entity().HasIndex(b => b.OnlineScoreID).IsUnique(); } private class OsuDbLoggerFactory : ILoggerFactory diff --git a/osu.Game/Graphics/Backgrounds/Triangles.cs b/osu.Game/Graphics/Backgrounds/Triangles.cs index 4a86d0e4f6..eb8dc7243e 100644 --- a/osu.Game/Graphics/Backgrounds/Triangles.cs +++ b/osu.Game/Graphics/Backgrounds/Triangles.cs @@ -3,12 +3,12 @@ using osu.Framework.Graphics; using osu.Framework.MathUtils; -using OpenTK; -using OpenTK.Graphics; +using osuTK; +using osuTK.Graphics; using System; using osu.Framework.Graphics.Shaders; using osu.Framework.Graphics.Textures; -using OpenTK.Graphics.ES30; +using osuTK.Graphics.ES30; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Primitives; using osu.Framework.Allocation; diff --git a/osu.Game/Graphics/Containers/ConstrainedIconContainer.cs b/osu.Game/Graphics/Containers/ConstrainedIconContainer.cs index 7289bfe52c..aae05ca767 100644 --- a/osu.Game/Graphics/Containers/ConstrainedIconContainer.cs +++ b/osu.Game/Graphics/Containers/ConstrainedIconContainer.cs @@ -4,7 +4,7 @@ using System; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using OpenTK; +using osuTK; namespace osu.Game.Graphics.Containers { diff --git a/osu.Game/Graphics/Containers/LinkFlowContainer.cs b/osu.Game/Graphics/Containers/LinkFlowContainer.cs index e4e7828d0e..54a2ea47f9 100644 --- a/osu.Game/Graphics/Containers/LinkFlowContainer.cs +++ b/osu.Game/Graphics/Containers/LinkFlowContainer.cs @@ -7,7 +7,7 @@ using System.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics.Sprites; using System.Collections.Generic; -using osu.Framework.Platform; +using osu.Framework.Logging; using osu.Game.Overlays; using osu.Game.Overlays.Notifications; @@ -21,16 +21,15 @@ namespace osu.Game.Graphics.Containers } private OsuGame game; - + private ChannelManager channelManager; private Action showNotImplementedError; - private GameHost host; [BackgroundDependencyLoader(true)] - private void load(OsuGame game, NotificationOverlay notifications, GameHost host) + private void load(OsuGame game, NotificationOverlay notifications, ChannelManager channelManager) { // will be null in tests this.game = game; - this.host = host; + this.channelManager = channelManager; showNotImplementedError = () => notifications?.Post(new SimpleNotification { @@ -80,7 +79,15 @@ namespace osu.Game.Graphics.Containers game?.ShowBeatmapSet(setId); break; case LinkAction.OpenChannel: - game?.OpenChannel(linkArgument); + try + { + channelManager?.OpenChannel(linkArgument); + } + catch (ChannelNotFoundException e) + { + Logger.Log($"The requested channel \"{linkArgument}\" does not exist"); + } + break; case LinkAction.OpenEditorTimestamp: case LinkAction.JoinMultiplayerMatch: @@ -88,7 +95,7 @@ namespace osu.Game.Graphics.Containers showNotImplementedError?.Invoke(); break; case LinkAction.External: - host.OpenUrlExternally(url); + game?.OpenUrlExternally(url); break; case LinkAction.OpenUserProfile: if (long.TryParse(linkArgument, out long userId)) diff --git a/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs b/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs index 2aa3fede2c..a24b6594e0 100644 --- a/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs +++ b/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs @@ -5,7 +5,7 @@ using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; using osu.Framework.Graphics.Containers; -using OpenTK; +using osuTK; using osu.Framework.Configuration; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; diff --git a/osu.Game/Graphics/Containers/OsuHoverContainer.cs b/osu.Game/Graphics/Containers/OsuHoverContainer.cs index 577d889be3..af804735a8 100644 --- a/osu.Game/Graphics/Containers/OsuHoverContainer.cs +++ b/osu.Game/Graphics/Containers/OsuHoverContainer.cs @@ -2,7 +2,7 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System.Collections.Generic; -using OpenTK.Graphics; +using osuTK.Graphics; using osu.Framework.Allocation; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; diff --git a/osu.Game/Graphics/Containers/OsuScrollContainer.cs b/osu.Game/Graphics/Containers/OsuScrollContainer.cs index 4f18eb9e7b..8aeade9265 100644 --- a/osu.Game/Graphics/Containers/OsuScrollContainer.cs +++ b/osu.Game/Graphics/Containers/OsuScrollContainer.cs @@ -3,7 +3,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Input.Events; -using OpenTK.Input; +using osuTK.Input; namespace osu.Game.Graphics.Containers { diff --git a/osu.Game/Graphics/Containers/ParallaxContainer.cs b/osu.Game/Graphics/Containers/ParallaxContainer.cs index a6b79a20dc..97e12ec0f9 100644 --- a/osu.Game/Graphics/Containers/ParallaxContainer.cs +++ b/osu.Game/Graphics/Containers/ParallaxContainer.cs @@ -4,7 +4,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics; using osu.Framework.Input; -using OpenTK; +using osuTK; using osu.Framework.Allocation; using osu.Game.Configuration; using osu.Framework.Configuration; diff --git a/osu.Game/Graphics/Containers/WaveContainer.cs b/osu.Game/Graphics/Containers/WaveContainer.cs index 82432c6ee7..96a2304ed1 100644 --- a/osu.Game/Graphics/Containers/WaveContainer.cs +++ b/osu.Game/Graphics/Containers/WaveContainer.cs @@ -6,7 +6,7 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using OpenTK.Graphics; +using osuTK.Graphics; namespace osu.Game.Graphics.Containers { diff --git a/osu.Game/Graphics/Cursor/MenuCursor.cs b/osu.Game/Graphics/Cursor/MenuCursor.cs index ba858bf52d..87d97806cd 100644 --- a/osu.Game/Graphics/Cursor/MenuCursor.cs +++ b/osu.Game/Graphics/Cursor/MenuCursor.cs @@ -1,7 +1,7 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using OpenTK; +using osuTK; using osu.Framework.Allocation; using osu.Framework.Configuration; using osu.Framework.Graphics; @@ -13,7 +13,7 @@ using System; using JetBrains.Annotations; using osu.Framework.Graphics.Textures; using osu.Framework.Input.Events; -using OpenTK.Input; +using osuTK.Input; namespace osu.Game.Graphics.Cursor { diff --git a/osu.Game/Graphics/Cursor/OsuTooltipContainer.cs b/osu.Game/Graphics/Cursor/OsuTooltipContainer.cs index 44156f6e83..e7a9aab622 100644 --- a/osu.Game/Graphics/Cursor/OsuTooltipContainer.cs +++ b/osu.Game/Graphics/Cursor/OsuTooltipContainer.cs @@ -1,8 +1,8 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using OpenTK; -using OpenTK.Graphics; +using osuTK; +using osuTK.Graphics; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; diff --git a/osu.Game/Graphics/IHasAccentColour.cs b/osu.Game/Graphics/IHasAccentColour.cs index 64c240aa84..0718e84a34 100644 --- a/osu.Game/Graphics/IHasAccentColour.cs +++ b/osu.Game/Graphics/IHasAccentColour.cs @@ -1,7 +1,7 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using OpenTK.Graphics; +using osuTK.Graphics; using osu.Framework.Graphics; using osu.Framework.Graphics.Transforms; diff --git a/osu.Game/Graphics/OsuColour.cs b/osu.Game/Graphics/OsuColour.cs index 7236248f18..fc627fa501 100644 --- a/osu.Game/Graphics/OsuColour.cs +++ b/osu.Game/Graphics/OsuColour.cs @@ -2,7 +2,7 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System; -using OpenTK.Graphics; +using osuTK.Graphics; namespace osu.Game.Graphics { diff --git a/osu.Game/Graphics/SpriteIcon.cs b/osu.Game/Graphics/SpriteIcon.cs index 1b1df45c77..2d63d0a52c 100644 --- a/osu.Game/Graphics/SpriteIcon.cs +++ b/osu.Game/Graphics/SpriteIcon.cs @@ -7,8 +7,8 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics; using osu.Framework.IO.Stores; -using OpenTK; -using OpenTK.Graphics; +using osuTK; +using osuTK.Graphics; using osu.Framework.Caching; namespace osu.Game.Graphics diff --git a/osu.Game/Graphics/UserInterface/Bar.cs b/osu.Game/Graphics/UserInterface/Bar.cs index 16c63a12f4..89bd6f6118 100644 --- a/osu.Game/Graphics/UserInterface/Bar.cs +++ b/osu.Game/Graphics/UserInterface/Bar.cs @@ -1,8 +1,8 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using OpenTK; -using OpenTK.Graphics; +using osuTK; +using osuTK.Graphics; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; diff --git a/osu.Game/Graphics/UserInterface/BarGraph.cs b/osu.Game/Graphics/UserInterface/BarGraph.cs index 0ba49929e9..f461a0f296 100644 --- a/osu.Game/Graphics/UserInterface/BarGraph.cs +++ b/osu.Game/Graphics/UserInterface/BarGraph.cs @@ -1,7 +1,7 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using OpenTK; +using osuTK; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using System.Collections.Generic; diff --git a/osu.Game/Graphics/UserInterface/BreadcrumbControl.cs b/osu.Game/Graphics/UserInterface/BreadcrumbControl.cs index ebb7b686e4..d4ef335c3e 100644 --- a/osu.Game/Graphics/UserInterface/BreadcrumbControl.cs +++ b/osu.Game/Graphics/UserInterface/BreadcrumbControl.cs @@ -2,7 +2,7 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System; -using OpenTK; +using osuTK; using osu.Framework; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; diff --git a/osu.Game/Graphics/UserInterface/DialogButton.cs b/osu.Game/Graphics/UserInterface/DialogButton.cs index b2220267ff..24bac05384 100644 --- a/osu.Game/Graphics/UserInterface/DialogButton.cs +++ b/osu.Game/Graphics/UserInterface/DialogButton.cs @@ -1,8 +1,8 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using OpenTK; -using OpenTK.Graphics; +using osuTK; +using osuTK.Graphics; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Shapes; diff --git a/osu.Game/Graphics/UserInterface/ExternalLinkButton.cs b/osu.Game/Graphics/UserInterface/ExternalLinkButton.cs index d82448e8a2..8f8ba1ede8 100644 --- a/osu.Game/Graphics/UserInterface/ExternalLinkButton.cs +++ b/osu.Game/Graphics/UserInterface/ExternalLinkButton.cs @@ -7,8 +7,8 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; using osu.Framework.Input.Events; using osu.Framework.Platform; -using OpenTK; -using OpenTK.Graphics; +using osuTK; +using osuTK.Graphics; namespace osu.Game.Graphics.UserInterface { diff --git a/osu.Game/Graphics/UserInterface/FocusedTextBox.cs b/osu.Game/Graphics/UserInterface/FocusedTextBox.cs index 122ac7b627..0564d364ed 100644 --- a/osu.Game/Graphics/UserInterface/FocusedTextBox.cs +++ b/osu.Game/Graphics/UserInterface/FocusedTextBox.cs @@ -1,11 +1,11 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using OpenTK.Graphics; +using osuTK.Graphics; using System; using osu.Framework.Input.Events; using osu.Game.Input.Bindings; -using OpenTK.Input; +using osuTK.Input; namespace osu.Game.Graphics.UserInterface { diff --git a/osu.Game/Graphics/UserInterface/IconButton.cs b/osu.Game/Graphics/UserInterface/IconButton.cs index f10f03873d..c59326a256 100644 --- a/osu.Game/Graphics/UserInterface/IconButton.cs +++ b/osu.Game/Graphics/UserInterface/IconButton.cs @@ -1,8 +1,8 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using OpenTK; -using OpenTK.Graphics; +using osuTK; +using osuTK.Graphics; using osu.Framework.Graphics; using osu.Framework.Input.Events; diff --git a/osu.Game/Graphics/UserInterface/LineGraph.cs b/osu.Game/Graphics/UserInterface/LineGraph.cs index ff2c4cf7cd..c84c500201 100644 --- a/osu.Game/Graphics/UserInterface/LineGraph.cs +++ b/osu.Game/Graphics/UserInterface/LineGraph.cs @@ -5,7 +5,7 @@ using System; using System.Collections.Generic; using System.Linq; using osu.Framework.Caching; -using OpenTK; +using osuTK; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Lines; diff --git a/osu.Game/Graphics/UserInterface/LoadingAnimation.cs b/osu.Game/Graphics/UserInterface/LoadingAnimation.cs index 8d45b03a43..e503436d47 100644 --- a/osu.Game/Graphics/UserInterface/LoadingAnimation.cs +++ b/osu.Game/Graphics/UserInterface/LoadingAnimation.cs @@ -3,8 +3,8 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using OpenTK; -using OpenTK.Graphics; +using osuTK; +using osuTK.Graphics; namespace osu.Game.Graphics.UserInterface { diff --git a/osu.Game/Graphics/UserInterface/Nub.cs b/osu.Game/Graphics/UserInterface/Nub.cs index 84b7fd9dae..974708af97 100644 --- a/osu.Game/Graphics/UserInterface/Nub.cs +++ b/osu.Game/Graphics/UserInterface/Nub.cs @@ -1,8 +1,8 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using OpenTK; -using OpenTK.Graphics; +using osuTK; +using osuTK.Graphics; using osu.Framework.Allocation; using osu.Framework.Configuration; using osu.Framework.Graphics; diff --git a/osu.Game/Graphics/UserInterface/OsuAnimatedButton.cs b/osu.Game/Graphics/UserInterface/OsuAnimatedButton.cs index 4516d7ce76..89760a9873 100644 --- a/osu.Game/Graphics/UserInterface/OsuAnimatedButton.cs +++ b/osu.Game/Graphics/UserInterface/OsuAnimatedButton.cs @@ -8,7 +8,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; using osu.Game.Graphics.Containers; -using OpenTK.Graphics; +using osuTK.Graphics; namespace osu.Game.Graphics.UserInterface { diff --git a/osu.Game/Graphics/UserInterface/OsuButton.cs b/osu.Game/Graphics/UserInterface/OsuButton.cs index ab880cd473..0ff4542678 100644 --- a/osu.Game/Graphics/UserInterface/OsuButton.cs +++ b/osu.Game/Graphics/UserInterface/OsuButton.cs @@ -9,7 +9,7 @@ using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; using osu.Game.Graphics.Sprites; -using OpenTK.Graphics; +using osuTK.Graphics; namespace osu.Game.Graphics.UserInterface { diff --git a/osu.Game/Graphics/UserInterface/OsuCheckbox.cs b/osu.Game/Graphics/UserInterface/OsuCheckbox.cs index e267a7f848..194e81dae1 100644 --- a/osu.Game/Graphics/UserInterface/OsuCheckbox.cs +++ b/osu.Game/Graphics/UserInterface/OsuCheckbox.cs @@ -10,7 +10,7 @@ using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; using osu.Game.Graphics.Sprites; -using OpenTK.Graphics; +using osuTK.Graphics; namespace osu.Game.Graphics.UserInterface { diff --git a/osu.Game/Graphics/UserInterface/OsuContextMenu.cs b/osu.Game/Graphics/UserInterface/OsuContextMenu.cs index 9b697f291f..2dd6d4fe78 100644 --- a/osu.Game/Graphics/UserInterface/OsuContextMenu.cs +++ b/osu.Game/Graphics/UserInterface/OsuContextMenu.cs @@ -1,7 +1,7 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using OpenTK.Graphics; +using osuTK.Graphics; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; diff --git a/osu.Game/Graphics/UserInterface/OsuDropdown.cs b/osu.Game/Graphics/UserInterface/OsuDropdown.cs index 30803d1545..3f15616d6c 100644 --- a/osu.Game/Graphics/UserInterface/OsuDropdown.cs +++ b/osu.Game/Graphics/UserInterface/OsuDropdown.cs @@ -2,7 +2,7 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System.Linq; -using OpenTK.Graphics; +using osuTK.Graphics; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; @@ -10,7 +10,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.UserInterface; using osu.Game.Graphics.Sprites; -using OpenTK; +using osuTK; namespace osu.Game.Graphics.UserInterface { diff --git a/osu.Game/Graphics/UserInterface/OsuEnumDropdown.cs b/osu.Game/Graphics/UserInterface/OsuEnumDropdown.cs index 502f468ec9..5c6a2a0569 100644 --- a/osu.Game/Graphics/UserInterface/OsuEnumDropdown.cs +++ b/osu.Game/Graphics/UserInterface/OsuEnumDropdown.cs @@ -2,9 +2,6 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System; -using System.ComponentModel; -using System.Reflection; -using System.Collections.Generic; namespace osu.Game.Graphics.UserInterface { @@ -15,18 +12,7 @@ namespace osu.Game.Graphics.UserInterface if (!typeof(T).IsEnum) throw new InvalidOperationException("OsuEnumDropdown only supports enums as the generic type argument"); - List> items = new List>(); - foreach (var val in (T[])Enum.GetValues(typeof(T))) - { - var field = typeof(T).GetField(Enum.GetName(typeof(T), val)); - items.Add( - new KeyValuePair( - field.GetCustomAttribute()?.Description ?? Enum.GetName(typeof(T), val), - val - ) - ); - } - Items = items; + Items = (T[])Enum.GetValues(typeof(T)); } } } diff --git a/osu.Game/Graphics/UserInterface/OsuMenu.cs b/osu.Game/Graphics/UserInterface/OsuMenu.cs index fe3e866a70..570ed368d8 100644 --- a/osu.Game/Graphics/UserInterface/OsuMenu.cs +++ b/osu.Game/Graphics/UserInterface/OsuMenu.cs @@ -4,7 +4,7 @@ using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; -using OpenTK.Graphics; +using osuTK.Graphics; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -12,7 +12,7 @@ using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; using osu.Game.Graphics.Sprites; -using OpenTK; +using osuTK; namespace osu.Game.Graphics.UserInterface { diff --git a/osu.Game/Graphics/UserInterface/OsuPasswordTextBox.cs b/osu.Game/Graphics/UserInterface/OsuPasswordTextBox.cs index f4ec67db23..c8a1bf9178 100644 --- a/osu.Game/Graphics/UserInterface/OsuPasswordTextBox.cs +++ b/osu.Game/Graphics/UserInterface/OsuPasswordTextBox.cs @@ -1,9 +1,9 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using OpenTK; -using OpenTK.Graphics; -using OpenTK.Input; +using osuTK; +using osuTK.Graphics; +using osuTK.Input; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; diff --git a/osu.Game/Graphics/UserInterface/OsuSliderBar.cs b/osu.Game/Graphics/UserInterface/OsuSliderBar.cs index eecc10469e..a59abcbcee 100644 --- a/osu.Game/Graphics/UserInterface/OsuSliderBar.cs +++ b/osu.Game/Graphics/UserInterface/OsuSliderBar.cs @@ -3,8 +3,8 @@ using System; using System.Globalization; -using OpenTK; -using OpenTK.Graphics; +using osuTK; +using osuTK.Graphics; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; diff --git a/osu.Game/Graphics/UserInterface/OsuTabControl.cs b/osu.Game/Graphics/UserInterface/OsuTabControl.cs index e7d6a87349..488e16b6fb 100644 --- a/osu.Game/Graphics/UserInterface/OsuTabControl.cs +++ b/osu.Game/Graphics/UserInterface/OsuTabControl.cs @@ -3,8 +3,8 @@ using System; using System.Linq; -using OpenTK; -using OpenTK.Graphics; +using osuTK; +using osuTK.Graphics; using osu.Framework.Allocation; using osu.Framework.Configuration; using osu.Framework.Extensions; diff --git a/osu.Game/Graphics/UserInterface/OsuTabControlCheckbox.cs b/osu.Game/Graphics/UserInterface/OsuTabControlCheckbox.cs index 1355ffdb8e..cb951898da 100644 --- a/osu.Game/Graphics/UserInterface/OsuTabControlCheckbox.cs +++ b/osu.Game/Graphics/UserInterface/OsuTabControlCheckbox.cs @@ -1,8 +1,8 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using OpenTK; -using OpenTK.Graphics; +using osuTK; +using osuTK.Graphics; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; diff --git a/osu.Game/Graphics/UserInterface/OsuTextBox.cs b/osu.Game/Graphics/UserInterface/OsuTextBox.cs index 37464faa73..523b60fdd2 100644 --- a/osu.Game/Graphics/UserInterface/OsuTextBox.cs +++ b/osu.Game/Graphics/UserInterface/OsuTextBox.cs @@ -6,7 +6,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.UserInterface; using osu.Game.Graphics.Sprites; -using OpenTK.Graphics; +using osuTK.Graphics; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; diff --git a/osu.Game/Graphics/UserInterface/PageTabControl.cs b/osu.Game/Graphics/UserInterface/PageTabControl.cs index fb7b4c5676..50e4743028 100644 --- a/osu.Game/Graphics/UserInterface/PageTabControl.cs +++ b/osu.Game/Graphics/UserInterface/PageTabControl.cs @@ -2,8 +2,8 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System; -using OpenTK; -using OpenTK.Graphics; +using osuTK; +using osuTK.Graphics; using osu.Framework.Allocation; using osu.Framework.Extensions; using osu.Framework.Graphics; diff --git a/osu.Game/Graphics/UserInterface/PercentageCounter.cs b/osu.Game/Graphics/UserInterface/PercentageCounter.cs index ef3fc156e7..b1514cb8c8 100644 --- a/osu.Game/Graphics/UserInterface/PercentageCounter.cs +++ b/osu.Game/Graphics/UserInterface/PercentageCounter.cs @@ -2,6 +2,7 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System; +using osu.Framework.Allocation; namespace osu.Game.Graphics.UserInterface { @@ -25,6 +26,9 @@ namespace osu.Game.Graphics.UserInterface Current.Value = DisplayedCount = 1.0f; } + [BackgroundDependencyLoader] + private void load(OsuColour colours) => AccentColour = colours.BlueLighter; + protected override string FormatCount(double count) { return $@"{count:P2}"; diff --git a/osu.Game/Graphics/UserInterface/ProgressBar.cs b/osu.Game/Graphics/UserInterface/ProgressBar.cs index beb1d0055e..ee64c7c25c 100644 --- a/osu.Game/Graphics/UserInterface/ProgressBar.cs +++ b/osu.Game/Graphics/UserInterface/ProgressBar.cs @@ -5,7 +5,7 @@ using System; using osu.Framework.Graphics; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.UserInterface; -using OpenTK.Graphics; +using osuTK.Graphics; namespace osu.Game.Graphics.UserInterface { diff --git a/osu.Game/Graphics/UserInterface/RollingCounter.cs b/osu.Game/Graphics/UserInterface/RollingCounter.cs index c2162b8a42..5a9dd4e05e 100644 --- a/osu.Game/Graphics/UserInterface/RollingCounter.cs +++ b/osu.Game/Graphics/UserInterface/RollingCounter.cs @@ -8,7 +8,7 @@ using osu.Framework.Graphics.Sprites; using osu.Game.Graphics.Sprites; using System; using System.Collections.Generic; -using OpenTK.Graphics; +using osuTK.Graphics; namespace osu.Game.Graphics.UserInterface { diff --git a/osu.Game/Graphics/UserInterface/ScoreCounter.cs b/osu.Game/Graphics/UserInterface/ScoreCounter.cs index df26f81629..8bf30c32c6 100644 --- a/osu.Game/Graphics/UserInterface/ScoreCounter.cs +++ b/osu.Game/Graphics/UserInterface/ScoreCounter.cs @@ -1,6 +1,7 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using osu.Framework.Allocation; using osu.Framework.Graphics; namespace osu.Game.Graphics.UserInterface @@ -31,6 +32,9 @@ namespace osu.Game.Graphics.UserInterface LeadingZeroes = leading; } + [BackgroundDependencyLoader] + private void load(OsuColour colours) => AccentColour = colours.BlueLighter; + protected override double GetProportionalDuration(double currentValue, double newValue) { return currentValue > newValue ? currentValue - newValue : newValue - currentValue; diff --git a/osu.Game/Graphics/UserInterface/SearchTextBox.cs b/osu.Game/Graphics/UserInterface/SearchTextBox.cs index 08e93fad18..40db6677f5 100644 --- a/osu.Game/Graphics/UserInterface/SearchTextBox.cs +++ b/osu.Game/Graphics/UserInterface/SearchTextBox.cs @@ -3,8 +3,8 @@ using osu.Framework.Graphics; using osu.Framework.Input.Events; -using OpenTK; -using OpenTK.Input; +using osuTK; +using osuTK.Input; namespace osu.Game.Graphics.UserInterface { diff --git a/osu.Game/Graphics/UserInterface/SimpleComboCounter.cs b/osu.Game/Graphics/UserInterface/SimpleComboCounter.cs index cf55240e71..ddc48c9be7 100644 --- a/osu.Game/Graphics/UserInterface/SimpleComboCounter.cs +++ b/osu.Game/Graphics/UserInterface/SimpleComboCounter.cs @@ -2,6 +2,7 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System; +using osu.Framework.Allocation; namespace osu.Game.Graphics.UserInterface { @@ -17,6 +18,9 @@ namespace osu.Game.Graphics.UserInterface Current.Value = DisplayedCount = 0; } + [BackgroundDependencyLoader] + private void load(OsuColour colours) => AccentColour = colours.BlueLighter; + protected override string FormatCount(int count) { return $@"{count}x"; diff --git a/osu.Game/Graphics/UserInterface/StarCounter.cs b/osu.Game/Graphics/UserInterface/StarCounter.cs index 621b5dcf11..009a2a2e1b 100644 --- a/osu.Game/Graphics/UserInterface/StarCounter.cs +++ b/osu.Game/Graphics/UserInterface/StarCounter.cs @@ -1,7 +1,7 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using OpenTK; +using osuTK; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.MathUtils; diff --git a/osu.Game/Graphics/UserInterface/TwoLayerButton.cs b/osu.Game/Graphics/UserInterface/TwoLayerButton.cs index 2000eb47e4..16f727b783 100644 --- a/osu.Game/Graphics/UserInterface/TwoLayerButton.cs +++ b/osu.Game/Graphics/UserInterface/TwoLayerButton.cs @@ -4,8 +4,8 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; -using OpenTK; -using OpenTK.Graphics; +using osuTK; +using osuTK.Graphics; using osu.Game.Graphics.Sprites; using osu.Framework.Extensions.Color4Extensions; using osu.Game.Graphics.Containers; diff --git a/osu.Game/IO/Archives/LegacyFilesystemReader.cs b/osu.Game/IO/Archives/LegacyDirectoryArchiveReader.cs similarity index 85% rename from osu.Game/IO/Archives/LegacyFilesystemReader.cs rename to osu.Game/IO/Archives/LegacyDirectoryArchiveReader.cs index dcaaf41c77..cad056b66a 100644 --- a/osu.Game/IO/Archives/LegacyFilesystemReader.cs +++ b/osu.Game/IO/Archives/LegacyDirectoryArchiveReader.cs @@ -8,13 +8,13 @@ using System.Linq; namespace osu.Game.IO.Archives { /// - /// Reads an extracted legacy beatmap from disk. + /// Reads an archive from a directory on disk. /// - public class LegacyFilesystemReader : ArchiveReader + public class LegacyDirectoryArchiveReader : ArchiveReader { private readonly string path; - public LegacyFilesystemReader(string path) + public LegacyDirectoryArchiveReader(string path) : base(Path.GetFileName(path)) { // re-get full path to standardise with Directory.GetFiles return values below. diff --git a/osu.Game/IO/Archives/LegacyFileArchiveReader.cs b/osu.Game/IO/Archives/LegacyFileArchiveReader.cs new file mode 100644 index 0000000000..28b5e628e4 --- /dev/null +++ b/osu.Game/IO/Archives/LegacyFileArchiveReader.cs @@ -0,0 +1,34 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Collections.Generic; +using System.IO; + +namespace osu.Game.IO.Archives +{ + /// + /// Reads a file on disk as an archive. + /// Note: In this case, the file is not an extractable archive, use instead. + /// + public class LegacyFileArchiveReader : ArchiveReader + { + private readonly string path; + + public LegacyFileArchiveReader(string path) + : base(Path.GetFileName(path)) + { + // re-get full path to standardise + this.path = Path.GetFullPath(path); + } + + public override Stream GetStream(string name) => File.OpenRead(path); + + public override void Dispose() + { + } + + public override IEnumerable Filenames => new[] { Name }; + + public override Stream GetUnderlyingStream() => null; + } +} diff --git a/osu.Game/IO/FileInfo.cs b/osu.Game/IO/FileInfo.cs index 1804be90a0..edd389bc65 100644 --- a/osu.Game/IO/FileInfo.cs +++ b/osu.Game/IO/FileInfo.cs @@ -1,14 +1,13 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using System.ComponentModel.DataAnnotations.Schema; using System.IO; +using osu.Game.Database; namespace osu.Game.IO { - public class FileInfo + public class FileInfo : IHasPrimaryKey { - [DatabaseGenerated(DatabaseGeneratedOption.Identity)] public int ID { get; set; } public string Hash { get; set; } diff --git a/osu.Game/IO/Serialization/Converters/Vector2Converter.cs b/osu.Game/IO/Serialization/Converters/Vector2Converter.cs index ba2158df28..4b057c0f2f 100644 --- a/osu.Game/IO/Serialization/Converters/Vector2Converter.cs +++ b/osu.Game/IO/Serialization/Converters/Vector2Converter.cs @@ -4,7 +4,7 @@ using System; using Newtonsoft.Json; using Newtonsoft.Json.Linq; -using OpenTK; +using osuTK; namespace osu.Game.IO.Serialization.Converters { diff --git a/osu.Game/IPC/ScoreIPCChannel.cs b/osu.Game/IPC/ScoreIPCChannel.cs deleted file mode 100644 index a66b8ce1f3..0000000000 --- a/osu.Game/IPC/ScoreIPCChannel.cs +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.Diagnostics; -using System.Threading.Tasks; -using osu.Framework.Platform; -using osu.Game.Rulesets.Scoring; - -namespace osu.Game.IPC -{ - public class ScoreIPCChannel : IpcChannel - { - private readonly ScoreStore scores; - - public ScoreIPCChannel(IIpcHost host, ScoreStore scores = null) - : base(host) - { - this.scores = scores; - MessageReceived += msg => - { - Debug.Assert(scores != null); - ImportAsync(msg.Path).ContinueWith(t => - { - if (t.Exception != null) throw t.Exception; - }, TaskContinuationOptions.OnlyOnFaulted); - }; - } - - public async Task ImportAsync(string path) - { - if (scores == null) - { - //we want to contact a remote osu! to handle the import. - await SendMessageAsync(new ScoreImportMessage { Path = path }); - return; - } - - scores.ReadReplayFile(path); - } - } - - public class ScoreImportMessage - { - public string Path; - } -} diff --git a/osu.Game/Input/Bindings/DatabasedKeyBinding.cs b/osu.Game/Input/Bindings/DatabasedKeyBinding.cs index 7a230f32a0..e69dd30822 100644 --- a/osu.Game/Input/Bindings/DatabasedKeyBinding.cs +++ b/osu.Game/Input/Bindings/DatabasedKeyBinding.cs @@ -10,7 +10,6 @@ namespace osu.Game.Input.Bindings [Table("KeyBinding")] public class DatabasedKeyBinding : KeyBinding, IHasPrimaryKey { - [DatabaseGenerated(DatabaseGeneratedOption.Identity)] public int ID { get; set; } public int? RulesetID { get; set; } diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs index 2f5f1aea3f..c58e845a6a 100644 --- a/osu.Game/Input/Bindings/GlobalActionContainer.cs +++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs @@ -43,6 +43,7 @@ namespace osu.Game.Input.Bindings new KeyBinding(InputKey.Space, GlobalAction.Select), new KeyBinding(InputKey.Enter, GlobalAction.Select), + new KeyBinding(InputKey.KeypadEnter, GlobalAction.Select), }; public IEnumerable InGameKeyBindings => new[] diff --git a/osu.Game/Input/Handlers/ReplayInputHandler.cs b/osu.Game/Input/Handlers/ReplayInputHandler.cs index 0a62c6ca72..c8c5657e41 100644 --- a/osu.Game/Input/Handlers/ReplayInputHandler.cs +++ b/osu.Game/Input/Handlers/ReplayInputHandler.cs @@ -10,7 +10,7 @@ using osu.Framework.Input.StateChanges.Events; using osu.Framework.Input.States; using osu.Framework.Platform; using osu.Game.Rulesets.UI; -using OpenTK; +using osuTK; namespace osu.Game.Input.Handlers { diff --git a/osu.Game/Input/IdleTracker.cs b/osu.Game/Input/IdleTracker.cs new file mode 100644 index 0000000000..d96fa8bd34 --- /dev/null +++ b/osu.Game/Input/IdleTracker.cs @@ -0,0 +1,71 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Configuration; +using osu.Framework.Graphics; +using osu.Framework.Input; +using osu.Framework.Input.Bindings; +using osu.Framework.Input.Events; + +namespace osu.Game.Input +{ + /// + /// Track whether the end-user is in an idle state, based on their last interaction with the game. + /// + public class IdleTracker : Component, IKeyBindingHandler, IHandleGlobalInput + { + private readonly double timeToIdle; + + private double lastInteractionTime; + + protected double TimeSpentIdle => Clock.CurrentTime - lastInteractionTime; + + /// + /// Whether the user is currently in an idle state. + /// + public IBindable IsIdle => isIdle; + + private readonly BindableBool isIdle = new BindableBool(); + + /// + /// Intstantiate a new . + /// + /// The length in milliseconds until an idle state should be assumed. + public IdleTracker(double timeToIdle) + { + this.timeToIdle = timeToIdle; + RelativeSizeAxes = Axes.Both; + } + + protected override void Update() + { + base.Update(); + isIdle.Value = TimeSpentIdle > timeToIdle; + } + + public bool OnPressed(PlatformAction action) => updateLastInteractionTime(); + + public bool OnReleased(PlatformAction action) => updateLastInteractionTime(); + + protected override bool Handle(UIEvent e) + { + switch (e) + { + case KeyDownEvent _: + case KeyUpEvent _: + case MouseDownEvent _: + case MouseUpEvent _: + case MouseMoveEvent _: + return updateLastInteractionTime(); + default: + return base.Handle(e); + } + } + + private bool updateLastInteractionTime() + { + lastInteractionTime = Clock.CurrentTime; + return false; + } + } +} diff --git a/osu.Game/Migrations/20181128100659_AddSkinInfoHash.Designer.cs b/osu.Game/Migrations/20181128100659_AddSkinInfoHash.Designer.cs new file mode 100644 index 0000000000..120674671a --- /dev/null +++ b/osu.Game/Migrations/20181128100659_AddSkinInfoHash.Designer.cs @@ -0,0 +1,387 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using osu.Game.Database; + +namespace osu.Game.Migrations +{ + [DbContext(typeof(OsuDbContext))] + [Migration("20181128100659_AddSkinInfoHash")] + partial class AddSkinInfoHash + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.1.4-rtm-31024"); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("ApproachRate"); + + b.Property("CircleSize"); + + b.Property("DrainRate"); + + b.Property("OverallDifficulty"); + + b.Property("SliderMultiplier"); + + b.Property("SliderTickRate"); + + b.HasKey("ID"); + + b.ToTable("BeatmapDifficulty"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("AudioLeadIn"); + + b.Property("BaseDifficultyID"); + + b.Property("BeatDivisor"); + + b.Property("BeatmapSetInfoID"); + + b.Property("Countdown"); + + b.Property("DistanceSpacing"); + + b.Property("GridSize"); + + b.Property("Hash"); + + b.Property("Hidden"); + + b.Property("LetterboxInBreaks"); + + b.Property("MD5Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapID"); + + b.Property("Path"); + + b.Property("RulesetID"); + + b.Property("SpecialStyle"); + + b.Property("StackLeniency"); + + b.Property("StarDifficulty"); + + b.Property("Status"); + + b.Property("StoredBookmarks"); + + b.Property("TimelineZoom"); + + b.Property("Version"); + + b.Property("WidescreenStoryboard"); + + b.HasKey("ID"); + + b.HasIndex("BaseDifficultyID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("Hash"); + + b.HasIndex("MD5Hash"); + + b.HasIndex("MetadataID"); + + b.HasIndex("OnlineBeatmapID") + .IsUnique(); + + b.HasIndex("RulesetID"); + + b.ToTable("BeatmapInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Artist"); + + b.Property("ArtistUnicode"); + + b.Property("AudioFile"); + + b.Property("AuthorString") + .HasColumnName("Author"); + + b.Property("BackgroundFile"); + + b.Property("PreviewTime"); + + b.Property("Source"); + + b.Property("Tags"); + + b.Property("Title"); + + b.Property("TitleUnicode"); + + b.HasKey("ID"); + + b.ToTable("BeatmapMetadata"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("BeatmapSetInfoID"); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.HasKey("ID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("FileInfoID"); + + b.ToTable("BeatmapSetFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapSetID"); + + b.Property("Protected"); + + b.Property("Status"); + + b.HasKey("ID"); + + b.HasIndex("DeletePending"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("MetadataID"); + + b.HasIndex("OnlineBeatmapSetID") + .IsUnique(); + + b.ToTable("BeatmapSetInfo"); + }); + + modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("IntKey") + .HasColumnName("Key"); + + b.Property("RulesetID"); + + b.Property("StringValue") + .HasColumnName("Value"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("RulesetID", "Variant"); + + b.ToTable("Settings"); + }); + + modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("IntAction") + .HasColumnName("Action"); + + b.Property("KeysString") + .HasColumnName("Keys"); + + b.Property("RulesetID"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("IntAction"); + + b.HasIndex("RulesetID", "Variant"); + + b.ToTable("KeyBinding"); + }); + + modelBuilder.Entity("osu.Game.IO.FileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Hash"); + + b.Property("ReferenceCount"); + + b.HasKey("ID"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("ReferenceCount"); + + b.ToTable("FileInfo"); + }); + + modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Available"); + + b.Property("InstantiationInfo"); + + b.Property("Name"); + + b.Property("ShortName"); + + b.HasKey("ID"); + + b.HasIndex("Available"); + + b.HasIndex("ShortName") + .IsUnique(); + + b.ToTable("RulesetInfo"); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.Property("SkinInfoID"); + + b.HasKey("ID"); + + b.HasIndex("FileInfoID"); + + b.HasIndex("SkinInfoID"); + + b.ToTable("SkinFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Creator"); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("Name"); + + b.HasKey("ID"); + + b.HasIndex("DeletePending"); + + b.HasIndex("Hash") + .IsUnique(); + + b.ToTable("SkinInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty") + .WithMany() + .HasForeignKey("BaseDifficultyID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet") + .WithMany("Beatmaps") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("Beatmaps") + .HasForeignKey("MetadataID"); + + b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") + .WithMany() + .HasForeignKey("RulesetID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo") + .WithMany("Files") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("BeatmapSets") + .HasForeignKey("MetadataID"); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => + { + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Skinning.SkinInfo") + .WithMany("Files") + .HasForeignKey("SkinInfoID") + .OnDelete(DeleteBehavior.Cascade); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/osu.Game/Migrations/20181128100659_AddSkinInfoHash.cs b/osu.Game/Migrations/20181128100659_AddSkinInfoHash.cs new file mode 100644 index 0000000000..860264a7dd --- /dev/null +++ b/osu.Game/Migrations/20181128100659_AddSkinInfoHash.cs @@ -0,0 +1,41 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace osu.Game.Migrations +{ + public partial class AddSkinInfoHash : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "Hash", + table: "SkinInfo", + nullable: true); + + migrationBuilder.CreateIndex( + name: "IX_SkinInfo_DeletePending", + table: "SkinInfo", + column: "DeletePending"); + + migrationBuilder.CreateIndex( + name: "IX_SkinInfo_Hash", + table: "SkinInfo", + column: "Hash", + unique: true); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropIndex( + name: "IX_SkinInfo_DeletePending", + table: "SkinInfo"); + + migrationBuilder.DropIndex( + name: "IX_SkinInfo_Hash", + table: "SkinInfo"); + + migrationBuilder.DropColumn( + name: "Hash", + table: "SkinInfo"); + } + } +} diff --git a/osu.Game/Migrations/20181130113755_AddScoreInfoTables.Designer.cs b/osu.Game/Migrations/20181130113755_AddScoreInfoTables.Designer.cs new file mode 100644 index 0000000000..eee53182ce --- /dev/null +++ b/osu.Game/Migrations/20181130113755_AddScoreInfoTables.Designer.cs @@ -0,0 +1,484 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using osu.Game.Database; + +namespace osu.Game.Migrations +{ + [DbContext(typeof(OsuDbContext))] + [Migration("20181130113755_AddScoreInfoTables")] + partial class AddScoreInfoTables + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.1.4-rtm-31024"); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("ApproachRate"); + + b.Property("CircleSize"); + + b.Property("DrainRate"); + + b.Property("OverallDifficulty"); + + b.Property("SliderMultiplier"); + + b.Property("SliderTickRate"); + + b.HasKey("ID"); + + b.ToTable("BeatmapDifficulty"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("AudioLeadIn"); + + b.Property("BaseDifficultyID"); + + b.Property("BeatDivisor"); + + b.Property("BeatmapSetInfoID"); + + b.Property("Countdown"); + + b.Property("DistanceSpacing"); + + b.Property("GridSize"); + + b.Property("Hash"); + + b.Property("Hidden"); + + b.Property("LetterboxInBreaks"); + + b.Property("MD5Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapID"); + + b.Property("Path"); + + b.Property("RulesetID"); + + b.Property("SpecialStyle"); + + b.Property("StackLeniency"); + + b.Property("StarDifficulty"); + + b.Property("Status"); + + b.Property("StoredBookmarks"); + + b.Property("TimelineZoom"); + + b.Property("Version"); + + b.Property("WidescreenStoryboard"); + + b.HasKey("ID"); + + b.HasIndex("BaseDifficultyID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("Hash"); + + b.HasIndex("MD5Hash"); + + b.HasIndex("MetadataID"); + + b.HasIndex("OnlineBeatmapID") + .IsUnique(); + + b.HasIndex("RulesetID"); + + b.ToTable("BeatmapInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Artist"); + + b.Property("ArtistUnicode"); + + b.Property("AudioFile"); + + b.Property("AuthorString") + .HasColumnName("Author"); + + b.Property("BackgroundFile"); + + b.Property("PreviewTime"); + + b.Property("Source"); + + b.Property("Tags"); + + b.Property("Title"); + + b.Property("TitleUnicode"); + + b.HasKey("ID"); + + b.ToTable("BeatmapMetadata"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("BeatmapSetInfoID"); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.HasKey("ID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("FileInfoID"); + + b.ToTable("BeatmapSetFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapSetID"); + + b.Property("Protected"); + + b.Property("Status"); + + b.HasKey("ID"); + + b.HasIndex("DeletePending"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("MetadataID"); + + b.HasIndex("OnlineBeatmapSetID") + .IsUnique(); + + b.ToTable("BeatmapSetInfo"); + }); + + modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("IntKey") + .HasColumnName("Key"); + + b.Property("RulesetID"); + + b.Property("StringValue") + .HasColumnName("Value"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("RulesetID", "Variant"); + + b.ToTable("Settings"); + }); + + modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("IntAction") + .HasColumnName("Action"); + + b.Property("KeysString") + .HasColumnName("Keys"); + + b.Property("RulesetID"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("IntAction"); + + b.HasIndex("RulesetID", "Variant"); + + b.ToTable("KeyBinding"); + }); + + modelBuilder.Entity("osu.Game.IO.FileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Hash"); + + b.Property("ReferenceCount"); + + b.HasKey("ID"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("ReferenceCount"); + + b.ToTable("FileInfo"); + }); + + modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Available"); + + b.Property("InstantiationInfo"); + + b.Property("Name"); + + b.Property("ShortName"); + + b.HasKey("ID"); + + b.HasIndex("Available"); + + b.HasIndex("ShortName") + .IsUnique(); + + b.ToTable("RulesetInfo"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.Property("ScoreInfoID"); + + b.HasKey("ID"); + + b.HasIndex("FileInfoID"); + + b.HasIndex("ScoreInfoID"); + + b.ToTable("ScoreFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Accuracy") + .HasColumnType("DECIMAL(1,4)"); + + b.Property("BeatmapInfoID"); + + b.Property("Combo"); + + b.Property("Date"); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("MaxCombo"); + + b.Property("ModsJson") + .HasColumnName("Mods"); + + b.Property("OnlineScoreID"); + + b.Property("PP"); + + b.Property("Rank"); + + b.Property("RulesetID"); + + b.Property("StatisticsJson") + .HasColumnName("Statistics"); + + b.Property("TotalScore"); + + b.Property("UserString") + .HasColumnName("User"); + + b.HasKey("ID"); + + b.HasIndex("BeatmapInfoID"); + + b.HasIndex("OnlineScoreID") + .IsUnique(); + + b.HasIndex("RulesetID"); + + b.ToTable("ScoreInfo"); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.Property("SkinInfoID"); + + b.HasKey("ID"); + + b.HasIndex("FileInfoID"); + + b.HasIndex("SkinInfoID"); + + b.ToTable("SkinFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Creator"); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("Name"); + + b.HasKey("ID"); + + b.HasIndex("DeletePending"); + + b.HasIndex("Hash") + .IsUnique(); + + b.ToTable("SkinInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty") + .WithMany() + .HasForeignKey("BaseDifficultyID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet") + .WithMany("Beatmaps") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("Beatmaps") + .HasForeignKey("MetadataID"); + + b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") + .WithMany() + .HasForeignKey("RulesetID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo") + .WithMany("Files") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("BeatmapSets") + .HasForeignKey("MetadataID"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b => + { + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Scoring.ScoreInfo") + .WithMany("Files") + .HasForeignKey("ScoreInfoID"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapInfo", "Beatmap") + .WithMany() + .HasForeignKey("BeatmapInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") + .WithMany() + .HasForeignKey("RulesetID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => + { + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Skinning.SkinInfo") + .WithMany("Files") + .HasForeignKey("SkinInfoID") + .OnDelete(DeleteBehavior.Cascade); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/osu.Game/Migrations/20181130113755_AddScoreInfoTables.cs b/osu.Game/Migrations/20181130113755_AddScoreInfoTables.cs new file mode 100644 index 0000000000..2b6f94c5a4 --- /dev/null +++ b/osu.Game/Migrations/20181130113755_AddScoreInfoTables.cs @@ -0,0 +1,112 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace osu.Game.Migrations +{ + public partial class AddScoreInfoTables : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "ScoreInfo", + columns: table => new + { + ID = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Rank = table.Column(nullable: false), + TotalScore = table.Column(nullable: false), + Accuracy = table.Column(type: "DECIMAL(1,4)", nullable: false), + PP = table.Column(nullable: true), + MaxCombo = table.Column(nullable: false), + Combo = table.Column(nullable: false), + RulesetID = table.Column(nullable: false), + Mods = table.Column(nullable: true), + User = table.Column(nullable: true), + BeatmapInfoID = table.Column(nullable: false), + OnlineScoreID = table.Column(nullable: true), + Date = table.Column(nullable: false), + Statistics = table.Column(nullable: true), + Hash = table.Column(nullable: true), + DeletePending = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ScoreInfo", x => x.ID); + table.ForeignKey( + name: "FK_ScoreInfo_BeatmapInfo_BeatmapInfoID", + column: x => x.BeatmapInfoID, + principalTable: "BeatmapInfo", + principalColumn: "ID", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_ScoreInfo_RulesetInfo_RulesetID", + column: x => x.RulesetID, + principalTable: "RulesetInfo", + principalColumn: "ID", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "ScoreFileInfo", + columns: table => new + { + ID = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + FileInfoID = table.Column(nullable: false), + Filename = table.Column(nullable: false), + ScoreInfoID = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_ScoreFileInfo", x => x.ID); + table.ForeignKey( + name: "FK_ScoreFileInfo_FileInfo_FileInfoID", + column: x => x.FileInfoID, + principalTable: "FileInfo", + principalColumn: "ID", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_ScoreFileInfo_ScoreInfo_ScoreInfoID", + column: x => x.ScoreInfoID, + principalTable: "ScoreInfo", + principalColumn: "ID", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateIndex( + name: "IX_ScoreFileInfo_FileInfoID", + table: "ScoreFileInfo", + column: "FileInfoID"); + + migrationBuilder.CreateIndex( + name: "IX_ScoreFileInfo_ScoreInfoID", + table: "ScoreFileInfo", + column: "ScoreInfoID"); + + migrationBuilder.CreateIndex( + name: "IX_ScoreInfo_BeatmapInfoID", + table: "ScoreInfo", + column: "BeatmapInfoID"); + + migrationBuilder.CreateIndex( + name: "IX_ScoreInfo_OnlineScoreID", + table: "ScoreInfo", + column: "OnlineScoreID", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_ScoreInfo_RulesetID", + table: "ScoreInfo", + column: "RulesetID"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "ScoreFileInfo"); + + migrationBuilder.DropTable( + name: "ScoreInfo"); + } + } +} diff --git a/osu.Game/Migrations/OsuDbContextModelSnapshot.cs b/osu.Game/Migrations/OsuDbContextModelSnapshot.cs index 663676a6d3..8026847e3b 100644 --- a/osu.Game/Migrations/OsuDbContextModelSnapshot.cs +++ b/osu.Game/Migrations/OsuDbContextModelSnapshot.cs @@ -14,7 +14,7 @@ namespace osu.Game.Migrations { #pragma warning disable 612, 618 modelBuilder - .HasAnnotation("ProductVersion", "2.1.3-rtm-32065"); + .HasAnnotation("ProductVersion", "2.1.4-rtm-31024"); modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => { @@ -281,6 +281,78 @@ namespace osu.Game.Migrations b.ToTable("RulesetInfo"); }); + modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.Property("ScoreInfoID"); + + b.HasKey("ID"); + + b.HasIndex("FileInfoID"); + + b.HasIndex("ScoreInfoID"); + + b.ToTable("ScoreFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Accuracy") + .HasColumnType("DECIMAL(1,4)"); + + b.Property("BeatmapInfoID"); + + b.Property("Combo"); + + b.Property("Date"); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("MaxCombo"); + + b.Property("ModsJson") + .HasColumnName("Mods"); + + b.Property("OnlineScoreID"); + + b.Property("PP"); + + b.Property("Rank"); + + b.Property("RulesetID"); + + b.Property("StatisticsJson") + .HasColumnName("Statistics"); + + b.Property("TotalScore"); + + b.Property("UserString") + .HasColumnName("User"); + + b.HasKey("ID"); + + b.HasIndex("BeatmapInfoID"); + + b.HasIndex("OnlineScoreID") + .IsUnique(); + + b.HasIndex("RulesetID"); + + b.ToTable("ScoreInfo"); + }); + modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => { b.Property("ID") @@ -311,10 +383,17 @@ namespace osu.Game.Migrations b.Property("DeletePending"); + b.Property("Hash"); + b.Property("Name"); b.HasKey("ID"); + b.HasIndex("DeletePending"); + + b.HasIndex("Hash") + .IsUnique(); + b.ToTable("SkinInfo"); }); @@ -360,6 +439,31 @@ namespace osu.Game.Migrations .HasForeignKey("MetadataID"); }); + modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b => + { + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Scoring.ScoreInfo") + .WithMany("Files") + .HasForeignKey("ScoreInfoID"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapInfo", "Beatmap") + .WithMany() + .HasForeignKey("BeatmapInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") + .WithMany() + .HasForeignKey("RulesetID") + .OnDelete(DeleteBehavior.Cascade); + }); + modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => { b.HasOne("osu.Game.IO.FileInfo", "FileInfo") diff --git a/osu.Game/Online/API/APIMessagesRequest.cs b/osu.Game/Online/API/APIMessagesRequest.cs new file mode 100644 index 0000000000..991096c0af --- /dev/null +++ b/osu.Game/Online/API/APIMessagesRequest.cs @@ -0,0 +1,28 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Collections.Generic; +using osu.Framework.IO.Network; +using osu.Game.Online.Chat; + +namespace osu.Game.Online.API +{ + public abstract class APIMessagesRequest : APIRequest> + { + private readonly long? sinceId; + + protected APIMessagesRequest(long? sinceId) + { + this.sinceId = sinceId; + } + + protected override WebRequest CreateWebRequest() + { + var req = base.CreateWebRequest(); + + if (sinceId.HasValue) req.AddParameter(@"since", sinceId.Value.ToString()); + + return req; + } + } +} diff --git a/osu.Game/Online/API/OAuth.cs b/osu.Game/Online/API/OAuth.cs index 7892df9aab..f60567a706 100644 --- a/osu.Game/Online/API/OAuth.cs +++ b/osu.Game/Online/API/OAuth.cs @@ -178,6 +178,7 @@ namespace osu.Game.Online.API AddParameter("grant_type", GrantType); AddParameter("client_id", ClientId); AddParameter("client_secret", ClientSecret); + AddParameter("scope", "*"); base.PrePerform(); } diff --git a/osu.Game/Online/API/Requests/CreateNewPrivateMessageRequest.cs b/osu.Game/Online/API/Requests/CreateNewPrivateMessageRequest.cs new file mode 100644 index 0000000000..00dab10c75 --- /dev/null +++ b/osu.Game/Online/API/Requests/CreateNewPrivateMessageRequest.cs @@ -0,0 +1,34 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Net.Http; +using osu.Framework.IO.Network; +using osu.Game.Online.Chat; +using osu.Game.Users; + +namespace osu.Game.Online.API.Requests +{ + public class CreateNewPrivateMessageRequest : APIRequest + { + private readonly User user; + private readonly Message message; + + public CreateNewPrivateMessageRequest(User user, Message message) + { + this.user = user; + this.message = message; + } + + protected override WebRequest CreateWebRequest() + { + var req = base.CreateWebRequest(); + req.Method = HttpMethod.Post; + req.AddParameter(@"target_id", user.Id.ToString()); + req.AddParameter(@"message", message.Content); + req.AddParameter(@"is_action", message.IsAction.ToString().ToLowerInvariant()); + return req; + } + + protected override string Target => @"chat/new"; + } +} diff --git a/osu.Game/Online/API/Requests/CreateNewPrivateMessageResponse.cs b/osu.Game/Online/API/Requests/CreateNewPrivateMessageResponse.cs new file mode 100644 index 0000000000..84f1593c31 --- /dev/null +++ b/osu.Game/Online/API/Requests/CreateNewPrivateMessageResponse.cs @@ -0,0 +1,16 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using Newtonsoft.Json; +using osu.Game.Online.Chat; + +namespace osu.Game.Online.API.Requests +{ + public class CreateNewPrivateMessageResponse + { + [JsonProperty("new_channel_id")] + public int ChannelID; + + public Message Message; + } +} diff --git a/osu.Game/Online/API/Requests/GetScoresRequest.cs b/osu.Game/Online/API/Requests/GetScoresRequest.cs index 3be5b91a0d..2751dd956b 100644 --- a/osu.Game/Online/API/Requests/GetScoresRequest.cs +++ b/osu.Game/Online/API/Requests/GetScoresRequest.cs @@ -33,8 +33,8 @@ namespace osu.Game.Online.API.Requests private void onSuccess(APIScores r) { - foreach (APIScore score in r.Scores) - score.ApplyBeatmap(beatmap); + foreach (APIScoreInfo score in r.Scores) + score.Beatmap = beatmap; } protected override WebRequest CreateWebRequest() diff --git a/osu.Game/Online/API/Requests/GetUserScoresRequest.cs b/osu.Game/Online/API/Requests/GetUserScoresRequest.cs index 4d4aa4d957..12d70ea327 100644 --- a/osu.Game/Online/API/Requests/GetUserScoresRequest.cs +++ b/osu.Game/Online/API/Requests/GetUserScoresRequest.cs @@ -6,7 +6,7 @@ using osu.Game.Online.API.Requests.Responses; namespace osu.Game.Online.API.Requests { - public class GetUserScoresRequest : APIRequest> + public class GetUserScoresRequest : APIRequest> { private readonly long userId; private readonly ScoreType type; diff --git a/osu.Game/Online/API/Requests/Responses/APIRecentActivity.cs b/osu.Game/Online/API/Requests/Responses/APIRecentActivity.cs index 2c65a37cf8..c38c443ec3 100644 --- a/osu.Game/Online/API/Requests/Responses/APIRecentActivity.cs +++ b/osu.Game/Online/API/Requests/Responses/APIRecentActivity.cs @@ -4,7 +4,7 @@ using System; using Humanizer; using Newtonsoft.Json; -using osu.Game.Rulesets.Scoring; +using osu.Game.Scoring; namespace osu.Game.Online.API.Requests.Responses { diff --git a/osu.Game/Online/API/Requests/Responses/APIScore.cs b/osu.Game/Online/API/Requests/Responses/APIScoreInfo.cs similarity index 76% rename from osu.Game/Online/API/Requests/Responses/APIScore.cs rename to osu.Game/Online/API/Requests/Responses/APIScoreInfo.cs index 25eb32a79f..b26bc751b9 100644 --- a/osu.Game/Online/API/Requests/Responses/APIScore.cs +++ b/osu.Game/Online/API/Requests/Responses/APIScoreInfo.cs @@ -7,16 +7,16 @@ using System.Linq; using Newtonsoft.Json; using osu.Game.Beatmaps; using osu.Game.Rulesets; -using osu.Game.Rulesets.Replays; using osu.Game.Rulesets.Scoring; +using osu.Game.Scoring; using osu.Game.Users; namespace osu.Game.Online.API.Requests.Responses { - public class APIScore : Score + public class APIScoreInfo : ScoreInfo { [JsonProperty(@"score")] - private double totalScore + private int totalScore { set => TotalScore = value; } @@ -33,15 +33,6 @@ namespace osu.Game.Online.API.Requests.Responses set => User = value; } - [JsonProperty(@"replay_data")] - private Replay replay - { - set => Replay = value; - } - - [JsonProperty(@"mode_int")] - public int OnlineRulesetID { get; set; } - [JsonProperty(@"score_id")] private long onlineScoreID { @@ -74,7 +65,7 @@ namespace osu.Game.Online.API.Requests.Responses } [JsonProperty(@"statistics")] - private Dictionary jsonStats + private Dictionary jsonStats { set { @@ -104,21 +95,36 @@ namespace osu.Game.Online.API.Requests.Responses } } + [JsonProperty(@"mode_int")] + public int OnlineRulesetID { get; set; } + [JsonProperty(@"mods")] private string[] modStrings { get; set; } - public void ApplyBeatmap(BeatmapInfo beatmap) + public override BeatmapInfo Beatmap { - Beatmap = beatmap; - ApplyRuleset(beatmap.Ruleset); + get => base.Beatmap; + set + { + base.Beatmap = value; + if (Beatmap.Ruleset != null) + Ruleset = value.Ruleset; + } } - public void ApplyRuleset(RulesetInfo ruleset) + public override RulesetInfo Ruleset { - Ruleset = ruleset; + get => base.Ruleset; + set + { + base.Ruleset = value; - // Evaluate the mod string - Mods = Ruleset.CreateInstance().GetAllMods().Where(mod => modStrings.Contains(mod.ShortenedName)).ToArray(); + if (modStrings != null) + { + // Evaluate the mod string + Mods = Ruleset.CreateInstance().GetAllMods().Where(mod => modStrings.Contains(mod.Acronym)).ToArray(); + } + } } } } diff --git a/osu.Game/Online/API/Requests/Responses/APIScores.cs b/osu.Game/Online/API/Requests/Responses/APIScores.cs index b4213db253..ccffa3f9ed 100644 --- a/osu.Game/Online/API/Requests/Responses/APIScores.cs +++ b/osu.Game/Online/API/Requests/Responses/APIScores.cs @@ -9,6 +9,6 @@ namespace osu.Game.Online.API.Requests.Responses public class APIScores { [JsonProperty(@"scores")] - public IEnumerable Scores; + public IEnumerable Scores; } } diff --git a/osu.Game/Online/Chat/Channel.cs b/osu.Game/Online/Chat/Channel.cs index bbe74fcac0..9d3b7b5cc9 100644 --- a/osu.Game/Online/Chat/Channel.cs +++ b/osu.Game/Online/Chat/Channel.cs @@ -3,15 +3,64 @@ using System; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.Linq; using Newtonsoft.Json; using osu.Framework.Configuration; using osu.Framework.Lists; +using osu.Game.Users; namespace osu.Game.Online.Chat { public class Channel { + public readonly int MaxHistory = 300; + + /// + /// Contains every joined user except the current logged in user. Currently only returned for PM channels. + /// + public readonly ObservableCollection Users = new ObservableCollection(); + + [JsonProperty(@"users")] + private long[] userIds + { + set + { + foreach (var id in value) + Users.Add(new User { Id = id }); + } + } + + /// + /// Contains all the messages send in the channel. + /// + public readonly SortedList Messages = new SortedList(Comparer.Default); + + /// + /// Contains all the messages that are still pending for submission to the server. + /// + private readonly List pendingMessages = new List(); + + + /// + /// An event that fires when new messages arrived. + /// + public event Action> NewMessagesArrived; + + /// + /// An event that fires when a pending message gets resolved. + /// + public event Action PendingMessageResolved; + + /// + /// An event that fires when a pending message gets removed. + /// + public event Action MessageRemoved; + + public bool ReadOnly => false; //todo not yet used. + + public override string ToString() => Name; + [JsonProperty(@"name")] public string Name; @@ -22,19 +71,16 @@ namespace osu.Game.Online.Chat public ChannelType Type; [JsonProperty(@"channel_id")] - public int Id; + public long Id; [JsonProperty(@"last_message_id")] public long? LastMessageId; - public readonly SortedList Messages = new SortedList(Comparer.Default); - - private readonly List pendingMessages = new List(); - + /// + /// Signalles if the current user joined this channel or not. Defaults to false. + /// public Bindable Joined = new Bindable(); - public bool ReadOnly => false; - public const int MAX_HISTORY = 300; [JsonConstructor] @@ -42,10 +88,10 @@ namespace osu.Game.Online.Chat { } - public event Action> NewMessagesArrived; - public event Action PendingMessageResolved; - public event Action MessageRemoved; - + /// + /// Adds the argument message as a local echo. When this local echo is resolved will get called. + /// + /// public void AddLocalEcho(LocalEchoMessage message) { pendingMessages.Add(message); @@ -54,8 +100,12 @@ namespace osu.Game.Online.Chat NewMessagesArrived?.Invoke(new[] { message }); } - public bool MessagesLoaded { get; private set; } + public bool MessagesLoaded; + /// + /// Adds new messages to the channel and purges old messages. Triggers the event. + /// + /// public void AddNewMessages(params Message[] messages) { messages = messages.Except(Messages).ToArray(); @@ -63,7 +113,6 @@ namespace osu.Game.Online.Chat if (messages.Length == 0) return; Messages.AddRange(messages); - MessagesLoaded = true; var maxMessageId = messages.Max(m => m.Id); if (maxMessageId > LastMessageId) @@ -74,14 +123,6 @@ namespace osu.Game.Online.Chat NewMessagesArrived?.Invoke(messages); } - private void purgeOldMessages() - { - // never purge local echos - int messageCount = Messages.Count - pendingMessages.Count; - if (messageCount > MAX_HISTORY) - Messages.RemoveRange(0, messageCount - MAX_HISTORY); - } - /// /// Replace or remove a message from the channel. /// @@ -101,17 +142,18 @@ namespace osu.Game.Online.Chat } if (Messages.Contains(final)) - { - // message already inserted, so let's throw away this update. - // we may want to handle this better in the future, but for the time being api requests are single-threaded so order is assumed. - MessageRemoved?.Invoke(echo); - return; - } + throw new InvalidOperationException("Attempted to add the same message again"); Messages.Add(final); PendingMessageResolved?.Invoke(echo, final); } - public override string ToString() => Name; + private void purgeOldMessages() + { + // never purge local echos + int messageCount = Messages.Count - pendingMessages.Count; + if (messageCount > MaxHistory) + Messages.RemoveRange(0, messageCount - MaxHistory); + } } } diff --git a/osu.Game/Online/Chat/ChannelManager.cs b/osu.Game/Online/Chat/ChannelManager.cs new file mode 100644 index 0000000000..29f971078b --- /dev/null +++ b/osu.Game/Online/Chat/ChannelManager.cs @@ -0,0 +1,443 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Configuration; +using osu.Framework.Graphics; +using osu.Framework.Logging; +using osu.Framework.Threading; +using osu.Game.Online.API; +using osu.Game.Online.API.Requests; +using osu.Game.Users; + +namespace osu.Game.Online.Chat +{ + /// + /// Manages everything channel related + /// + public class ChannelManager : Component, IOnlineComponent + { + /// + /// The channels the player joins on startup + /// + private readonly string[] defaultChannels = + { + @"#lazer", + @"#osu", + @"#lobby" + }; + + private readonly BindableCollection availableChannels = new BindableCollection(); + private readonly BindableCollection joinedChannels = new BindableCollection(); + + /// + /// The currently opened channel + /// + public Bindable CurrentChannel { get; } = new Bindable(); + + /// + /// The Channels the player has joined + /// + public IBindableCollection JoinedChannels => joinedChannels; + + /// + /// The channels available for the player to join + /// + public IBindableCollection AvailableChannels => availableChannels; + + private IAPIProvider api; + private ScheduledDelegate fetchMessagesScheduleder; + + public ChannelManager() + { + CurrentChannel.ValueChanged += currentChannelChanged; + } + + /// + /// Opens a channel or switches to the channel if already opened. + /// + /// If the name of the specifed channel was not found this exception will be thrown. + /// + public void OpenChannel(string name) + { + if (name == null) + throw new ArgumentNullException(nameof(name)); + + CurrentChannel.Value = AvailableChannels.FirstOrDefault(c => c.Name == name) ?? throw new ChannelNotFoundException(name); + } + + /// + /// Opens a new private channel. + /// + /// The user the private channel is opened with. + public void OpenPrivateChannel(User user) + { + if (user == null) + throw new ArgumentNullException(nameof(user)); + + CurrentChannel.Value = JoinedChannels.FirstOrDefault(c => c.Type == ChannelType.PM && c.Users.Count == 1 && c.Users.Any(u => u.Id == user.Id)) + ?? new Channel { Name = user.Username, Users = { user }, Type = ChannelType.PM }; + } + + private void currentChannelChanged(Channel channel) => JoinChannel(channel); + + /// + /// Ensure we run post actions in sequence, once at a time. + /// + private readonly Queue postQueue = new Queue(); + + /// + /// Posts a message to the currently opened channel. + /// + /// The message text that is going to be posted + /// Is true if the message is an action, e.g.: user is currently eating + public void PostMessage(string text, bool isAction = false) + { + if (CurrentChannel.Value == null) + return; + + var currentChannel = CurrentChannel.Value; + + void dequeueAndRun() + { + if (postQueue.Count > 0) + postQueue.Dequeue().Invoke(); + } + + postQueue.Enqueue(() => + { + if (!api.IsLoggedIn) + { + currentChannel.AddNewMessages(new ErrorMessage("Please sign in to participate in chat!")); + return; + } + + var message = new LocalEchoMessage + { + Sender = api.LocalUser.Value, + Timestamp = DateTimeOffset.Now, + ChannelId = CurrentChannel.Value.Id, + IsAction = isAction, + Content = text + }; + + currentChannel.AddLocalEcho(message); + + // 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) + { + var createNewPrivateMessageRequest = new CreateNewPrivateMessageRequest(currentChannel.Users.First(), message); + + createNewPrivateMessageRequest.Success += createRes => + { + currentChannel.Id = createRes.ChannelID; + currentChannel.ReplaceMessage(message, createRes.Message); + dequeueAndRun(); + }; + + createNewPrivateMessageRequest.Failure += exception => + { + Logger.Error(exception, "Posting message failed."); + currentChannel.ReplaceMessage(message, null); + dequeueAndRun(); + }; + + api.Queue(createNewPrivateMessageRequest); + return; + } + + var req = new PostMessageRequest(message); + + req.Success += m => + { + currentChannel.ReplaceMessage(message, m); + dequeueAndRun(); + }; + + req.Failure += exception => + { + Logger.Error(exception, "Posting message failed."); + currentChannel.ReplaceMessage(message, null); + dequeueAndRun(); + }; + + api.Queue(req); + }); + + // always run if the queue is empty + if (postQueue.Count == 1) + dequeueAndRun(); + } + + /// + /// Posts a command locally. Commands like /help will result in a help message written in the current channel. + /// + /// the text containing the command identifier and command parameters. + public void PostCommand(string text) + { + if (CurrentChannel.Value == null) + return; + + var parameters = text.Split(new[] { ' ' }, 2); + string command = parameters[0]; + string content = parameters.Length == 2 ? parameters[1] : string.Empty; + + switch (command) + { + case "me": + if (string.IsNullOrWhiteSpace(content)) + { + CurrentChannel.Value.AddNewMessages(new ErrorMessage("Usage: /me [action]")); + break; + } + + PostMessage(content, true); + break; + + case "help": + CurrentChannel.Value.AddNewMessages(new InfoMessage("Supported commands: /help, /me [action]")); + break; + + default: + CurrentChannel.Value.AddNewMessages(new ErrorMessage($@"""/{command}"" is not supported! For a list of supported commands see /help")); + break; + } + } + + private void handleChannelMessages(IEnumerable messages) + { + var channels = JoinedChannels.ToList(); + + foreach (var group in messages.GroupBy(m => m.ChannelId)) + channels.Find(c => c.Id == group.Key)?.AddNewMessages(group.ToArray()); + } + + private void initializeChannels() + { + var req = new ListChannelsRequest(); + + var joinDefaults = JoinedChannels.Count == 0; + + req.Success += channels => + { + foreach (var channel in channels) + { + var ch = getChannel(channel, addToAvailable: true); + + // join any channels classified as "defaults" + if (joinDefaults && defaultChannels.Any(c => c.Equals(channel.Name, StringComparison.OrdinalIgnoreCase))) + JoinChannel(ch); + } + }; + req.Failure += error => + { + Logger.Error(error, "Fetching channel list failed"); + initializeChannels(); + }; + + api.Queue(req); + } + + /// + /// Fetches inital messages of a channel + /// + /// TODO: remove this when the API supports returning initial fetch messages for more than one channel by specifying the last message id per channel instead of one last message id globally. + /// right now it caps out at 50 messages and therefore only returns one channel's worth of content. + /// + /// The channel + private void fetchInitalMessages(Channel channel) + { + if (channel.Id <= 0) return; + + var fetchInitialMsgReq = new GetMessagesRequest(channel); + fetchInitialMsgReq.Success += messages => + { + handleChannelMessages(messages); + channel.MessagesLoaded = true; // this will mark the channel as having received messages even if there were none. + }; + + api.Queue(fetchInitialMsgReq); + } + + /// + /// Find an existing channel instance for the provided channel. Lookup is performed basd on ID. + /// The provided channel may be used if an existing instance is not found. + /// + /// A candidate channel to be used for lookup or permanently on lookup failure. + /// Whether the channel should be added to if not already. + /// Whether the channel should be added to if not already. + /// The found channel. + private Channel getChannel(Channel lookup, bool addToAvailable = false, bool addToJoined = false) + { + Channel found = null; + + bool lookupCondition(Channel ch) => lookup.Id > 0 ? ch.Id == lookup.Id : lookup.Name == ch.Name; + + var available = AvailableChannels.FirstOrDefault(lookupCondition); + if (available != null) + found = available; + + var joined = JoinedChannels.FirstOrDefault(lookupCondition); + if (found == null && joined != null) + found = joined; + + if (found == null) + { + found = lookup; + + // if we're using a channel object from the server, we want to remove ourselves from the users list. + // this is because we check the first user in the channel to display a name/icon on tabs for now. + var foundSelf = found.Users.FirstOrDefault(u => u.Id == api.LocalUser.Value.Id); + if (foundSelf != null) + found.Users.Remove(foundSelf); + } + + if (joined == null && addToJoined) joinedChannels.Add(found); + if (available == null && addToAvailable) availableChannels.Add(found); + + return found; + } + + /// + /// Joins a channel if it has not already been joined. + /// + /// The channel to join. + /// Whether the channel has already been joined server-side. Will skip a join request. + /// The joined channel. Note that this may not match the parameter channel as it is a backed object. + public Channel JoinChannel(Channel channel, bool alreadyJoined = false) + { + if (channel == null) return null; + + channel = getChannel(channel, addToJoined: true); + + // ensure we are joined to the channel + if (!channel.Joined.Value) + { + if (alreadyJoined) + channel.Joined.Value = true; + else + { + switch (channel.Type) + { + case ChannelType.Public: + var req = new JoinChannelRequest(channel, api.LocalUser); + req.Success += () => JoinChannel(channel, true); + req.Failure += ex => LeaveChannel(channel); + api.Queue(req); + return channel; + } + } + } + + if (CurrentChannel.Value == null) + CurrentChannel.Value = channel; + + if (!channel.MessagesLoaded) + { + // let's fetch a small number of messages to bring us up-to-date with the backlog. + fetchInitalMessages(channel); + } + + return channel; + } + + public void LeaveChannel(Channel channel) + { + if (channel == null) return; + + if (channel == CurrentChannel.Value) + CurrentChannel.Value = null; + + joinedChannels.Remove(channel); + + if (channel.Joined.Value) + { + api.Queue(new LeaveChannelRequest(channel, api.LocalUser)); + channel.Joined.Value = false; + } + } + + public void APIStateChanged(APIAccess api, APIState state) + { + switch (state) + { + case APIState.Online: + fetchUpdates(); + break; + default: + fetchMessagesScheduleder?.Cancel(); + fetchMessagesScheduleder = null; + break; + } + } + + private long lastMessageId; + private const int update_poll_interval = 1000; + + private bool channelsInitialised; + + private void fetchUpdates() + { + fetchMessagesScheduleder?.Cancel(); + fetchMessagesScheduleder = Scheduler.AddDelayed(() => + { + var fetchReq = new GetUpdatesRequest(lastMessageId); + + fetchReq.Success += updates => + { + if (updates?.Presence != null) + { + foreach (var channel in updates.Presence) + { + // we received this from the server so should mark the channel already joined. + JoinChannel(channel, true); + } + + //todo: handle left channels + + handleChannelMessages(updates.Messages); + + foreach (var group in updates.Messages.GroupBy(m => m.ChannelId)) + JoinedChannels.FirstOrDefault(c => c.Id == group.Key)?.AddNewMessages(group.ToArray()); + + lastMessageId = updates.Messages.LastOrDefault()?.Id ?? lastMessageId; + } + + if (!channelsInitialised) + { + channelsInitialised = true; + // we want this to run after the first presence so we can see if the user is in any channels already. + initializeChannels(); + } + + fetchUpdates(); + }; + + fetchReq.Failure += delegate { fetchUpdates(); }; + + api.Queue(fetchReq); + }, update_poll_interval); + } + + [BackgroundDependencyLoader] + private void load(IAPIProvider api) + { + this.api = api; + api.Register(this); + } + } + + /// + /// An exception thrown when a channel could not been found. + /// + public class ChannelNotFoundException : Exception + { + public ChannelNotFoundException(string channelName) + : base($"A channel with the name {channelName} could not be found.") + { + } + } +} diff --git a/osu.Game/Online/Chat/ChannelType.cs b/osu.Game/Online/Chat/ChannelType.cs index 4ac0a99fc6..c8b9463ded 100644 --- a/osu.Game/Online/Chat/ChannelType.cs +++ b/osu.Game/Online/Chat/ChannelType.cs @@ -5,7 +5,12 @@ namespace osu.Game.Online.Chat { public enum ChannelType { + Public, + Private, + Multiplayer, + Spectator, + Temporary, PM, - Public + Group, } } diff --git a/osu.Game/Online/Chat/DrawableLinkCompiler.cs b/osu.Game/Online/Chat/DrawableLinkCompiler.cs index 0148d1d2c3..de017baf35 100644 --- a/osu.Game/Online/Chat/DrawableLinkCompiler.cs +++ b/osu.Game/Online/Chat/DrawableLinkCompiler.cs @@ -10,7 +10,7 @@ using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; -using OpenTK; +using osuTK; namespace osu.Game.Online.Chat { diff --git a/osu.Game/Online/Chat/ExternalLinkOpener.cs b/osu.Game/Online/Chat/ExternalLinkOpener.cs new file mode 100644 index 0000000000..d8b8adbbad --- /dev/null +++ b/osu.Game/Online/Chat/ExternalLinkOpener.cs @@ -0,0 +1,36 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Allocation; +using osu.Framework.Configuration; +using osu.Framework.Graphics; +using osu.Framework.Platform; +using osu.Game.Configuration; +using osu.Game.Overlays; +using osu.Game.Overlays.Chat; + +namespace osu.Game.Online.Chat +{ + public class ExternalLinkOpener : Component + { + private GameHost host; + private DialogOverlay dialogOverlay; + private Bindable externalLinkWarning; + + [BackgroundDependencyLoader(true)] + private void load(GameHost host, DialogOverlay dialogOverlay, OsuConfigManager config) + { + this.host = host; + this.dialogOverlay = dialogOverlay; + externalLinkWarning = config.GetBindable(OsuSetting.ExternalLinkWarning); + } + + public void OpenUrlExternally(string url) + { + if (externalLinkWarning) + dialogOverlay.Push(new ExternalLinkDialog(url, () => host.OpenUrlExternally(url))); + else + host.OpenUrlExternally(url); + } + } +} diff --git a/osu.Game/Online/Chat/Message.cs b/osu.Game/Online/Chat/Message.cs index 65e0415cd3..3f0f352ac7 100644 --- a/osu.Game/Online/Chat/Message.cs +++ b/osu.Game/Online/Chat/Message.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.ComponentModel; using Newtonsoft.Json; using osu.Game.Users; @@ -16,10 +15,10 @@ namespace osu.Game.Online.Chat //todo: this should be inside sender. [JsonProperty(@"sender_id")] - public int UserId; + public long UserId; [JsonProperty(@"channel_id")] - public int ChannelId; + public long ChannelId; [JsonProperty(@"is_action")] public bool IsAction; @@ -69,12 +68,4 @@ namespace osu.Game.Online.Chat // ReSharper disable once ImpureMethodCallOnReadonlyValueField public override int GetHashCode() => Id.GetHashCode(); } - - public enum TargetType - { - [Description(@"channel")] - Channel, - [Description(@"user")] - User - } } diff --git a/osu.Game/Online/Multiplayer/GameType.cs b/osu.Game/Online/Multiplayer/GameType.cs index 571d3df681..8d39e8f59d 100644 --- a/osu.Game/Online/Multiplayer/GameType.cs +++ b/osu.Game/Online/Multiplayer/GameType.cs @@ -1,8 +1,8 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using OpenTK; -using OpenTK.Graphics; +using osuTK; +using osuTK.Graphics; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -14,6 +14,9 @@ namespace osu.Game.Online.Multiplayer { public abstract string Name { get; } public abstract Drawable GetIcon(OsuColour colours, float size); + + public override int GetHashCode() => GetType().GetHashCode(); + public override bool Equals(object obj) => GetType() == obj?.GetType(); } public class GameTypeTag : GameType diff --git a/osu.Game/Online/Multiplayer/RoomStatus.cs b/osu.Game/Online/Multiplayer/RoomStatus.cs index 6b82e5f059..7c6e71a360 100644 --- a/osu.Game/Online/Multiplayer/RoomStatus.cs +++ b/osu.Game/Online/Multiplayer/RoomStatus.cs @@ -1,7 +1,7 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using OpenTK.Graphics; +using osuTK.Graphics; using osu.Game.Graphics; namespace osu.Game.Online.Multiplayer diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index b75a37e9df..cd40d4793a 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -14,7 +14,7 @@ using osu.Framework.Allocation; using osu.Game.Overlays.Toolbar; using osu.Game.Screens; using osu.Game.Screens.Menu; -using OpenTK; +using osuTK; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -26,15 +26,17 @@ using osu.Framework.Platform; using osu.Framework.Threading; using osu.Game.Beatmaps; using osu.Game.Graphics; -using osu.Game.Rulesets.Scoring; +using osu.Game.Input; using osu.Game.Overlays.Notifications; using osu.Game.Rulesets; using osu.Game.Screens.Play; using osu.Game.Input.Bindings; +using osu.Game.Online.Chat; using osu.Game.Rulesets.Mods; using osu.Game.Skinning; -using OpenTK.Graphics; +using osuTK.Graphics; using osu.Game.Overlays.Volume; +using osu.Game.Scoring; using osu.Game.Screens.Select; using osu.Game.Utils; using LogLevel = osu.Framework.Logging.LogLevel; @@ -49,7 +51,9 @@ namespace osu.Game { public Toolbar Toolbar; - private ChatOverlay chat; + private ChatOverlay chatOverlay; + + private ChannelManager channelManager; private MusicController musicController; @@ -85,6 +89,8 @@ namespace osu.Game public float ToolbarOffset => Toolbar.Position.Y + Toolbar.DrawHeight; + private IdleTracker idleTracker; + public readonly Bindable OverlayActivationMode = new Bindable(); private OsuScreen screenStack; @@ -142,7 +148,7 @@ namespace osu.Game { this.frameworkConfig = frameworkConfig; - ScoreStore.ScoreImported += score => Schedule(() => LoadScore(score)); + ScoreManager.ItemAdded += (score, _, silent) => Schedule(() => LoadScore(score, silent)); if (!Host.IsPrimaryInstance) { @@ -178,13 +184,10 @@ namespace osu.Game LocalConfig.BindWith(OsuSetting.VolumeInactive, inactiveVolumeAdjust); } - private ScheduledDelegate scoreLoad; + private ExternalLinkOpener externalLinkOpener; + public void OpenUrlExternally(string url) => externalLinkOpener.OpenUrlExternally(url); - /// - /// Open chat to a channel matching the provided name, if present. - /// - /// The name of the channel. - public void OpenChannel(string channelName) => chat.OpenChannel(chat.AvailableChannels.Find(c => c.Name == channelName)); + private ScheduledDelegate scoreLoad; /// /// Show a beatmap set as an overlay. @@ -245,41 +248,69 @@ namespace osu.Game /// The beatmap to show. public void ShowBeatmap(int beatmapId) => beatmapSetOverlay.FetchAndShowBeatmap(beatmapId); - protected void LoadScore(Score score) + protected void LoadScore(ScoreInfo score, bool silent) { + if (silent) + return; + scoreLoad?.Cancel(); var menu = intro.ChildScreen; if (menu == null) { - scoreLoad = Schedule(() => LoadScore(score)); + scoreLoad = Schedule(() => LoadScore(score, false)); return; } - if (!menu.IsCurrentScreen) + var databasedScore = ScoreManager.GetScore(score); + var databasedScoreInfo = databasedScore.ScoreInfo; + if (databasedScore.Replay == null) { - menu.MakeCurrent(); - this.Delay(500).Schedule(() => LoadScore(score), out scoreLoad); + Logger.Log("The loaded score has no replay data.", LoggingTarget.Information); return; } - if (score.Beatmap == null) + var databasedBeatmap = BeatmapManager.QueryBeatmap(b => b.ID == databasedScoreInfo.Beatmap.ID); + if (databasedBeatmap == null) + { + Logger.Log("Tried to load a score for a beatmap we don't have!", LoggingTarget.Information); + return; + } + + if (!currentScreen.AllowExternalScreenChange) { notifications.Post(new SimpleNotification { - Text = @"Tried to load a score for a beatmap we don't have!", - Icon = FontAwesome.fa_life_saver, + Text = $"Click here to watch {databasedScoreInfo.User.Username} on {databasedScoreInfo.Beatmap}", + Activated = () => + { + loadScore(); + return true; + } }); + return; } - ruleset.Value = score.Ruleset; + loadScore(); - Beatmap.Value = BeatmapManager.GetWorkingBeatmap(score.Beatmap); - Beatmap.Value.Mods.Value = score.Mods; + void loadScore() + { + if (!menu.IsCurrentScreen) + { + menu.MakeCurrent(); + this.Delay(500).Schedule(loadScore, out scoreLoad); + return; + } - menu.Push(new PlayerLoader(new ReplayPlayer(score.Replay))); + ruleset.Value = databasedScoreInfo.Ruleset; + + Beatmap.Value = BeatmapManager.GetWorkingBeatmap(databasedBeatmap); + Beatmap.Value.Mods.Value = databasedScoreInfo.Mods; + + currentScreen.Push(new PlayerLoader(new ReplayPlayer(databasedScore))); + } } protected override void Dispose(bool isDisposing) @@ -316,6 +347,7 @@ namespace osu.Game }, mainContent = new Container { RelativeSizeAxes = Axes.Both }, overlayContent = new Container { RelativeSizeAxes = Axes.Both, Depth = float.MinValue }, + idleTracker = new IdleTracker(6000) }); loadComponentSingleFile(screenStack = new Loader(), d => @@ -343,7 +375,8 @@ namespace osu.Game //overlay elements loadComponentSingleFile(direct = new DirectOverlay { Depth = -1 }, mainContent.Add); loadComponentSingleFile(social = new SocialOverlay { Depth = -1 }, mainContent.Add); - loadComponentSingleFile(chat = new ChatOverlay { Depth = -1 }, mainContent.Add); + loadComponentSingleFile(channelManager = new ChannelManager(), AddInternal); + loadComponentSingleFile(chatOverlay = new ChatOverlay { Depth = -1 }, mainContent.Add); loadComponentSingleFile(settings = new MainSettings { GetToolbarHeight = () => ToolbarOffset, @@ -372,17 +405,21 @@ namespace osu.Game Depth = -6, }, overlayContent.Add); + dependencies.Cache(idleTracker); dependencies.Cache(settings); dependencies.Cache(onscreenDisplay); dependencies.Cache(social); dependencies.Cache(direct); - dependencies.Cache(chat); + dependencies.Cache(chatOverlay); + dependencies.Cache(channelManager); dependencies.Cache(userProfile); dependencies.Cache(musicController); dependencies.Cache(beatmapSetOverlay); dependencies.Cache(notifications); dependencies.Cache(dialogOverlay); + Add(externalLinkOpener = new ExternalLinkOpener()); + var singleDisplaySideOverlays = new OverlayContainer[] { settings, notifications }; overlays.AddRange(singleDisplaySideOverlays); @@ -409,7 +446,7 @@ namespace osu.Game } // ensure only one of these overlays are open at once. - var singleDisplayOverlays = new OverlayContainer[] { chat, social, direct }; + var singleDisplayOverlays = new OverlayContainer[] { chatOverlay, social, direct }; overlays.AddRange(singleDisplayOverlays); foreach (var overlay in singleDisplayOverlays) @@ -534,7 +571,7 @@ namespace osu.Game switch (action) { case GlobalAction.ToggleChat: - chat.ToggleVisibility(); + chatOverlay.ToggleVisibility(); return true; case GlobalAction.ToggleSocial: social.ToggleVisibility(); diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 73c970ce5d..2729676504 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -28,9 +28,9 @@ using osu.Game.Input; using osu.Game.Input.Bindings; using osu.Game.IO; using osu.Game.Rulesets; -using osu.Game.Rulesets.Scoring; +using osu.Game.Scoring; using osu.Game.Skinning; -using OpenTK.Input; +using osuTK.Input; using DebugUtils = osu.Game.Utils.DebugUtils; namespace osu.Game @@ -46,14 +46,14 @@ namespace osu.Game protected BeatmapManager BeatmapManager; + protected ScoreManager ScoreManager; + protected SkinManager SkinManager; protected RulesetStore RulesetStore; protected FileStore FileStore; - protected ScoreStore ScoreStore; - protected KeyBindingStore KeyBindingStore; protected SettingsStore SettingsStore; @@ -154,14 +154,14 @@ namespace osu.Game dependencies.Cache(RulesetStore = new RulesetStore(contextFactory)); dependencies.Cache(FileStore = new FileStore(contextFactory, Host.Storage)); dependencies.Cache(BeatmapManager = new BeatmapManager(Host.Storage, contextFactory, RulesetStore, api, Audio, Host)); - dependencies.Cache(ScoreStore = new ScoreStore(contextFactory, Host, BeatmapManager, RulesetStore)); + dependencies.Cache(ScoreManager = new ScoreManager(RulesetStore, BeatmapManager, Host.Storage, contextFactory, Host)); dependencies.Cache(KeyBindingStore = new KeyBindingStore(contextFactory, RulesetStore)); dependencies.Cache(SettingsStore = new SettingsStore(contextFactory)); dependencies.Cache(RulesetConfigCache = new RulesetConfigCache(SettingsStore)); dependencies.Cache(new OsuColour()); fileImporters.Add(BeatmapManager); - fileImporters.Add(ScoreStore); + fileImporters.Add(ScoreManager); fileImporters.Add(SkinManager); var defaultBeatmap = new DummyWorkingBeatmap(this); diff --git a/osu.Game/Overlays/BeatmapSet/AuthorInfo.cs b/osu.Game/Overlays/BeatmapSet/AuthorInfo.cs index 398518ef2b..c2f03a4b66 100644 --- a/osu.Game/Overlays/BeatmapSet/AuthorInfo.cs +++ b/osu.Game/Overlays/BeatmapSet/AuthorInfo.cs @@ -7,8 +7,8 @@ using osu.Framework.Graphics.Containers; using osu.Game.Beatmaps; using osu.Game.Graphics.Sprites; using osu.Game.Users; -using OpenTK; -using OpenTK.Graphics; +using osuTK; +using osuTK.Graphics; using osu.Framework.Allocation; using osu.Game.Graphics.Containers; using osu.Framework.Graphics.Cursor; diff --git a/osu.Game/Overlays/BeatmapSet/BasicStats.cs b/osu.Game/Overlays/BeatmapSet/BasicStats.cs index a7b6b16dcc..26402b6e37 100644 --- a/osu.Game/Overlays/BeatmapSet/BasicStats.cs +++ b/osu.Game/Overlays/BeatmapSet/BasicStats.cs @@ -9,7 +9,7 @@ using osu.Framework.Graphics.Cursor; using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; -using OpenTK; +using osuTK; namespace osu.Game.Overlays.BeatmapSet { diff --git a/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs b/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs index bd9d65ccd8..64a8a1062c 100644 --- a/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs +++ b/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs @@ -16,8 +16,8 @@ using osu.Game.Beatmaps.Drawables; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; -using OpenTK; -using OpenTK.Graphics; +using osuTK; +using osuTK.Graphics; namespace osu.Game.Overlays.BeatmapSet { diff --git a/osu.Game/Overlays/BeatmapSet/Buttons/DownloadButton.cs b/osu.Game/Overlays/BeatmapSet/Buttons/DownloadButton.cs index f3e49f68b1..8406dada44 100644 --- a/osu.Game/Overlays/BeatmapSet/Buttons/DownloadButton.cs +++ b/osu.Game/Overlays/BeatmapSet/Buttons/DownloadButton.cs @@ -12,8 +12,8 @@ using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Online.API; using osu.Game.Users; -using OpenTK; -using OpenTK.Graphics; +using osuTK; +using osuTK.Graphics; namespace osu.Game.Overlays.BeatmapSet.Buttons { diff --git a/osu.Game/Overlays/BeatmapSet/Buttons/FavouriteButton.cs b/osu.Game/Overlays/BeatmapSet/Buttons/FavouriteButton.cs index 3821c96369..929f89482c 100644 --- a/osu.Game/Overlays/BeatmapSet/Buttons/FavouriteButton.cs +++ b/osu.Game/Overlays/BeatmapSet/Buttons/FavouriteButton.cs @@ -8,7 +8,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; using osu.Game.Graphics.Backgrounds; -using OpenTK; +using osuTK; namespace osu.Game.Overlays.BeatmapSet.Buttons { diff --git a/osu.Game/Overlays/BeatmapSet/Buttons/PreviewButton.cs b/osu.Game/Overlays/BeatmapSet/Buttons/PreviewButton.cs index 1988b7d222..b971403443 100644 --- a/osu.Game/Overlays/BeatmapSet/Buttons/PreviewButton.cs +++ b/osu.Game/Overlays/BeatmapSet/Buttons/PreviewButton.cs @@ -13,8 +13,8 @@ using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Overlays.Direct; -using OpenTK; -using OpenTK.Graphics; +using osuTK; +using osuTK.Graphics; namespace osu.Game.Overlays.BeatmapSet.Buttons { diff --git a/osu.Game/Overlays/BeatmapSet/Details.cs b/osu.Game/Overlays/BeatmapSet/Details.cs index ccd0fa04ab..709d163eaa 100644 --- a/osu.Game/Overlays/BeatmapSet/Details.cs +++ b/osu.Game/Overlays/BeatmapSet/Details.cs @@ -9,8 +9,8 @@ using osu.Framework.Graphics.Shapes; using osu.Game.Beatmaps; using osu.Game.Overlays.BeatmapSet.Buttons; using osu.Game.Screens.Select.Details; -using OpenTK; -using OpenTK.Graphics; +using osuTK; +using osuTK.Graphics; namespace osu.Game.Overlays.BeatmapSet { diff --git a/osu.Game/Overlays/BeatmapSet/Header.cs b/osu.Game/Overlays/BeatmapSet/Header.cs index 6f01fae92d..d353522d8d 100644 --- a/osu.Game/Overlays/BeatmapSet/Header.cs +++ b/osu.Game/Overlays/BeatmapSet/Header.cs @@ -13,8 +13,8 @@ using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Overlays.BeatmapSet.Buttons; -using OpenTK; -using OpenTK.Graphics; +using osuTK; +using osuTK.Graphics; namespace osu.Game.Overlays.BeatmapSet { diff --git a/osu.Game/Overlays/BeatmapSet/Info.cs b/osu.Game/Overlays/BeatmapSet/Info.cs index 53216ad666..e985da0eb7 100644 --- a/osu.Game/Overlays/BeatmapSet/Info.cs +++ b/osu.Game/Overlays/BeatmapSet/Info.cs @@ -10,8 +10,8 @@ using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; -using OpenTK; -using OpenTK.Graphics; +using osuTK; +using osuTK.Graphics; namespace osu.Game.Overlays.BeatmapSet { diff --git a/osu.Game/Overlays/BeatmapSet/Scores/DrawableScore.cs b/osu.Game/Overlays/BeatmapSet/Scores/DrawableScore.cs index 309d75f60a..f643e130aa 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/DrawableScore.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/DrawableScore.cs @@ -1,7 +1,7 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using OpenTK; +using osuTK; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -26,7 +26,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores private readonly Box background; - public DrawableScore(int index, APIScore score) + public DrawableScore(int index, APIScoreInfo score) { ScoreModsContainer modsContainer; diff --git a/osu.Game/Overlays/BeatmapSet/Scores/DrawableTopScore.cs b/osu.Game/Overlays/BeatmapSet/Scores/DrawableTopScore.cs index d954b48b86..643839fa88 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/DrawableTopScore.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/DrawableTopScore.cs @@ -1,8 +1,8 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using OpenTK; -using OpenTK.Graphics; +using osuTK; +using osuTK.Graphics; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; @@ -16,6 +16,7 @@ using osu.Game.Overlays.Profile.Sections.Ranks; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; +using osu.Game.Scoring; using osu.Game.Screens.Select.Leaderboards; using osu.Game.Users; @@ -42,8 +43,8 @@ namespace osu.Game.Overlays.BeatmapSet.Scores private readonly InfoColumn statistics; private readonly ScoreModsContainer modsContainer; - private APIScore score; - public APIScore Score + private APIScoreInfo score; + public APIScoreInfo Score { get { return score; } set diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs index 60811d8b12..e338d2f90f 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs @@ -1,7 +1,7 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using OpenTK; +using osuTK; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Graphics.UserInterface; @@ -29,10 +29,10 @@ namespace osu.Game.Overlays.BeatmapSet.Scores set => loadingAnimation.FadeTo(value ? 1 : 0, fade_duration); } - private IEnumerable scores; + private IEnumerable scores; private BeatmapInfo beatmap; - public IEnumerable Scores + public IEnumerable Scores { get { return scores; } set diff --git a/osu.Game/Overlays/BeatmapSetOverlay.cs b/osu.Game/Overlays/BeatmapSetOverlay.cs index 23c8ec3aab..6294851415 100644 --- a/osu.Game/Overlays/BeatmapSetOverlay.cs +++ b/osu.Game/Overlays/BeatmapSetOverlay.cs @@ -16,8 +16,8 @@ using osu.Game.Online.API.Requests; using osu.Game.Overlays.BeatmapSet; using osu.Game.Overlays.BeatmapSet.Scores; using osu.Game.Rulesets; -using OpenTK; -using OpenTK.Graphics; +using osuTK; +using osuTK.Graphics; namespace osu.Game.Overlays { @@ -139,7 +139,7 @@ namespace osu.Game.Overlays var req = new GetBeatmapSetRequest(beatmapId, BeatmapSetLookupType.BeatmapId); req.Success += res => { - ShowBeatmapSet(res.ToBeatmapSet(rulesets)); + BeatmapSet = res.ToBeatmapSet(rulesets); header.Picker.Beatmap.Value = header.BeatmapSet.Beatmaps.First(b => b.OnlineBeatmapID == beatmapId); }; api.Queue(req); @@ -150,7 +150,7 @@ namespace osu.Game.Overlays { BeatmapSet = null; var req = new GetBeatmapSetRequest(beatmapSetId); - req.Success += res => ShowBeatmapSet(res.ToBeatmapSet(rulesets)); + req.Success += res => BeatmapSet = res.ToBeatmapSet(rulesets); api.Queue(req); Show(); } diff --git a/osu.Game/Overlays/Chat/ChatLine.cs b/osu.Game/Overlays/Chat/ChatLine.cs index 1fccaeb123..c11de48430 100644 --- a/osu.Game/Overlays/Chat/ChatLine.cs +++ b/osu.Game/Overlays/Chat/ChatLine.cs @@ -1,9 +1,10 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using System; using System.Linq; -using OpenTK; -using OpenTK.Graphics; +using osuTK; +using osuTK.Graphics; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; @@ -81,6 +82,8 @@ namespace osu.Game.Overlays.Chat Padding = new MarginPadding { Left = padding, Right = padding }; } + private ChannelManager chatManager; + private Message message; private OsuSpriteText username; private LinkFlowContainer contentFlow; @@ -104,9 +107,9 @@ namespace osu.Game.Overlays.Chat } [BackgroundDependencyLoader(true)] - private void load(OsuColour colours, ChatOverlay chat) + private void load(OsuColour colours, ChannelManager chatManager) { - this.chat = chat; + this.chatManager = chatManager; customUsernameColour = colours.ChatBlue; } @@ -215,8 +218,6 @@ namespace osu.Game.Overlays.Chat FinishTransforms(true); } - private ChatOverlay chat; - private void updateMessageContent() { this.FadeTo(message is LocalEchoMessage ? 0.4f : 1.0f, 500, Easing.OutQuint); @@ -226,7 +227,7 @@ namespace osu.Game.Overlays.Chat username.Text = $@"{message.Sender.Username}" + (senderHasBackground || message.IsAction ? "" : ":"); // remove non-existent channels from the link list - message.Links.RemoveAll(link => link.Action == LinkAction.OpenChannel && chat?.AvailableChannels.Any(c => c.Name == link.Argument) != true); + message.Links.RemoveAll(link => link.Action == LinkAction.OpenChannel && chatManager?.AvailableChannels.Any(c => c.Name == link.Argument) != true); contentFlow.Clear(); contentFlow.AddLinks(message.DisplayContent, message.Links); @@ -236,20 +237,24 @@ namespace osu.Game.Overlays.Chat { private readonly User sender; + private Action startChatAction; + public MessageSender(User sender) { this.sender = sender; } [BackgroundDependencyLoader(true)] - private void load(UserProfileOverlay profile) + private void load(UserProfileOverlay profile, ChannelManager chatManager) { Action = () => profile?.ShowUser(sender); + startChatAction = () => chatManager?.OpenPrivateChannel(sender); } public MenuItem[] ContextMenuItems => new MenuItem[] { new OsuMenuItem("View Profile", MenuItemType.Highlighted, Action), + new OsuMenuItem("Start Chat", MenuItemType.Standard, startChatAction), }; } } diff --git a/osu.Game/Overlays/Chat/ChatTabControl.cs b/osu.Game/Overlays/Chat/ChatTabControl.cs deleted file mode 100644 index ec4fd85901..0000000000 --- a/osu.Game/Overlays/Chat/ChatTabControl.cs +++ /dev/null @@ -1,348 +0,0 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Allocation; -using osu.Framework.Extensions.Color4Extensions; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Graphics.Sprites; -using osu.Framework.Graphics.UserInterface; -using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; -using osu.Game.Graphics.UserInterface; -using osu.Game.Online.Chat; -using OpenTK; -using OpenTK.Input; -using OpenTK.Graphics; -using osu.Framework.Configuration; -using System; -using osu.Framework.Input.Events; -using osu.Game.Graphics.Containers; - -namespace osu.Game.Overlays.Chat -{ - public class ChatTabControl : OsuTabControl - { - private const float shear_width = 10; - - public Action OnRequestLeave; - - public readonly Bindable ChannelSelectorActive = new Bindable(); - - private readonly ChannelTabItem.ChannelSelectorTabItem selectorTab; - - public ChatTabControl() - { - TabContainer.Margin = new MarginPadding { Left = 50 }; - TabContainer.Spacing = new Vector2(-shear_width, 0); - TabContainer.Masking = false; - - AddInternal(new SpriteIcon - { - Icon = FontAwesome.fa_comments, - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Size = new Vector2(20), - Margin = new MarginPadding(10), - }); - - AddTabItem(selectorTab = new ChannelTabItem.ChannelSelectorTabItem(new Channel { Name = "+" })); - - ChannelSelectorActive.BindTo(selectorTab.Active); - } - - protected override void AddTabItem(TabItem item, bool addToDropdown = true) - { - if (item != selectorTab && TabContainer.GetLayoutPosition(selectorTab) < float.MaxValue) - // performTabSort might've made selectorTab's position wonky, fix it - TabContainer.SetLayoutPosition(selectorTab, float.MaxValue); - - base.AddTabItem(item, addToDropdown); - - if (SelectedTab == null) - SelectTab(item); - } - - protected override TabItem CreateTabItem(Channel value) => new ChannelTabItem(value) { OnRequestClose = tabCloseRequested }; - - protected override void SelectTab(TabItem tab) - { - if (tab is ChannelTabItem.ChannelSelectorTabItem) - { - tab.Active.Toggle(); - return; - } - - selectorTab.Active.Value = false; - - base.SelectTab(tab); - } - - private void tabCloseRequested(TabItem tab) - { - int totalTabs = TabContainer.Count - 1; // account for selectorTab - int currentIndex = MathHelper.Clamp(TabContainer.IndexOf(tab), 1, totalTabs); - - if (tab == SelectedTab && totalTabs > 1) - // Select the tab after tab-to-be-removed's index, or the tab before if current == last - SelectTab(TabContainer[currentIndex == totalTabs ? currentIndex - 1 : currentIndex + 1]); - else if (totalTabs == 1 && !selectorTab.Active) - // Open channel selection overlay if all channel tabs will be closed after removing this tab - SelectTab(selectorTab); - - OnRequestLeave?.Invoke(tab.Value); - } - - private class ChannelTabItem : TabItem - { - private Color4 backgroundInactive; - private Color4 backgroundHover; - private Color4 backgroundActive; - - public override bool IsRemovable => !Pinned; - - private readonly SpriteText text; - private readonly SpriteText textBold; - private readonly ClickableContainer closeButton; - private readonly Box box; - private readonly Box highlightBox; - private readonly SpriteIcon icon; - - public Action OnRequestClose; - - private void updateState() - { - if (Active) - fadeActive(); - else - fadeInactive(); - } - - private const float transition_length = 400; - - private void fadeActive() - { - this.ResizeTo(new Vector2(Width, 1.1f), transition_length, Easing.OutQuint); - - box.FadeColour(backgroundActive, transition_length, Easing.OutQuint); - highlightBox.FadeIn(transition_length, Easing.OutQuint); - - text.FadeOut(transition_length, Easing.OutQuint); - textBold.FadeIn(transition_length, Easing.OutQuint); - } - - private void fadeInactive() - { - this.ResizeTo(new Vector2(Width, 1), transition_length, Easing.OutQuint); - - box.FadeColour(backgroundInactive, transition_length, Easing.OutQuint); - highlightBox.FadeOut(transition_length, Easing.OutQuint); - - text.FadeIn(transition_length, Easing.OutQuint); - textBold.FadeOut(transition_length, Easing.OutQuint); - } - - protected override bool OnMouseUp(MouseUpEvent e) - { - if (e.Button == MouseButton.Middle) - { - closeButton.Action(); - return true; - } - - return false; - } - - protected override bool OnHover(HoverEvent e) - { - if (IsRemovable) - closeButton.FadeIn(200, Easing.OutQuint); - - if (!Active) - box.FadeColour(backgroundHover, transition_length, Easing.OutQuint); - return true; - } - - protected override void OnHoverLost(HoverLostEvent e) - { - closeButton.FadeOut(200, Easing.OutQuint); - updateState(); - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - backgroundActive = colours.ChatBlue; - backgroundInactive = colours.Gray4; - backgroundHover = colours.Gray7; - - highlightBox.Colour = colours.Yellow; - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - updateState(); - } - - public ChannelTabItem(Channel value) : base(value) - { - Width = 150; - - RelativeSizeAxes = Axes.Y; - - Anchor = Anchor.BottomLeft; - Origin = Anchor.BottomLeft; - - Shear = new Vector2(shear_width / ChatOverlay.TAB_AREA_HEIGHT, 0); - - Masking = true; - EdgeEffect = new EdgeEffectParameters - { - Type = EdgeEffectType.Shadow, - Radius = 10, - Colour = Color4.Black.Opacity(0.2f), - }; - - Children = new Drawable[] - { - box = new Box - { - EdgeSmoothness = new Vector2(1, 0), - RelativeSizeAxes = Axes.Both, - }, - highlightBox = new Box - { - Width = 5, - Alpha = 0, - Anchor = Anchor.BottomRight, - Origin = Anchor.BottomRight, - EdgeSmoothness = new Vector2(1, 0), - RelativeSizeAxes = Axes.Y, - }, - new Container - { - Shear = new Vector2(-shear_width / ChatOverlay.TAB_AREA_HEIGHT, 0), - RelativeSizeAxes = Axes.Both, - Children = new Drawable[] - { - icon = new SpriteIcon - { - Icon = FontAwesome.fa_hashtag, - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Colour = Color4.Black, - X = -10, - Alpha = 0.2f, - Size = new Vector2(ChatOverlay.TAB_AREA_HEIGHT), - }, - text = new OsuSpriteText - { - Margin = new MarginPadding(5), - Origin = Anchor.CentreLeft, - Anchor = Anchor.CentreLeft, - Text = value.ToString(), - TextSize = 18, - }, - textBold = new OsuSpriteText - { - Alpha = 0, - Margin = new MarginPadding(5), - Origin = Anchor.CentreLeft, - Anchor = Anchor.CentreLeft, - Text = value.ToString(), - Font = @"Exo2.0-Bold", - TextSize = 18, - }, - closeButton = new CloseButton - { - Alpha = 0, - Margin = new MarginPadding { Right = 20 }, - Origin = Anchor.CentreRight, - Anchor = Anchor.CentreRight, - Action = delegate - { - if (IsRemovable) OnRequestClose?.Invoke(this); - }, - }, - }, - }, - }; - } - - public class CloseButton : OsuClickableContainer - { - private readonly SpriteIcon icon; - - public CloseButton() - { - Size = new Vector2(20); - - Child = icon = new SpriteIcon - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Scale = new Vector2(0.75f), - Icon = FontAwesome.fa_close, - RelativeSizeAxes = Axes.Both, - }; - } - - protected override bool OnMouseDown(MouseDownEvent e) - { - icon.ScaleTo(0.5f, 1000, Easing.OutQuint); - return base.OnMouseDown(e); - } - - protected override bool OnMouseUp(MouseUpEvent e) - { - icon.ScaleTo(0.75f, 1000, Easing.OutElastic); - return base.OnMouseUp(e); - } - - protected override bool OnHover(HoverEvent e) - { - icon.FadeColour(Color4.Red, 200, Easing.OutQuint); - return base.OnHover(e); - } - - protected override void OnHoverLost(HoverLostEvent e) - { - icon.FadeColour(Color4.White, 200, Easing.OutQuint); - base.OnHoverLost(e); - } - } - - public class ChannelSelectorTabItem : ChannelTabItem - { - public override bool IsRemovable => false; - - public override bool IsSwitchable => false; - - public ChannelSelectorTabItem(Channel value) : base(value) - { - Depth = float.MaxValue; - Width = 45; - - icon.Alpha = 0; - - text.TextSize = 45; - textBold.TextSize = 45; - } - - [BackgroundDependencyLoader] - private new void load(OsuColour colour) - { - backgroundInactive = colour.Gray2; - backgroundActive = colour.Gray3; - } - } - - protected override void OnActivated() => updateState(); - - protected override void OnDeactivated() => updateState(); - } - } -} diff --git a/osu.Game/Overlays/Chat/DrawableChannel.cs b/osu.Game/Overlays/Chat/DrawableChannel.cs index c57e71b5ad..2418eda2b6 100644 --- a/osu.Game/Overlays/Chat/DrawableChannel.cs +++ b/osu.Game/Overlays/Chat/DrawableChannel.cs @@ -5,8 +5,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; -using OpenTK.Graphics; -using osu.Framework.Allocation; +using osuTK.Graphics; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Graphics.Containers; @@ -49,21 +48,18 @@ namespace osu.Game.Overlays.Chat }, } }; - - Channel.NewMessagesArrived += newMessagesArrived; - Channel.MessageRemoved += messageRemoved; - Channel.PendingMessageResolved += pendingMessageResolved; - } - - [BackgroundDependencyLoader] - private void load() - { - newMessagesArrived(Channel.Messages); } protected override void LoadComplete() { base.LoadComplete(); + + newMessagesArrived(Channel.Messages); + + Channel.NewMessagesArrived += newMessagesArrived; + Channel.MessageRemoved += messageRemoved; + Channel.PendingMessageResolved += pendingMessageResolved; + scrollToEnd(); } @@ -79,17 +75,15 @@ namespace osu.Game.Overlays.Chat private void newMessagesArrived(IEnumerable newMessages) { // Add up to last Channel.MAX_HISTORY messages - var displayMessages = newMessages.Skip(Math.Max(0, newMessages.Count() - Channel.MAX_HISTORY)); + var displayMessages = newMessages.Skip(Math.Max(0, newMessages.Count() - Channel.MaxHistory)); flow.AddRange(displayMessages.Select(m => new ChatLine(m))); - if (!IsLoaded) return; - if (scroll.IsScrolledToEnd(10) || !flow.Children.Any() || newMessages.Any(m => m is LocalMessage)) scrollToEnd(); var staleMessages = flow.Children.Where(c => c.LifetimeEnd == double.MaxValue).ToArray(); - int count = staleMessages.Length - Channel.MAX_HISTORY; + int count = staleMessages.Length - Channel.MaxHistory; for (int i = 0; i < count; i++) { diff --git a/osu.Game/Overlays/Chat/ExternalLinkDialog.cs b/osu.Game/Overlays/Chat/ExternalLinkDialog.cs new file mode 100644 index 0000000000..b3f7b282a9 --- /dev/null +++ b/osu.Game/Overlays/Chat/ExternalLinkDialog.cs @@ -0,0 +1,33 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using osu.Game.Graphics; +using osu.Game.Overlays.Dialog; + +namespace osu.Game.Overlays.Chat +{ + public class ExternalLinkDialog : PopupDialog + { + public ExternalLinkDialog(string url, Action openExternalLinkAction) + { + HeaderText = "Just checking..."; + BodyText = $"You are about to leave osu! and open the following link in a web browser:\n\n{url}"; + + Icon = FontAwesome.fa_warning; + + Buttons = new PopupDialogButton[] + { + new PopupDialogOkButton + { + Text = @"Yes. Go for it.", + Action = openExternalLinkAction + }, + new PopupDialogCancelButton + { + Text = @"No! Abort mission!" + }, + }; + } + } +} diff --git a/osu.Game/Overlays/Chat/ChannelListItem.cs b/osu.Game/Overlays/Chat/Selection/ChannelListItem.cs similarity index 98% rename from osu.Game/Overlays/Chat/ChannelListItem.cs rename to osu.Game/Overlays/Chat/Selection/ChannelListItem.cs index 8df29c89f2..5a96465520 100644 --- a/osu.Game/Overlays/Chat/ChannelListItem.cs +++ b/osu.Game/Overlays/Chat/Selection/ChannelListItem.cs @@ -3,8 +3,8 @@ using System; using System.Collections.Generic; -using OpenTK; -using OpenTK.Graphics; +using osuTK; +using osuTK.Graphics; using osu.Framework.Allocation; using osu.Framework.Configuration; using osu.Framework.Graphics; @@ -15,7 +15,7 @@ using osu.Game.Graphics.Sprites; using osu.Game.Online.Chat; using osu.Game.Graphics.Containers; -namespace osu.Game.Overlays.Chat +namespace osu.Game.Overlays.Chat.Selection { public class ChannelListItem : OsuClickableContainer, IFilterable { diff --git a/osu.Game/Overlays/Chat/ChannelSection.cs b/osu.Game/Overlays/Chat/Selection/ChannelSection.cs similarity index 97% rename from osu.Game/Overlays/Chat/ChannelSection.cs rename to osu.Game/Overlays/Chat/Selection/ChannelSection.cs index 85fdecaaba..94ee9d4bf6 100644 --- a/osu.Game/Overlays/Chat/ChannelSection.cs +++ b/osu.Game/Overlays/Chat/Selection/ChannelSection.cs @@ -3,13 +3,13 @@ using System.Collections.Generic; using System.Linq; -using OpenTK; +using osuTK; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Online.Chat; -namespace osu.Game.Overlays.Chat +namespace osu.Game.Overlays.Chat.Selection { public class ChannelSection : Container, IHasFilterableChildren { diff --git a/osu.Game/Overlays/Chat/ChannelSelectionOverlay.cs b/osu.Game/Overlays/Chat/Selection/ChannelSelectionOverlay.cs similarity index 93% rename from osu.Game/Overlays/Chat/ChannelSelectionOverlay.cs rename to osu.Game/Overlays/Chat/Selection/ChannelSelectionOverlay.cs index a86465b77b..3afac211f1 100644 --- a/osu.Game/Overlays/Chat/ChannelSelectionOverlay.cs +++ b/osu.Game/Overlays/Chat/Selection/ChannelSelectionOverlay.cs @@ -3,8 +3,8 @@ using System; using System.Collections.Generic; -using OpenTK; -using OpenTK.Graphics; +using osuTK; +using osuTK.Graphics; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; @@ -18,7 +18,7 @@ using osu.Game.Graphics.UserInterface; using osu.Game.Online.Chat; using osu.Game.Graphics.Containers; -namespace osu.Game.Overlays.Chat +namespace osu.Game.Overlays.Chat.Selection { public class ChannelSelectionOverlay : OsuFocusedOverlayContainer { @@ -35,23 +35,6 @@ namespace osu.Game.Overlays.Chat public Action OnRequestJoin; public Action OnRequestLeave; - public IEnumerable Sections - { - set - { - sectionsFlow.ChildrenEnumerable = value; - - foreach (ChannelSection s in sectionsFlow.Children) - { - foreach (ChannelListItem c in s.ChannelFlow.Children) - { - c.OnRequestJoin = channel => { OnRequestJoin?.Invoke(channel); }; - c.OnRequestLeave = channel => { OnRequestLeave?.Invoke(channel); }; - } - } - } - } - public ChannelSelectionOverlay() { RelativeSizeAxes = Axes.X; @@ -140,6 +123,30 @@ namespace osu.Game.Overlays.Chat search.Current.ValueChanged += newValue => sectionsFlow.SearchTerm = newValue; } + public void UpdateAvailableChannels(IEnumerable channels) + { + Scheduler.Add(() => + { + sectionsFlow.ChildrenEnumerable = new[] + { + new ChannelSection + { + Header = "All Channels", + Channels = channels, + }, + }; + + foreach (ChannelSection s in sectionsFlow.Children) + { + foreach (ChannelListItem c in s.ChannelFlow.Children) + { + c.OnRequestJoin = channel => { OnRequestJoin?.Invoke(channel); }; + c.OnRequestLeave = channel => { OnRequestLeave?.Invoke(channel); }; + } + } + }); + } + [BackgroundDependencyLoader] private void load(OsuColour colours) { diff --git a/osu.Game/Overlays/Chat/Tabs/ChannelSelectorTabItem.cs b/osu.Game/Overlays/Chat/Tabs/ChannelSelectorTabItem.cs new file mode 100644 index 0000000000..b370d8f3c5 --- /dev/null +++ b/osu.Game/Overlays/Chat/Tabs/ChannelSelectorTabItem.cs @@ -0,0 +1,34 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Allocation; +using osu.Game.Graphics; +using osu.Game.Online.Chat; + +namespace osu.Game.Overlays.Chat.Tabs +{ + public class ChannelSelectorTabItem : ChannelTabItem + { + public override bool IsRemovable => false; + + public override bool IsSwitchable => false; + + public ChannelSelectorTabItem(Channel value) : base(value) + { + Depth = float.MaxValue; + Width = 45; + + Icon.Alpha = 0; + + Text.TextSize = 45; + TextBold.TextSize = 45; + } + + [BackgroundDependencyLoader] + private new void load(OsuColour colour) + { + BackgroundInactive = colour.Gray2; + BackgroundActive = colour.Gray3; + } + } +} diff --git a/osu.Game/Overlays/Chat/Tabs/ChannelTabControl.cs b/osu.Game/Overlays/Chat/Tabs/ChannelTabControl.cs new file mode 100644 index 0000000000..79cb0a4d14 --- /dev/null +++ b/osu.Game/Overlays/Chat/Tabs/ChannelTabControl.cs @@ -0,0 +1,121 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Graphics; +using osu.Framework.Graphics.UserInterface; +using osu.Game.Graphics; +using osu.Game.Graphics.UserInterface; +using osu.Game.Online.Chat; +using osuTK; +using osu.Framework.Configuration; +using System; +using System.Linq; + +namespace osu.Game.Overlays.Chat.Tabs +{ + public class ChannelTabControl : OsuTabControl + { + public static readonly float SHEAR_WIDTH = 10; + + public Action OnRequestLeave; + + public readonly Bindable ChannelSelectorActive = new Bindable(); + + private readonly ChannelSelectorTabItem selectorTab; + + public ChannelTabControl() + { + TabContainer.Margin = new MarginPadding { Left = 50 }; + TabContainer.Spacing = new Vector2(-SHEAR_WIDTH, 0); + TabContainer.Masking = false; + + AddInternal(new SpriteIcon + { + Icon = FontAwesome.fa_comments, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Size = new Vector2(20), + Margin = new MarginPadding(10), + }); + + AddTabItem(selectorTab = new ChannelSelectorTabItem(new Channel { Name = "+" })); + + ChannelSelectorActive.BindTo(selectorTab.Active); + } + + protected override void AddTabItem(TabItem item, bool addToDropdown = true) + { + if (item != selectorTab && TabContainer.GetLayoutPosition(selectorTab) < float.MaxValue) + // performTabSort might've made selectorTab's position wonky, fix it + TabContainer.SetLayoutPosition(selectorTab, float.MaxValue); + + base.AddTabItem(item, addToDropdown); + } + + protected override TabItem CreateTabItem(Channel value) + { + switch (value.Type) + { + default: + return new ChannelTabItem(value) { OnRequestClose = tabCloseRequested }; + case ChannelType.PM: + return new PrivateChannelTabItem(value) { OnRequestClose = tabCloseRequested }; + } + } + + /// + /// Adds a channel to the ChannelTabControl. + /// The first channel added will automaticly selected. + /// + /// The channel that is going to be added. + public void AddChannel(Channel channel) + { + if (!Items.Contains(channel)) + AddItem(channel); + + if (Current.Value == null) + Current.Value = channel; + } + + /// + /// Removes a channel from the ChannelTabControl. + /// If the selected channel is the one that is beeing removed, the next available channel will be selected. + /// + /// The channel that is going to be removed. + public void RemoveChannel(Channel channel) + { + RemoveItem(channel); + + if (Current.Value == channel) + Current.Value = Items.FirstOrDefault(); + } + + protected override void SelectTab(TabItem tab) + { + if (tab is ChannelSelectorTabItem) + { + tab.Active.Toggle(); + return; + } + + selectorTab.Active.Value = false; + + base.SelectTab(tab); + } + + private void tabCloseRequested(TabItem tab) + { + int totalTabs = TabContainer.Count - 1; // account for selectorTab + int currentIndex = MathHelper.Clamp(TabContainer.IndexOf(tab), 1, totalTabs); + + if (tab == SelectedTab && totalTabs > 1) + // Select the tab after tab-to-be-removed's index, or the tab before if current == last + SelectTab(TabContainer[currentIndex == totalTabs ? currentIndex - 1 : currentIndex + 1]); + else if (totalTabs == 1 && !selectorTab.Active) + // Open channel selection overlay if all channel tabs will be closed after removing this tab + SelectTab(selectorTab); + + OnRequestLeave?.Invoke(tab.Value); + } + } +} diff --git a/osu.Game/Overlays/Chat/Tabs/ChannelTabItem.cs b/osu.Game/Overlays/Chat/Tabs/ChannelTabItem.cs new file mode 100644 index 0000000000..e98eac0aa2 --- /dev/null +++ b/osu.Game/Overlays/Chat/Tabs/ChannelTabItem.cs @@ -0,0 +1,212 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using osu.Framework.Allocation; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.UserInterface; +using osu.Framework.Input.Events; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Online.Chat; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Overlays.Chat.Tabs +{ + public class ChannelTabItem : TabItem + { + protected Color4 BackgroundInactive; + private Color4 backgroundHover; + protected Color4 BackgroundActive; + + public override bool IsRemovable => !Pinned; + + protected readonly SpriteText Text; + protected readonly SpriteText TextBold; + protected readonly ClickableContainer CloseButton; + private readonly Box box; + private readonly Box highlightBox; + protected readonly SpriteIcon Icon; + + public Action OnRequestClose; + private readonly Container content; + + protected override Container Content => content; + + public ChannelTabItem(Channel value) + : base(value) + { + Width = 150; + + RelativeSizeAxes = Axes.Y; + + Anchor = Anchor.BottomLeft; + Origin = Anchor.BottomLeft; + + Shear = new Vector2(ChannelTabControl.SHEAR_WIDTH / ChatOverlay.TAB_AREA_HEIGHT, 0); + + Masking = true; + + InternalChildren = new Drawable[] + { + box = new Box + { + EdgeSmoothness = new Vector2(1, 0), + RelativeSizeAxes = Axes.Both, + }, + highlightBox = new Box + { + Width = 5, + Alpha = 0, + Anchor = Anchor.BottomRight, + Origin = Anchor.BottomRight, + EdgeSmoothness = new Vector2(1, 0), + RelativeSizeAxes = Axes.Y, + }, + content = new Container + { + Shear = new Vector2(-ChannelTabControl.SHEAR_WIDTH / ChatOverlay.TAB_AREA_HEIGHT, 0), + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + Icon = new SpriteIcon + { + Icon = DisplayIcon, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Colour = Color4.Black, + X = -10, + Alpha = 0.2f, + Size = new Vector2(ChatOverlay.TAB_AREA_HEIGHT), + }, + Text = new OsuSpriteText + { + Margin = new MarginPadding(5), + Origin = Anchor.CentreLeft, + Anchor = Anchor.CentreLeft, + Text = value.ToString(), + TextSize = 18, + }, + TextBold = new OsuSpriteText + { + Alpha = 0, + Margin = new MarginPadding(5), + Origin = Anchor.CentreLeft, + Anchor = Anchor.CentreLeft, + Text = value.ToString(), + Font = @"Exo2.0-Bold", + TextSize = 18, + }, + CloseButton = new TabCloseButton + { + Alpha = 0, + Margin = new MarginPadding { Right = 20 }, + Origin = Anchor.CentreRight, + Anchor = Anchor.CentreRight, + Action = delegate + { + if (IsRemovable) OnRequestClose?.Invoke(this); + }, + }, + }, + }, + }; + } + + protected virtual FontAwesome DisplayIcon => FontAwesome.fa_hashtag; + + protected virtual bool ShowCloseOnHover => true; + + protected override bool OnHover(HoverEvent e) + { + if (IsRemovable && ShowCloseOnHover) + CloseButton.FadeIn(200, Easing.OutQuint); + + if (!Active) + box.FadeColour(backgroundHover, TRANSITION_LENGTH, Easing.OutQuint); + return true; + } + + protected override void OnHoverLost(HoverLostEvent e) + { + CloseButton.FadeOut(200, Easing.OutQuint); + updateState(); + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + BackgroundActive = colours.ChatBlue; + BackgroundInactive = colours.Gray4; + backgroundHover = colours.Gray7; + + highlightBox.Colour = colours.Yellow; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + updateState(); + FinishTransforms(true); + } + + private void updateState() + { + if (Active) + FadeActive(); + else + FadeInactive(); + } + + protected const float TRANSITION_LENGTH = 400; + + private readonly EdgeEffectParameters activateEdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Shadow, + Radius = 15, + Colour = Color4.Black.Opacity(0.4f), + }; + + private readonly EdgeEffectParameters deactivateEdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Shadow, + Radius = 10, + Colour = Color4.Black.Opacity(0.2f), + }; + + protected virtual void FadeActive() + { + this.ResizeHeightTo(1.1f, TRANSITION_LENGTH, Easing.OutQuint); + + TweenEdgeEffectTo(activateEdgeEffect, TRANSITION_LENGTH); + + box.FadeColour(BackgroundActive, TRANSITION_LENGTH, Easing.OutQuint); + highlightBox.FadeIn(TRANSITION_LENGTH, Easing.OutQuint); + + Text.FadeOut(TRANSITION_LENGTH, Easing.OutQuint); + TextBold.FadeIn(TRANSITION_LENGTH, Easing.OutQuint); + } + + protected virtual void FadeInactive() + { + this.ResizeHeightTo(1, TRANSITION_LENGTH, Easing.OutQuint); + + TweenEdgeEffectTo(deactivateEdgeEffect, TRANSITION_LENGTH); + + box.FadeColour(BackgroundInactive, TRANSITION_LENGTH, Easing.OutQuint); + highlightBox.FadeOut(TRANSITION_LENGTH, Easing.OutQuint); + + Text.FadeIn(TRANSITION_LENGTH, Easing.OutQuint); + TextBold.FadeOut(TRANSITION_LENGTH, Easing.OutQuint); + } + + protected override void OnActivated() => updateState(); + protected override void OnDeactivated() => updateState(); + } +} diff --git a/osu.Game/Overlays/Chat/Tabs/PrivateChannelTabItem.cs b/osu.Game/Overlays/Chat/Tabs/PrivateChannelTabItem.cs new file mode 100644 index 0000000000..e04d7891f8 --- /dev/null +++ b/osu.Game/Overlays/Chat/Tabs/PrivateChannelTabItem.cs @@ -0,0 +1,97 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Online.Chat; +using osu.Game.Users; +using osuTK; + +namespace osu.Game.Overlays.Chat.Tabs +{ + public class PrivateChannelTabItem : ChannelTabItem + { + private readonly OsuSpriteText username; + private readonly Avatar avatarContainer; + + protected override FontAwesome DisplayIcon => FontAwesome.fa_at; + + public PrivateChannelTabItem(Channel value) + : base(value) + { + if (value.Type != ChannelType.PM) + throw new ArgumentException("Argument value needs to have the targettype user!"); + + AddRange(new Drawable[] + { + new Container + { + RelativeSizeAxes = Axes.Y, + AutoSizeAxes = Axes.X, + Margin = new MarginPadding + { + Horizontal = 3 + }, + Origin = Anchor.BottomLeft, + Anchor = Anchor.BottomLeft, + Children = new Drawable[] + { + new CircularContainer + { + Scale = new Vector2(0.95f), + Size = new Vector2(ChatOverlay.TAB_AREA_HEIGHT), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Masking = true, + Child = new DelayedLoadWrapper(new Avatar(value.Users.First()) + { + RelativeSizeAxes = Axes.Both, + OnLoadComplete = d => d.FadeInFromZero(300, Easing.OutQuint), + }) + { + RelativeSizeAxes = Axes.Both, + } + }, + } + }, + }); + + Text.X = ChatOverlay.TAB_AREA_HEIGHT; + TextBold.X = ChatOverlay.TAB_AREA_HEIGHT; + } + + protected override bool ShowCloseOnHover => false; + + protected override void FadeActive() + { + base.FadeActive(); + + this.ResizeWidthTo(200, TRANSITION_LENGTH, Easing.OutQuint); + CloseButton.FadeIn(TRANSITION_LENGTH, Easing.OutQuint); + } + + + protected override void FadeInactive() + { + base.FadeInactive(); + + this.ResizeWidthTo(ChatOverlay.TAB_AREA_HEIGHT + 10, TRANSITION_LENGTH, Easing.OutQuint); + CloseButton.FadeOut(TRANSITION_LENGTH, Easing.OutQuint); + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + var user = Value.Users.First(); + + BackgroundActive = user.Colour != null ? OsuColour.FromHex(user.Colour) : colours.BlueDark; + BackgroundInactive = BackgroundActive.Darken(0.5f); + } + } +} diff --git a/osu.Game/Overlays/Chat/Tabs/TabCloseButton.cs b/osu.Game/Overlays/Chat/Tabs/TabCloseButton.cs new file mode 100644 index 0000000000..d7d9b3f95c --- /dev/null +++ b/osu.Game/Overlays/Chat/Tabs/TabCloseButton.cs @@ -0,0 +1,55 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Graphics; +using osu.Framework.Input.Events; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Overlays.Chat.Tabs +{ + public class TabCloseButton : OsuClickableContainer + { + private readonly SpriteIcon icon; + + public TabCloseButton() + { + Size = new Vector2(20); + + Child = icon = new SpriteIcon + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Scale = new Vector2(0.75f), + Icon = FontAwesome.fa_close, + RelativeSizeAxes = Axes.Both, + }; + } + + protected override bool OnMouseDown(MouseDownEvent e) + { + icon.ScaleTo(0.5f, 1000, Easing.OutQuint); + return base.OnMouseDown(e); + } + + protected override bool OnMouseUp(MouseUpEvent e) + { + icon.ScaleTo(0.75f, 1000, Easing.OutElastic); + return base.OnMouseUp(e); + } + + protected override bool OnHover(HoverEvent e) + { + icon.FadeColour(Color4.Red, 200, Easing.OutQuint); + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + icon.FadeColour(Color4.White, 200, Easing.OutQuint); + base.OnHoverLost(e); + } + } +} diff --git a/osu.Game/Overlays/ChatOverlay.cs b/osu.Game/Overlays/ChatOverlay.cs index ff2ff9af14..bdb28d1246 100644 --- a/osu.Game/Overlays/ChatOverlay.cs +++ b/osu.Game/Overlays/ChatOverlay.cs @@ -1,11 +1,9 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using System; using System.Collections.Generic; -using System.Linq; -using OpenTK; -using OpenTK.Graphics; +using osuTK; +using osuTK.Graphics; using osu.Framework.Allocation; using osu.Framework.Configuration; using osu.Framework.Graphics; @@ -13,42 +11,38 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; -using osu.Framework.Threading; using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; -using osu.Game.Online.API; -using osu.Game.Online.API.Requests; using osu.Game.Online.Chat; using osu.Game.Overlays.Chat; +using osu.Game.Overlays.Chat.Selection; +using osu.Game.Overlays.Chat.Tabs; namespace osu.Game.Overlays { - public class ChatOverlay : OsuFocusedOverlayContainer, IOnlineComponent + public class ChatOverlay : OsuFocusedOverlayContainer { private const float textbox_height = 60; private const float channel_selection_min_height = 0.3f; - private ScheduledDelegate messageRequest; + private ChannelManager channelManager; private readonly Container currentChannelContainer; + private readonly List loadedChannels = new List(); private readonly LoadingAnimation loading; private readonly FocusedTextBox textbox; - private APIAccess api; - private const int transition_length = 500; public const float DEFAULT_HEIGHT = 0.4f; public const float TAB_AREA_HEIGHT = 50; - private GetUpdatesRequest fetchReq; - - private readonly ChatTabControl channelTabs; + private readonly ChannelTabControl channelTabControl; private readonly Container chatContainer; private readonly TabsArea tabsArea; @@ -57,7 +51,6 @@ namespace osu.Game.Overlays public Bindable ChatHeight { get; set; } - public List AvailableChannels { get; private set; } = new List(); private readonly Container channelSelectionContainer; private readonly ChannelSelectionOverlay channelSelection; @@ -154,10 +147,12 @@ namespace osu.Game.Overlays RelativeSizeAxes = Axes.Both, Colour = Color4.Black, }, - channelTabs = new ChatTabControl + channelTabControl = new ChannelTabControl { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, RelativeSizeAxes = Axes.Both, - OnRequestLeave = removeChannel, + OnRequestLeave = channel => channelManager.LeaveChannel(channel) }, } }, @@ -165,11 +160,11 @@ namespace osu.Game.Overlays }, }; - channelTabs.Current.ValueChanged += newChannel => CurrentChannel = newChannel; - channelTabs.ChannelSelectorActive.ValueChanged += value => channelSelection.State = value ? Visibility.Visible : Visibility.Hidden; + channelTabControl.Current.ValueChanged += chat => channelManager.CurrentChannel.Value = chat; + channelTabControl.ChannelSelectorActive.ValueChanged += value => channelSelection.State = value ? Visibility.Visible : Visibility.Hidden; channelSelection.StateChanged += state => { - channelTabs.ChannelSelectorActive.Value = state == Visibility.Visible; + channelTabControl.ChannelSelectorActive.Value = state == Visibility.Visible; if (state == Visibility.Visible) { @@ -180,13 +175,52 @@ namespace osu.Game.Overlays else textbox.HoldFocus = true; }; + + channelSelection.OnRequestJoin = channel => channelManager.JoinChannel(channel); + channelSelection.OnRequestLeave = channel => channelManager.LeaveChannel(channel); + } + + private void currentChannelChanged(Channel channel) + { + if (channel == null) + { + textbox.Current.Disabled = true; + currentChannelContainer.Clear(false); + return; + } + + textbox.Current.Disabled = channel.ReadOnly; + + if (channelTabControl.Current.Value != channel) + Scheduler.Add(() => channelTabControl.Current.Value = channel); + + var loaded = loadedChannels.Find(d => d.Channel == channel); + if (loaded == null) + { + currentChannelContainer.FadeOut(500, Easing.OutQuint); + loading.Show(); + + loaded = new DrawableChannel(channel); + loadedChannels.Add(loaded); + LoadComponentAsync(loaded, l => + { + loading.Hide(); + + currentChannelContainer.Clear(false); + currentChannelContainer.Add(loaded); + currentChannelContainer.FadeIn(500, Easing.OutQuint); + }); + } + else + { + currentChannelContainer.Clear(false); + Scheduler.Add(() => currentChannelContainer.Add(loaded)); + } } private double startDragChatHeight; private bool isDragging; - public void OpenChannel(Channel channel) => addChannel(channel); - protected override bool OnDragStart(DragStartEvent e) { isDragging = tabsArea.IsHovered; @@ -220,19 +254,6 @@ namespace osu.Game.Overlays return base.OnDragEnd(e); } - public void APIStateChanged(APIAccess api, APIState state) - { - switch (state) - { - case APIState.Online: - initializeChannels(); - break; - default: - messageRequest?.Cancel(); - break; - } - } - public override bool AcceptsFocus => true; protected override void OnFocus(FocusEvent e) @@ -261,11 +282,8 @@ namespace osu.Game.Overlays } [BackgroundDependencyLoader] - private void load(APIAccess api, OsuConfigManager config, OsuColour colours) + private void load(OsuConfigManager config, OsuColour colours, ChannelManager channelManager) { - this.api = api; - api.Register(this); - ChatHeight = config.GetBindable(OsuSetting.ChatDisplayHeight); ChatHeight.ValueChanged += h => { @@ -276,255 +294,67 @@ namespace osu.Game.Overlays ChatHeight.TriggerChange(); chatBackground.Colour = colours.ChatBlue; - } - private long lastMessageId; - - private readonly List careChannels = new List(); - - private readonly List loadedChannels = new List(); - - private void initializeChannels() - { loading.Show(); - messageRequest?.Cancel(); + this.channelManager = channelManager; + channelManager.CurrentChannel.ValueChanged += currentChannelChanged; + channelManager.JoinedChannels.ItemsAdded += onChannelAddedToJoinedChannels; + channelManager.JoinedChannels.ItemsRemoved += onChannelRemovedFromJoinedChannels; + channelManager.AvailableChannels.ItemsAdded += availableChannelsChanged; + channelManager.AvailableChannels.ItemsRemoved += availableChannelsChanged; - ListChannelsRequest req = new ListChannelsRequest(); - req.Success += delegate(List channels) - { - AvailableChannels = channels; - - Scheduler.Add(delegate - { - //todo: decide how to handle default channels for a user now that they are saved server-side. - addChannel(channels.Find(c => c.Name == @"#lazer")); - addChannel(channels.Find(c => c.Name == @"#osu")); - - channelSelection.OnRequestJoin = addChannel; - channelSelection.OnRequestLeave = removeChannel; - channelSelection.Sections = new[] - { - new ChannelSection - { - Header = "All Channels", - Channels = channels, - }, - }; - }); - - messageRequest = Scheduler.AddDelayed(fetchUpdates, 1000, true); - }; - - api.Queue(req); + //for the case that channelmanager was faster at fetching the channels than our attachment to CollectionChanged. + channelSelection.UpdateAvailableChannels(channelManager.AvailableChannels); + foreach (Channel channel in channelManager.JoinedChannels) + channelTabControl.AddChannel(channel); } - private Channel currentChannel; - - protected Channel CurrentChannel + private void onChannelAddedToJoinedChannels(IEnumerable channels) { - get { return currentChannel; } + foreach (Channel channel in channels) + channelTabControl.AddChannel(channel); + } - set + private void onChannelRemovedFromJoinedChannels(IEnumerable channels) + { + foreach (Channel channel in channels) { - if (currentChannel == value) return; - - if (value == null) - { - currentChannel = null; - textbox.Current.Disabled = true; - currentChannelContainer.Clear(false); - return; - } - - currentChannel = value; - - textbox.Current.Disabled = currentChannel.ReadOnly; - channelTabs.Current.Value = value; - - var loaded = loadedChannels.Find(d => d.Channel == value); - if (loaded == null) - { - currentChannelContainer.FadeOut(500, Easing.OutQuint); - loading.Show(); - - loaded = new DrawableChannel(currentChannel); - loadedChannels.Add(loaded); - LoadComponentAsync(loaded, l => - { - if (currentChannel.MessagesLoaded) - loading.Hide(); - - currentChannelContainer.Clear(false); - currentChannelContainer.Add(loaded); - currentChannelContainer.FadeIn(500, Easing.OutQuint); - }); - } - else - { - currentChannelContainer.Clear(false); - currentChannelContainer.Add(loaded); - } + channelTabControl.RemoveChannel(channel); + loadedChannels.Remove(loadedChannels.Find(c => c.Channel == channel)); } } - private void addChannel(Channel channel) + private void availableChannelsChanged(IEnumerable channels) + => channelSelection.UpdateAvailableChannels(channelManager.AvailableChannels); + + protected override void Dispose(bool isDisposing) { - if (channel == null) return; + base.Dispose(isDisposing); - // ReSharper disable once AccessToModifiedClosure - var existing = careChannels.Find(c => c.Id == channel.Id); - - if (existing != null) + if (channelManager != null) { - // if we already have this channel loaded, we don't want to make a second one. - channel = existing; + channelManager.CurrentChannel.ValueChanged -= currentChannelChanged; + channelManager.JoinedChannels.ItemsAdded -= onChannelAddedToJoinedChannels; + channelManager.JoinedChannels.ItemsRemoved -= onChannelRemovedFromJoinedChannels; + channelManager.AvailableChannels.ItemsAdded -= availableChannelsChanged; + channelManager.AvailableChannels.ItemsRemoved -= availableChannelsChanged; } - else - { - careChannels.Add(channel); - channelTabs.AddItem(channel); - - if (channel.Type == ChannelType.Public && !channel.Joined) - { - var req = new JoinChannelRequest(channel, api.LocalUser); - req.Success += () => addChannel(channel); - req.Failure += ex => removeChannel(channel); - api.Queue(req); - return; - } - } - - // let's fetch a small number of messages to bring us up-to-date with the backlog. - fetchInitialMessages(channel); - - if (CurrentChannel == null) - CurrentChannel = channel; - - channel.Joined.Value = true; - } - - private void removeChannel(Channel channel) - { - if (channel == null) return; - - if (channel == CurrentChannel) CurrentChannel = null; - - careChannels.Remove(channel); - loadedChannels.Remove(loadedChannels.Find(c => c.Channel == channel)); - channelTabs.RemoveItem(channel); - - api.Queue(new LeaveChannelRequest(channel, api.LocalUser)); - channel.Joined.Value = false; - } - - private void fetchInitialMessages(Channel channel) - { - var req = new GetMessagesRequest(channel); - req.Success += messages => - { - channel.AddNewMessages(messages.ToArray()); - if (channel == currentChannel) - loading.Hide(); - }; - - api.Queue(req); - } - - private void fetchUpdates() - { - if (fetchReq != null) return; - - fetchReq = new GetUpdatesRequest(lastMessageId); - - fetchReq.Success += updates => - { - if (updates?.Presence != null) - { - foreach (var channel in updates.Presence) - addChannel(AvailableChannels.Find(c => c.Id == channel.Id)); - - foreach (var group in updates.Messages.GroupBy(m => m.ChannelId)) - careChannels.Find(c => c.Id == group.Key)?.AddNewMessages(group.ToArray()); - - lastMessageId = updates.Messages.LastOrDefault()?.Id ?? lastMessageId; - } - - fetchReq = null; - }; - - fetchReq.Failure += delegate { fetchReq = null; }; - - api.Queue(fetchReq); } private void postMessage(TextBox textbox, bool newText) { - var postText = textbox.Text; + 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; - - if (string.IsNullOrWhiteSpace(postText)) - return; - - var target = currentChannel; - - if (target == null) return; - - if (!api.IsLoggedIn) - { - target.AddNewMessages(new ErrorMessage("Please sign in to participate in chat!")); - return; - } - - bool isAction = false; - - if (postText[0] == '/') - { - string[] parameters = postText.Substring(1).Split(new[] { ' ' }, 2); - string command = parameters[0]; - string content = parameters.Length == 2 ? parameters[1] : string.Empty; - - switch (command) - { - case "me": - - if (string.IsNullOrWhiteSpace(content)) - { - currentChannel.AddNewMessages(new ErrorMessage("Usage: /me [action]")); - return; - } - - isAction = true; - postText = content; - break; - - case "help": - currentChannel.AddNewMessages(new InfoMessage("Supported commands: /help, /me [action]")); - return; - - default: - currentChannel.AddNewMessages(new ErrorMessage($@"""/{command}"" is not supported! For a list of supported commands see /help")); - return; - } - } - - var message = new LocalEchoMessage - { - Sender = api.LocalUser.Value, - Timestamp = DateTimeOffset.Now, - ChannelId = target.Id, - IsAction = isAction, - Content = postText - }; - - var req = new PostMessageRequest(message); - - target.AddLocalEcho(message); - req.Failure += e => target.ReplaceMessage(message, null); - req.Success += m => target.ReplaceMessage(message, m); - - api.Queue(req); } private class TabsArea : Container diff --git a/osu.Game/Overlays/Dialog/PopupDialog.cs b/osu.Game/Overlays/Dialog/PopupDialog.cs index f421d2202c..ca921b7882 100644 --- a/osu.Game/Overlays/Dialog/PopupDialog.cs +++ b/osu.Game/Overlays/Dialog/PopupDialog.cs @@ -7,16 +7,14 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; using osu.Game.Graphics; using osu.Game.Graphics.Backgrounds; using osu.Game.Graphics.Containers; -using osu.Game.Graphics.Sprites; using osu.Game.Input.Bindings; -using OpenTK; -using OpenTK.Graphics; -using OpenTK.Input; +using osuTK; +using osuTK.Graphics; +using osuTK.Input; namespace osu.Game.Overlays.Dialog { @@ -35,7 +33,7 @@ namespace osu.Game.Overlays.Dialog private readonly Container ring; private readonly FillFlowContainer buttonsContainer; private readonly SpriteIcon icon; - private readonly SpriteText header; + private readonly TextFlowContainer header; private readonly TextFlowContainer body; private bool actionInvoked; @@ -46,10 +44,19 @@ namespace osu.Game.Overlays.Dialog set => icon.Icon = value; } + private string text; + public string HeaderText { - get => header.Text; - set => header.Text = value; + get => text; + set + { + if (text == value) + return; + text = value; + + header.Text = value; + } } public string BodyText @@ -164,18 +171,20 @@ namespace osu.Game.Overlays.Dialog }, }, }, - header = new OsuSpriteText + header = new OsuTextFlowContainer(t => t.TextSize = 25) { Origin = Anchor.TopCentre, Anchor = Anchor.TopCentre, - TextSize = 25, - Shadow = true, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Padding = new MarginPadding(15), + TextAnchor = Anchor.TopCentre, }, body = new OsuTextFlowContainer(t => t.TextSize = 18) { - Padding = new MarginPadding(15), RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, + Padding = new MarginPadding(15), TextAnchor = Anchor.TopCentre, }, }, diff --git a/osu.Game/Overlays/Direct/DirectGridPanel.cs b/osu.Game/Overlays/Direct/DirectGridPanel.cs index 1c462e3a73..006e3a034b 100644 --- a/osu.Game/Overlays/Direct/DirectGridPanel.cs +++ b/osu.Game/Overlays/Direct/DirectGridPanel.cs @@ -1,8 +1,8 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using OpenTK; -using OpenTK.Graphics; +using osuTK; +using osuTK.Graphics; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; diff --git a/osu.Game/Overlays/Direct/DirectListPanel.cs b/osu.Game/Overlays/Direct/DirectListPanel.cs index 6cb5ebad4e..a922ceb484 100644 --- a/osu.Game/Overlays/Direct/DirectListPanel.cs +++ b/osu.Game/Overlays/Direct/DirectListPanel.cs @@ -1,8 +1,8 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using OpenTK; -using OpenTK.Graphics; +using osuTK; +using osuTK.Graphics; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; diff --git a/osu.Game/Overlays/Direct/DirectPanel.cs b/osu.Game/Overlays/Direct/DirectPanel.cs index 5c7c34a0ed..44556a6360 100644 --- a/osu.Game/Overlays/Direct/DirectPanel.cs +++ b/osu.Game/Overlays/Direct/DirectPanel.cs @@ -18,8 +18,8 @@ using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Online.API.Requests; -using OpenTK; -using OpenTK.Graphics; +using osuTK; +using osuTK.Graphics; namespace osu.Game.Overlays.Direct { @@ -174,11 +174,11 @@ namespace osu.Game.Overlays.Direct }; } - private void setAdded(BeatmapSetInfo s) + private void setAdded(BeatmapSetInfo s, bool existing, bool silent) => Schedule(() => { if (s.OnlineBeatmapSetID == SetInfo.OnlineBeatmapSetID) progressBar.FadeOut(500); - } + }); protected override void LoadComplete() { diff --git a/osu.Game/Overlays/Direct/DownloadButton.cs b/osu.Game/Overlays/Direct/DownloadButton.cs index 99a5881487..e326f5e592 100644 --- a/osu.Game/Overlays/Direct/DownloadButton.cs +++ b/osu.Game/Overlays/Direct/DownloadButton.cs @@ -8,7 +8,7 @@ using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables; using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; -using OpenTK; +using osuTK; namespace osu.Game.Overlays.Direct { diff --git a/osu.Game/Overlays/Direct/FilterControl.cs b/osu.Game/Overlays/Direct/FilterControl.cs index c4825f72fe..3f2c3a4fbf 100644 --- a/osu.Game/Overlays/Direct/FilterControl.cs +++ b/osu.Game/Overlays/Direct/FilterControl.cs @@ -10,8 +10,8 @@ using osu.Game.Graphics.Containers; using osu.Game.Online.API.Requests; using osu.Game.Overlays.SearchableList; using osu.Game.Rulesets; -using OpenTK; -using OpenTK.Graphics; +using osuTK; +using osuTK.Graphics; namespace osu.Game.Overlays.Direct { diff --git a/osu.Game/Overlays/Direct/Header.cs b/osu.Game/Overlays/Direct/Header.cs index 01180f1fde..2ca7b558ba 100644 --- a/osu.Game/Overlays/Direct/Header.cs +++ b/osu.Game/Overlays/Direct/Header.cs @@ -2,7 +2,7 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System.ComponentModel; -using OpenTK.Graphics; +using osuTK.Graphics; using osu.Framework.Graphics; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; diff --git a/osu.Game/Overlays/Direct/IconPill.cs b/osu.Game/Overlays/Direct/IconPill.cs index 61a0c63814..9ac544ea78 100644 --- a/osu.Game/Overlays/Direct/IconPill.cs +++ b/osu.Game/Overlays/Direct/IconPill.cs @@ -5,8 +5,8 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; -using OpenTK; -using OpenTK.Graphics; +using osuTK; +using osuTK.Graphics; namespace osu.Game.Overlays.Direct { diff --git a/osu.Game/Overlays/Direct/PlayButton.cs b/osu.Game/Overlays/Direct/PlayButton.cs index ac7a26fca3..d9b36e2367 100644 --- a/osu.Game/Overlays/Direct/PlayButton.cs +++ b/osu.Game/Overlays/Direct/PlayButton.cs @@ -10,8 +10,8 @@ using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; -using OpenTK; -using OpenTK.Graphics; +using osuTK; +using osuTK.Graphics; namespace osu.Game.Overlays.Direct { diff --git a/osu.Game/Overlays/DirectOverlay.cs b/osu.Game/Overlays/DirectOverlay.cs index 641f57d25f..765c86e6ad 100644 --- a/osu.Game/Overlays/DirectOverlay.cs +++ b/osu.Game/Overlays/DirectOverlay.cs @@ -18,8 +18,8 @@ using osu.Game.Online.API.Requests; using osu.Game.Overlays.Direct; using osu.Game.Overlays.SearchableList; using osu.Game.Rulesets; -using OpenTK; -using OpenTK.Graphics; +using osuTK; +using osuTK.Graphics; namespace osu.Game.Overlays { diff --git a/osu.Game/Overlays/HoldToConfirmOverlay.cs b/osu.Game/Overlays/HoldToConfirmOverlay.cs index 7e2f6f5891..b2c1ad20be 100644 --- a/osu.Game/Overlays/HoldToConfirmOverlay.cs +++ b/osu.Game/Overlays/HoldToConfirmOverlay.cs @@ -5,7 +5,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics.Containers; -using OpenTK.Graphics; +using osuTK.Graphics; namespace osu.Game.Overlays { diff --git a/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs b/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs index 63ddc25fde..fe2903d393 100644 --- a/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs +++ b/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs @@ -15,8 +15,8 @@ using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Input; -using OpenTK.Graphics; -using OpenTK.Input; +using osuTK.Graphics; +using osuTK.Input; namespace osu.Game.Overlays.KeyBinding { diff --git a/osu.Game/Overlays/KeyBinding/KeyBindingsSubsection.cs b/osu.Game/Overlays/KeyBinding/KeyBindingsSubsection.cs index 895dda872a..9e57b9eefa 100644 --- a/osu.Game/Overlays/KeyBinding/KeyBindingsSubsection.cs +++ b/osu.Game/Overlays/KeyBinding/KeyBindingsSubsection.cs @@ -10,7 +10,7 @@ using osu.Game.Graphics.UserInterface; using osu.Game.Input; using osu.Game.Overlays.Settings; using osu.Game.Rulesets; -using OpenTK; +using osuTK; using osu.Game.Graphics; namespace osu.Game.Overlays.KeyBinding diff --git a/osu.Game/Overlays/LoginOverlay.cs b/osu.Game/Overlays/LoginOverlay.cs index 731c5ee973..e094f8ef48 100644 --- a/osu.Game/Overlays/LoginOverlay.cs +++ b/osu.Game/Overlays/LoginOverlay.cs @@ -6,7 +6,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Graphics; using osu.Game.Overlays.Settings.Sections.General; -using OpenTK.Graphics; +using osuTK.Graphics; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Cursor; diff --git a/osu.Game/Overlays/MainSettings.cs b/osu.Game/Overlays/MainSettings.cs index 736843ee4d..cd9cf8d50e 100644 --- a/osu.Game/Overlays/MainSettings.cs +++ b/osu.Game/Overlays/MainSettings.cs @@ -14,8 +14,8 @@ using osu.Game.Input.Bindings; using osu.Game.Overlays.Settings; using osu.Game.Overlays.Settings.Sections; using osu.Game.Screens.Ranking; -using OpenTK; -using OpenTK.Graphics; +using osuTK; +using osuTK.Graphics; namespace osu.Game.Overlays { diff --git a/osu.Game/Overlays/MedalOverlay.cs b/osu.Game/Overlays/MedalOverlay.cs index dcd325490a..85875d2052 100644 --- a/osu.Game/Overlays/MedalOverlay.cs +++ b/osu.Game/Overlays/MedalOverlay.cs @@ -1,8 +1,8 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using OpenTK; -using OpenTK.Graphics; +using osuTK; +using osuTK.Graphics; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -16,7 +16,7 @@ using osu.Framework.Allocation; using osu.Framework.Audio.Sample; using osu.Framework.Audio; using osu.Framework.Graphics.Textures; -using OpenTK.Input; +using osuTK.Input; using osu.Framework.Graphics.Shapes; using System; using osu.Framework.Input.Events; diff --git a/osu.Game/Overlays/MedalSplash/DrawableMedal.cs b/osu.Game/Overlays/MedalSplash/DrawableMedal.cs index 629b6d6fa4..87d5724d24 100644 --- a/osu.Game/Overlays/MedalSplash/DrawableMedal.cs +++ b/osu.Game/Overlays/MedalSplash/DrawableMedal.cs @@ -3,7 +3,7 @@ using System; using osu.Framework; -using OpenTK; +using osuTK; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; diff --git a/osu.Game/Overlays/Mods/ModButton.cs b/osu.Game/Overlays/Mods/ModButton.cs index ecb65f6df2..239688e284 100644 --- a/osu.Game/Overlays/Mods/ModButton.cs +++ b/osu.Game/Overlays/Mods/ModButton.cs @@ -1,9 +1,9 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using OpenTK; -using OpenTK.Graphics; -using OpenTK.Input; +using osuTK; +using osuTK.Graphics; +using osuTK.Input; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; diff --git a/osu.Game/Overlays/Mods/ModButtonEmpty.cs b/osu.Game/Overlays/Mods/ModButtonEmpty.cs index 9129856c8f..dfe14e066e 100644 --- a/osu.Game/Overlays/Mods/ModButtonEmpty.cs +++ b/osu.Game/Overlays/Mods/ModButtonEmpty.cs @@ -1,7 +1,7 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using OpenTK; +using osuTK; using osu.Framework.Graphics.Containers; namespace osu.Game.Overlays.Mods diff --git a/osu.Game/Overlays/Mods/ModSection.cs b/osu.Game/Overlays/Mods/ModSection.cs index c0d2d889c6..8ac98ddff6 100644 --- a/osu.Game/Overlays/Mods/ModSection.cs +++ b/osu.Game/Overlays/Mods/ModSection.cs @@ -1,8 +1,8 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using OpenTK; -using OpenTK.Input; +using osuTK; +using osuTK.Input; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Graphics.Sprites; diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index 55d5d797e7..742a3830b4 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -1,8 +1,8 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using OpenTK; -using OpenTK.Graphics; +using osuTK; +using osuTK.Graphics; using osu.Framework.Allocation; using osu.Framework.Configuration; using osu.Framework.Extensions.Color4Extensions; diff --git a/osu.Game/Overlays/Mods/Sections/AutomationSection.cs b/osu.Game/Overlays/Mods/Sections/AutomationSection.cs index 2b509d539e..c14ed270f7 100644 --- a/osu.Game/Overlays/Mods/Sections/AutomationSection.cs +++ b/osu.Game/Overlays/Mods/Sections/AutomationSection.cs @@ -2,7 +2,7 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using osu.Game.Rulesets.Mods; -using OpenTK.Input; +using osuTK.Input; namespace osu.Game.Overlays.Mods.Sections { diff --git a/osu.Game/Overlays/Mods/Sections/ConversionSection.cs b/osu.Game/Overlays/Mods/Sections/ConversionSection.cs index 568f0ecfce..90ff879265 100644 --- a/osu.Game/Overlays/Mods/Sections/ConversionSection.cs +++ b/osu.Game/Overlays/Mods/Sections/ConversionSection.cs @@ -2,7 +2,7 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using osu.Game.Rulesets.Mods; -using OpenTK.Input; +using osuTK.Input; namespace osu.Game.Overlays.Mods.Sections { diff --git a/osu.Game/Overlays/Mods/Sections/DifficultyIncreaseSection.cs b/osu.Game/Overlays/Mods/Sections/DifficultyIncreaseSection.cs index 5aced7ed5d..f1b7edf98b 100644 --- a/osu.Game/Overlays/Mods/Sections/DifficultyIncreaseSection.cs +++ b/osu.Game/Overlays/Mods/Sections/DifficultyIncreaseSection.cs @@ -2,7 +2,7 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using osu.Game.Rulesets.Mods; -using OpenTK.Input; +using osuTK.Input; namespace osu.Game.Overlays.Mods.Sections { diff --git a/osu.Game/Overlays/Mods/Sections/DifficultyReductionSection.cs b/osu.Game/Overlays/Mods/Sections/DifficultyReductionSection.cs index 29fae2c70a..8ab9251431 100644 --- a/osu.Game/Overlays/Mods/Sections/DifficultyReductionSection.cs +++ b/osu.Game/Overlays/Mods/Sections/DifficultyReductionSection.cs @@ -2,7 +2,7 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using osu.Game.Rulesets.Mods; -using OpenTK.Input; +using osuTK.Input; namespace osu.Game.Overlays.Mods.Sections { diff --git a/osu.Game/Overlays/Mods/Sections/FunSection.cs b/osu.Game/Overlays/Mods/Sections/FunSection.cs index ef975d9d75..29329f9c1d 100644 --- a/osu.Game/Overlays/Mods/Sections/FunSection.cs +++ b/osu.Game/Overlays/Mods/Sections/FunSection.cs @@ -2,7 +2,7 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using osu.Game.Rulesets.Mods; -using OpenTK.Input; +using osuTK.Input; namespace osu.Game.Overlays.Mods.Sections { diff --git a/osu.Game/Overlays/Music/CollectionsDropdown.cs b/osu.Game/Overlays/Music/CollectionsDropdown.cs index e06688e8f0..7b2f81b60c 100644 --- a/osu.Game/Overlays/Music/CollectionsDropdown.cs +++ b/osu.Game/Overlays/Music/CollectionsDropdown.cs @@ -1,8 +1,8 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using OpenTK; -using OpenTK.Graphics; +using osuTK; +using osuTK.Graphics; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; diff --git a/osu.Game/Overlays/Music/FilterControl.cs b/osu.Game/Overlays/Music/FilterControl.cs index 2d492dcf1e..598b99b9a6 100644 --- a/osu.Game/Overlays/Music/FilterControl.cs +++ b/osu.Game/Overlays/Music/FilterControl.cs @@ -1,14 +1,13 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; -using OpenTK; -using OpenTK.Graphics; +using osuTK; +using osuTK.Graphics; using System; namespace osu.Game.Overlays.Music @@ -37,7 +36,7 @@ namespace osu.Game.Overlays.Music new CollectionsDropdown { RelativeSizeAxes = Axes.X, - Items = new[] { new KeyValuePair(@"All", PlaylistCollection.All) }, + Items = new[] { PlaylistCollection.All }, } }, }, diff --git a/osu.Game/Overlays/Music/PlaylistItem.cs b/osu.Game/Overlays/Music/PlaylistItem.cs index c0a59df767..5d89e53081 100644 --- a/osu.Game/Overlays/Music/PlaylistItem.cs +++ b/osu.Game/Overlays/Music/PlaylistItem.cs @@ -3,7 +3,7 @@ using System; using System.Collections.Generic; -using OpenTK.Graphics; +using osuTK.Graphics; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -13,7 +13,7 @@ using osu.Framework.Localisation; using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.Containers; -using OpenTK; +using osuTK; namespace osu.Game.Overlays.Music { diff --git a/osu.Game/Overlays/Music/PlaylistList.cs b/osu.Game/Overlays/Music/PlaylistList.cs index 17c8d2f154..b619abbc2f 100644 --- a/osu.Game/Overlays/Music/PlaylistList.cs +++ b/osu.Game/Overlays/Music/PlaylistList.cs @@ -11,7 +11,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Input.Events; using osu.Game.Beatmaps; using osu.Game.Graphics.Containers; -using OpenTK; +using osuTK; namespace osu.Game.Overlays.Music { @@ -75,7 +75,7 @@ namespace osu.Game.Overlays.Music [BackgroundDependencyLoader] private void load(BeatmapManager beatmaps, IBindableBeatmap beatmap) { - beatmaps.GetAllUsableBeatmapSets().ForEach(addBeatmapSet); + beatmaps.GetAllUsableBeatmapSets().ForEach(b => addBeatmapSet(b, false, false)); beatmaps.ItemAdded += addBeatmapSet; beatmaps.ItemRemoved += removeBeatmapSet; @@ -83,20 +83,23 @@ namespace osu.Game.Overlays.Music beatmapBacking.ValueChanged += _ => updateSelectedSet(); } - private void addBeatmapSet(BeatmapSetInfo obj) + private void addBeatmapSet(BeatmapSetInfo obj, bool existing, bool silent) => Schedule(() => { + if (existing) + return; + var newItem = new PlaylistItem(obj) { OnSelect = set => Selected?.Invoke(set) }; items.Add(newItem); items.SetLayoutPosition(newItem, items.Count - 1); - } + }); - private void removeBeatmapSet(BeatmapSetInfo obj) + private void removeBeatmapSet(BeatmapSetInfo obj) => Schedule(() => { var itemToRemove = items.FirstOrDefault(i => i.BeatmapSetInfo.ID == obj.ID); if (itemToRemove != null) items.Remove(itemToRemove); - } + }); private void updateSelectedSet() { diff --git a/osu.Game/Overlays/Music/PlaylistOverlay.cs b/osu.Game/Overlays/Music/PlaylistOverlay.cs index b74e7e1178..6c45c63145 100644 --- a/osu.Game/Overlays/Music/PlaylistOverlay.cs +++ b/osu.Game/Overlays/Music/PlaylistOverlay.cs @@ -11,8 +11,8 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Game.Beatmaps; using osu.Game.Graphics; -using OpenTK; -using OpenTK.Graphics; +using osuTK; +using osuTK.Graphics; namespace osu.Game.Overlays.Music { diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index b32fd265cb..2dc997d5ed 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -22,8 +22,8 @@ using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Overlays.Music; -using OpenTK; -using OpenTK.Graphics; +using osuTK; +using osuTK.Graphics; namespace osu.Game.Overlays { @@ -214,8 +214,15 @@ namespace osu.Game.Overlays beatmapSets.Insert(index, beatmapSetInfo); } - private void handleBeatmapAdded(BeatmapSetInfo obj) => beatmapSets.Add(obj); - private void handleBeatmapRemoved(BeatmapSetInfo obj) => beatmapSets.RemoveAll(s => s.ID == obj.ID); + private void handleBeatmapAdded(BeatmapSetInfo obj, bool existing, bool silent) + { + if (existing) + return; + + Schedule(() => beatmapSets.Add(obj)); + } + + private void handleBeatmapRemoved(BeatmapSetInfo obj) => Schedule(() => beatmapSets.RemoveAll(s => s.ID == obj.ID)); protected override void LoadComplete() { @@ -245,10 +252,10 @@ namespace osu.Game.Overlays { base.Update(); - if (current?.TrackLoaded ?? false) - { - var track = current.Track; + var track = current?.TrackLoaded ?? false ? current.Track : null; + if (track?.IsDummyDevice == false) + { progressBar.EndTime = track.Length; progressBar.CurrentTime = track.CurrentTime; @@ -258,7 +265,11 @@ namespace osu.Game.Overlays next(); } else + { + progressBar.CurrentTime = 0; + progressBar.EndTime = 1; playButton.Icon = FontAwesome.fa_play_circle_o; + } } private void play() @@ -402,6 +413,10 @@ namespace osu.Game.Overlays { base.PopOut(); + // This is here mostly as a performance fix. + // If the playlist is not hidden it will update children even when the music controller is hidden (due to AlwaysPresent). + playlist.State = Visibility.Hidden; + this.FadeOut(transition_length, Easing.OutQuint); dragContainer.ScaleTo(0.9f, transition_length, Easing.OutQuint); } diff --git a/osu.Game/Overlays/NotificationOverlay.cs b/osu.Game/Overlays/NotificationOverlay.cs index 78f8f57343..b9c5151e26 100644 --- a/osu.Game/Overlays/NotificationOverlay.cs +++ b/osu.Game/Overlays/NotificationOverlay.cs @@ -6,7 +6,7 @@ using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Overlays.Notifications; -using OpenTK.Graphics; +using osuTK.Graphics; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics.Containers; using System; diff --git a/osu.Game/Overlays/Notifications/Notification.cs b/osu.Game/Overlays/Notifications/Notification.cs index aa2b248bc4..ad20e6cd01 100644 --- a/osu.Game/Overlays/Notifications/Notification.cs +++ b/osu.Game/Overlays/Notifications/Notification.cs @@ -8,8 +8,8 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Game.Graphics; -using OpenTK; -using OpenTK.Graphics; +using osuTK; +using osuTK.Graphics; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; using osu.Game.Graphics.Containers; diff --git a/osu.Game/Overlays/Notifications/NotificationSection.cs b/osu.Game/Overlays/Notifications/NotificationSection.cs index f41e3e876f..6958835f03 100644 --- a/osu.Game/Overlays/Notifications/NotificationSection.cs +++ b/osu.Game/Overlays/Notifications/NotificationSection.cs @@ -10,7 +10,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; -using OpenTK; +using osuTK; using osu.Game.Graphics.Containers; namespace osu.Game.Overlays.Notifications diff --git a/osu.Game/Overlays/Notifications/ProgressNotification.cs b/osu.Game/Overlays/Notifications/ProgressNotification.cs index 254258d098..56342ea4c4 100644 --- a/osu.Game/Overlays/Notifications/ProgressNotification.cs +++ b/osu.Game/Overlays/Notifications/ProgressNotification.cs @@ -8,8 +8,8 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; using osu.Game.Graphics.Containers; -using OpenTK; -using OpenTK.Graphics; +using osuTK; +using osuTK.Graphics; namespace osu.Game.Overlays.Notifications { diff --git a/osu.Game/Overlays/Notifications/SimpleNotification.cs b/osu.Game/Overlays/Notifications/SimpleNotification.cs index 25a832941e..d0f49a74c0 100644 --- a/osu.Game/Overlays/Notifications/SimpleNotification.cs +++ b/osu.Game/Overlays/Notifications/SimpleNotification.cs @@ -8,7 +8,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; using osu.Game.Graphics.Containers; -using OpenTK; +using osuTK; namespace osu.Game.Overlays.Notifications { diff --git a/osu.Game/Overlays/OnScreenDisplay.cs b/osu.Game/Overlays/OnScreenDisplay.cs index 97c6554908..9cf42c3a29 100644 --- a/osu.Game/Overlays/OnScreenDisplay.cs +++ b/osu.Game/Overlays/OnScreenDisplay.cs @@ -11,8 +11,8 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; -using OpenTK; -using OpenTK.Graphics; +using osuTK; +using osuTK.Graphics; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics.Transforms; using osu.Framework.Threading; diff --git a/osu.Game/Overlays/Profile/Header/BadgeContainer.cs b/osu.Game/Overlays/Profile/Header/BadgeContainer.cs index 6a87db4211..06fef22309 100644 --- a/osu.Game/Overlays/Profile/Header/BadgeContainer.cs +++ b/osu.Game/Overlays/Profile/Header/BadgeContainer.cs @@ -13,7 +13,7 @@ using osu.Framework.Input.Events; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Users; -using OpenTK; +using osuTK; namespace osu.Game.Overlays.Profile.Header { diff --git a/osu.Game/Overlays/Profile/Header/RankGraph.cs b/osu.Game/Overlays/Profile/Header/RankGraph.cs index bfb01ce1c8..f74c8b5069 100644 --- a/osu.Game/Overlays/Profile/Header/RankGraph.cs +++ b/osu.Game/Overlays/Profile/Header/RankGraph.cs @@ -15,7 +15,7 @@ using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Users; -using OpenTK; +using osuTK; namespace osu.Game.Overlays.Profile.Header { diff --git a/osu.Game/Overlays/Profile/Header/SupporterIcon.cs b/osu.Game/Overlays/Profile/Header/SupporterIcon.cs index 37ad63464c..1325ea4e9a 100644 --- a/osu.Game/Overlays/Profile/Header/SupporterIcon.cs +++ b/osu.Game/Overlays/Profile/Header/SupporterIcon.cs @@ -8,7 +8,7 @@ using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; using osu.Game.Graphics.Backgrounds; -using OpenTK; +using osuTK; namespace osu.Game.Overlays.Profile.Header { diff --git a/osu.Game/Overlays/Profile/ProfileHeader.cs b/osu.Game/Overlays/Profile/ProfileHeader.cs index 4839348e0e..a8075ec295 100644 --- a/osu.Game/Overlays/Profile/ProfileHeader.cs +++ b/osu.Game/Overlays/Profile/ProfileHeader.cs @@ -2,8 +2,8 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System; -using OpenTK; -using OpenTK.Graphics; +using osuTK; +using osuTK.Graphics; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; diff --git a/osu.Game/Overlays/Profile/ProfileSection.cs b/osu.Game/Overlays/Profile/ProfileSection.cs index c408f69cd7..1aa43feb8c 100644 --- a/osu.Game/Overlays/Profile/ProfileSection.cs +++ b/osu.Game/Overlays/Profile/ProfileSection.cs @@ -1,14 +1,14 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using OpenTK; +using osuTK; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Users; -using OpenTK.Graphics; +using osuTK.Graphics; using osu.Framework.Configuration; namespace osu.Game.Overlays.Profile diff --git a/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs b/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs index 621f752b9c..195269d427 100644 --- a/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs @@ -7,7 +7,7 @@ using osu.Framework.Graphics; using osu.Game.Online.API.Requests; using osu.Game.Overlays.Direct; using osu.Game.Users; -using OpenTK; +using osuTK; namespace osu.Game.Overlays.Profile.Sections.Beatmaps { diff --git a/osu.Game/Overlays/Profile/Sections/DrawableProfileRow.cs b/osu.Game/Overlays/Profile/Sections/DrawableProfileRow.cs index 165299e8c0..3aefce39dc 100644 --- a/osu.Game/Overlays/Profile/Sections/DrawableProfileRow.cs +++ b/osu.Game/Overlays/Profile/Sections/DrawableProfileRow.cs @@ -8,8 +8,8 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; using osu.Game.Graphics; -using OpenTK; -using OpenTK.Graphics; +using osuTK; +using osuTK.Graphics; namespace osu.Game.Overlays.Profile.Sections { diff --git a/osu.Game/Overlays/Profile/Sections/Historical/DrawableMostPlayedRow.cs b/osu.Game/Overlays/Profile/Sections/Historical/DrawableMostPlayedRow.cs index 0a2b2fe121..bc0f22f4d0 100644 --- a/osu.Game/Overlays/Profile/Sections/Historical/DrawableMostPlayedRow.cs +++ b/osu.Game/Overlays/Profile/Sections/Historical/DrawableMostPlayedRow.cs @@ -8,7 +8,7 @@ using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; -using OpenTK; +using osuTK; namespace osu.Game.Overlays.Profile.Sections.Historical { diff --git a/osu.Game/Overlays/Profile/Sections/Kudosu/KudosuInfo.cs b/osu.Game/Overlays/Profile/Sections/Kudosu/KudosuInfo.cs index 788041205b..5207aa6528 100644 --- a/osu.Game/Overlays/Profile/Sections/Kudosu/KudosuInfo.cs +++ b/osu.Game/Overlays/Profile/Sections/Kudosu/KudosuInfo.cs @@ -1,8 +1,8 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using OpenTK; -using OpenTK.Graphics; +using osuTK; +using osuTK.Graphics; using osu.Framework.Configuration; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; diff --git a/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs b/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs index db93fcbc1b..40da95c30b 100644 --- a/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs @@ -1,7 +1,7 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using OpenTK; +using osuTK; using osu.Framework.Allocation; using osu.Framework.Configuration; using osu.Framework.Graphics; diff --git a/osu.Game/Overlays/Profile/Sections/Ranks/DrawablePerformanceScore.cs b/osu.Game/Overlays/Profile/Sections/Ranks/DrawablePerformanceScore.cs index ddbe10c05c..98a4bdba45 100644 --- a/osu.Game/Overlays/Profile/Sections/Ranks/DrawablePerformanceScore.cs +++ b/osu.Game/Overlays/Profile/Sections/Ranks/DrawablePerformanceScore.cs @@ -5,7 +5,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; -using osu.Game.Rulesets.Scoring; +using osu.Game.Scoring; namespace osu.Game.Overlays.Profile.Sections.Ranks { @@ -13,7 +13,7 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks { private readonly double? weight; - public DrawablePerformanceScore(Score score, double? weight = null) + public DrawablePerformanceScore(ScoreInfo score, double? weight = null) : base(score) { this.weight = weight; diff --git a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs index 67e86265ed..1c39cb309c 100644 --- a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs +++ b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs @@ -1,24 +1,24 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using OpenTK; +using osuTK; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Rulesets.Mods; using osu.Game.Screens.Select.Leaderboards; -using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; +using osu.Game.Scoring; namespace osu.Game.Overlays.Profile.Sections.Ranks { public abstract class DrawableProfileScore : DrawableProfileRow { private readonly ScoreModsContainer modsContainer; - protected readonly Score Score; + protected readonly ScoreInfo Score; - protected DrawableProfileScore(Score score) + protected DrawableProfileScore(ScoreInfo score) { Score = score; diff --git a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableTotalScore.cs b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableTotalScore.cs index 3a39ef5d61..a48468e4e5 100644 --- a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableTotalScore.cs +++ b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableTotalScore.cs @@ -4,13 +4,13 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Game.Graphics.Sprites; -using osu.Game.Rulesets.Scoring; +using osu.Game.Scoring; namespace osu.Game.Overlays.Profile.Sections.Ranks { public class DrawableTotalScore : DrawableProfileScore { - public DrawableTotalScore(Score score) + public DrawableTotalScore(ScoreInfo score) : base(score) { } diff --git a/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs b/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs index ed82c62e5c..1df07070a1 100644 --- a/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs @@ -7,8 +7,8 @@ using osu.Framework.Graphics.Containers; using osu.Game.Online.API.Requests; using osu.Game.Users; using System; +using System.Collections.Generic; using System.Linq; -using osu.Game.Online.API.Requests.Responses; namespace osu.Game.Overlays.Profile.Sections.Ranks { @@ -37,35 +37,36 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks request.Success += scores => Schedule(() => { foreach (var s in scores) - s.ApplyRuleset(Rulesets.GetRuleset(s.OnlineRulesetID)); - - ShowMoreButton.FadeTo(scores.Count == ItemsPerPage ? 1 : 0); - ShowMoreLoading.Hide(); + s.Ruleset = Rulesets.GetRuleset(s.RulesetID); if (!scores.Any() && VisiblePages == 1) { + ShowMoreButton.Hide(); + ShowMoreLoading.Hide(); MissingText.Show(); return; } - MissingText.Hide(); + IEnumerable drawableScores; - foreach (APIScore score in scores) + switch (type) { - DrawableProfileScore drawableScore; - - switch (type) - { - default: - drawableScore = new DrawablePerformanceScore(score, includeWeight ? Math.Pow(0.95, ItemsContainer.Count) : (double?)null); - break; - case ScoreType.Recent: - drawableScore = new DrawableTotalScore(score); - break; - } - - ItemsContainer.Add(drawableScore); + default: + drawableScores = scores.Select(score => new DrawablePerformanceScore(score, includeWeight ? Math.Pow(0.95, ItemsContainer.Count) : (double?)null)); + break; + case ScoreType.Recent: + drawableScores = scores.Select(score => new DrawableTotalScore(score)); + break; } + + LoadComponentsAsync(drawableScores, s => + { + MissingText.Hide(); + ShowMoreButton.FadeTo(scores.Count == ItemsPerPage ? 1 : 0); + ShowMoreLoading.Hide(); + + ItemsContainer.AddRange(s); + }); }); Api.Queue(request); diff --git a/osu.Game/Overlays/Profile/Sections/Ranks/ScoreModsContainer.cs b/osu.Game/Overlays/Profile/Sections/Ranks/ScoreModsContainer.cs index 90f476fa73..f67d333a0f 100644 --- a/osu.Game/Overlays/Profile/Sections/Ranks/ScoreModsContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/Ranks/ScoreModsContainer.cs @@ -1,7 +1,7 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using OpenTK; +using osuTK; using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.UI; using System.Collections.Generic; diff --git a/osu.Game/Overlays/SearchableList/DisplayStyleControl.cs b/osu.Game/Overlays/SearchableList/DisplayStyleControl.cs index 26a87230de..35888bee73 100644 --- a/osu.Game/Overlays/SearchableList/DisplayStyleControl.cs +++ b/osu.Game/Overlays/SearchableList/DisplayStyleControl.cs @@ -1,7 +1,7 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using OpenTK; +using osuTK; using osu.Framework.Configuration; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; diff --git a/osu.Game/Overlays/SearchableList/HeaderTabControl.cs b/osu.Game/Overlays/SearchableList/HeaderTabControl.cs index 62685eb323..07f1714e2c 100644 --- a/osu.Game/Overlays/SearchableList/HeaderTabControl.cs +++ b/osu.Game/Overlays/SearchableList/HeaderTabControl.cs @@ -1,7 +1,7 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using OpenTK.Graphics; +using osuTK.Graphics; using osu.Framework.Graphics.UserInterface; using osu.Game.Graphics.UserInterface; diff --git a/osu.Game/Overlays/SearchableList/SearchableListFilterControl.cs b/osu.Game/Overlays/SearchableList/SearchableListFilterControl.cs index 82a60a09e1..bd03ff9734 100644 --- a/osu.Game/Overlays/SearchableList/SearchableListFilterControl.cs +++ b/osu.Game/Overlays/SearchableList/SearchableListFilterControl.cs @@ -2,7 +2,7 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System; -using OpenTK.Graphics; +using osuTK.Graphics; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; diff --git a/osu.Game/Overlays/SearchableList/SearchableListHeader.cs b/osu.Game/Overlays/SearchableList/SearchableListHeader.cs index 9c4fe1c398..312197c7ae 100644 --- a/osu.Game/Overlays/SearchableList/SearchableListHeader.cs +++ b/osu.Game/Overlays/SearchableList/SearchableListHeader.cs @@ -2,8 +2,8 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System; -using OpenTK; -using OpenTK.Graphics; +using osuTK; +using osuTK.Graphics; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; diff --git a/osu.Game/Overlays/SearchableList/SearchableListOverlay.cs b/osu.Game/Overlays/SearchableList/SearchableListOverlay.cs index df249b0b88..2c97cbeb40 100644 --- a/osu.Game/Overlays/SearchableList/SearchableListOverlay.cs +++ b/osu.Game/Overlays/SearchableList/SearchableListOverlay.cs @@ -1,7 +1,7 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using OpenTK.Graphics; +using osuTK.Graphics; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; diff --git a/osu.Game/Overlays/SearchableList/SlimEnumDropdown.cs b/osu.Game/Overlays/SearchableList/SlimEnumDropdown.cs index 33ddb8f53f..4c7f3e1c31 100644 --- a/osu.Game/Overlays/SearchableList/SlimEnumDropdown.cs +++ b/osu.Game/Overlays/SearchableList/SlimEnumDropdown.cs @@ -1,12 +1,12 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using OpenTK.Graphics; +using osuTK.Graphics; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.UserInterface; using osu.Game.Graphics.UserInterface; -using OpenTK; +using osuTK; namespace osu.Game.Overlays.SearchableList { diff --git a/osu.Game/Overlays/Settings/Sections/Audio/AudioDevicesSettings.cs b/osu.Game/Overlays/Settings/Sections/Audio/AudioDevicesSettings.cs index d637eb96f6..ad79024f62 100644 --- a/osu.Game/Overlays/Settings/Sections/Audio/AudioDevicesSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Audio/AudioDevicesSettings.cs @@ -6,6 +6,7 @@ using osu.Framework.Audio; using osu.Framework.Graphics; using System.Collections.Generic; using System.Linq; +using osu.Game.Graphics.UserInterface; namespace osu.Game.Overlays.Settings.Sections.Audio { @@ -35,12 +36,12 @@ namespace osu.Game.Overlays.Settings.Sections.Audio private void updateItems() { - var deviceItems = new List> { new KeyValuePair("Default", string.Empty) }; - deviceItems.AddRange(audio.AudioDeviceNames.Select(d => new KeyValuePair(d, d))); + var deviceItems = new List { string.Empty }; + deviceItems.AddRange(audio.AudioDeviceNames); var preferredDeviceName = audio.AudioDevice.Value; - if (deviceItems.All(kv => kv.Value != preferredDeviceName)) - deviceItems.Add(new KeyValuePair(preferredDeviceName, preferredDeviceName)); + if (deviceItems.All(kv => kv != preferredDeviceName)) + deviceItems.Add(preferredDeviceName); // The option dropdown for audio device selection lists all audio // device names. Dropdowns, however, may not have multiple identical @@ -59,7 +60,7 @@ namespace osu.Game.Overlays.Settings.Sections.Audio Children = new Drawable[] { - dropdown = new SettingsDropdown() + dropdown = new AudioDeviceSettingsDropdown() }; updateItems(); @@ -69,5 +70,16 @@ namespace osu.Game.Overlays.Settings.Sections.Audio audio.OnNewDevice += onDeviceChanged; audio.OnLostDevice += onDeviceChanged; } + + private class AudioDeviceSettingsDropdown : SettingsDropdown + { + protected override OsuDropdown CreateDropdown() => new AudioDeviceDropdownControl { Items = Items }; + + private class AudioDeviceDropdownControl : DropdownControl + { + protected override string GenerateItemText(string item) + => string.IsNullOrEmpty(item) ? "Default" : base.GenerateItemText(item); + } + } } } diff --git a/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs b/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs index 11a3d36779..6623fbc73b 100644 --- a/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs @@ -10,11 +10,11 @@ using osu.Game.Configuration; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Online.API; -using OpenTK; +using osuTK; using osu.Game.Users; using System.ComponentModel; using osu.Game.Graphics; -using OpenTK.Graphics; +using osuTK.Graphics; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Input.Events; using RectangleF = osu.Framework.Graphics.Primitives.RectangleF; diff --git a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs index 254de6c6f7..685244e06b 100644 --- a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs @@ -6,9 +6,9 @@ using System.Drawing; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Configuration; -using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Game.Graphics.UserInterface; namespace osu.Game.Overlays.Settings.Sections.Graphics { @@ -85,7 +85,7 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics if (resolutions.Count > 1) { - resolutionSettingsContainer.Child = resolutionDropdown = new SettingsDropdown + resolutionSettingsContainer.Child = resolutionDropdown = new ResolutionSettingsDropdown { LabelText = "Resolution", ShowsDefaultIndicator = false, @@ -115,18 +115,36 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics }, true); } - private IReadOnlyList> getResolutions() + private IReadOnlyList getResolutions() { - var resolutions = new KeyValuePair("Default", new Size(9999, 9999)).Yield(); + var resolutions = new List { new Size(9999, 9999) }; if (game.Window != null) - resolutions = resolutions.Concat(game.Window.AvailableResolutions - .Where(r => r.Width >= 800 && r.Height >= 600) - .OrderByDescending(r => r.Width) - .ThenByDescending(r => r.Height) - .Select(res => new KeyValuePair($"{res.Width}x{res.Height}", new Size(res.Width, res.Height))) - .Distinct()); - return resolutions.ToList(); + { + resolutions.AddRange(game.Window.AvailableResolutions + .Where(r => r.Width >= 800 && r.Height >= 600) + .OrderByDescending(r => r.Width) + .ThenByDescending(r => r.Height) + .Select(res => new Size(res.Width, res.Height)) + .Distinct()); + } + + return resolutions; + } + + private class ResolutionSettingsDropdown : SettingsDropdown + { + protected override OsuDropdown CreateDropdown() => new ResolutionDropdownControl { Items = Items }; + + private class ResolutionDropdownControl : DropdownControl + { + protected override string GenerateItemText(Size item) + { + if (item == new Size(9999, 9999)) + return "Default"; + return $"{item.Width}x{item.Height}"; + } + } } } } diff --git a/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs b/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs index 7b9b7bf57f..c4d180790c 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs @@ -59,8 +59,8 @@ namespace osu.Game.Overlays.Settings.Sections.Input rawInputToggle.ValueChanged += enabled => { // this is temporary until we support per-handler settings. - const string raw_mouse_handler = @"OpenTKRawMouseHandler"; - const string standard_mouse_handler = @"OpenTKMouseHandler"; + const string raw_mouse_handler = @"OsuTKRawMouseHandler"; + const string standard_mouse_handler = @"OsuTKMouseHandler"; ignoredInputHandler.Value = enabled ? standard_mouse_handler : raw_mouse_handler; }; diff --git a/osu.Game/Overlays/Settings/Sections/MaintenanceSection.cs b/osu.Game/Overlays/Settings/Sections/MaintenanceSection.cs index aa933ca188..673a49ea8f 100644 --- a/osu.Game/Overlays/Settings/Sections/MaintenanceSection.cs +++ b/osu.Game/Overlays/Settings/Sections/MaintenanceSection.cs @@ -4,7 +4,7 @@ using osu.Framework.Graphics; using osu.Game.Graphics; using osu.Game.Overlays.Settings.Sections.Maintenance; -using OpenTK; +using osuTK; namespace osu.Game.Overlays.Settings.Sections { diff --git a/osu.Game/Overlays/Settings/Sections/Online/WebSettings.cs b/osu.Game/Overlays/Settings/Sections/Online/WebSettings.cs new file mode 100644 index 0000000000..62e307f323 --- /dev/null +++ b/osu.Game/Overlays/Settings/Sections/Online/WebSettings.cs @@ -0,0 +1,27 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Game.Configuration; + +namespace osu.Game.Overlays.Settings.Sections.Online +{ + public class WebSettings : SettingsSubsection + { + protected override string Header => "Web"; + + [BackgroundDependencyLoader] + private void load(OsuConfigManager config) + { + Children = new Drawable[] + { + new SettingsCheckbox + { + LabelText = "Warn about opening external links", + Bindable = config.GetBindable(OsuSetting.ExternalLinkWarning) + }, + }; + } + } +} diff --git a/osu.Game/Overlays/Settings/Sections/OnlineSection.cs b/osu.Game/Overlays/Settings/Sections/OnlineSection.cs index 28cb503288..3a2e52d2eb 100644 --- a/osu.Game/Overlays/Settings/Sections/OnlineSection.cs +++ b/osu.Game/Overlays/Settings/Sections/OnlineSection.cs @@ -3,6 +3,7 @@ using osu.Framework.Graphics; using osu.Game.Graphics; +using osu.Game.Overlays.Settings.Sections.Online; namespace osu.Game.Overlays.Settings.Sections { @@ -15,6 +16,7 @@ namespace osu.Game.Overlays.Settings.Sections { Children = new Drawable[] { + new WebSettings() }; } } diff --git a/osu.Game/Overlays/Settings/Sections/SkinSection.cs b/osu.Game/Overlays/Settings/Sections/SkinSection.cs index 15787d29f7..23f35d5d3a 100644 --- a/osu.Game/Overlays/Settings/Sections/SkinSection.cs +++ b/osu.Game/Overlays/Settings/Sections/SkinSection.cs @@ -1,26 +1,29 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // 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.Game.Configuration; using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; using osu.Game.Skinning; -using OpenTK; +using osuTK; namespace osu.Game.Overlays.Settings.Sections { public class SkinSection : SettingsSection { - private SettingsDropdown skinDropdown; + private SkinSettingsDropdown skinDropdown; public override string Header => "Skin"; public override FontAwesome Icon => FontAwesome.fa_paint_brush; + private readonly Bindable dropdownBindable = new Bindable(); + private readonly Bindable configBindable = new Bindable(); + private SkinManager skins; [BackgroundDependencyLoader] @@ -31,7 +34,7 @@ namespace osu.Game.Overlays.Settings.Sections FlowContent.Spacing = new Vector2(0, 5); Children = new Drawable[] { - skinDropdown = new SettingsDropdown(), + skinDropdown = new SkinSettingsDropdown(), new SettingsSlider { LabelText = "Menu cursor size", @@ -54,19 +57,28 @@ namespace osu.Game.Overlays.Settings.Sections skins.ItemAdded += itemAdded; skins.ItemRemoved += itemRemoved; - skinDropdown.Items = skins.GetAllUsableSkins().Select(s => new KeyValuePair(s.ToString(), s.ID)); + config.BindWith(OsuSetting.Skin, configBindable); - var skinBindable = config.GetBindable(OsuSetting.Skin); + skinDropdown.Bindable = dropdownBindable; + skinDropdown.Items = skins.GetAllUsableSkins().ToArray(); // Todo: This should not be necessary when OsuConfigManager is databased - if (skinDropdown.Items.All(s => s.Value != skinBindable.Value)) - skinBindable.Value = 0; + if (skinDropdown.Items.All(s => s.ID != configBindable.Value)) + configBindable.Value = 0; - skinDropdown.Bindable = skinBindable; + configBindable.BindValueChanged(v => dropdownBindable.Value = skinDropdown.Items.Single(s => s.ID == v), true); + dropdownBindable.BindValueChanged(v => configBindable.Value = v.ID); } - private void itemRemoved(SkinInfo s) => skinDropdown.Items = skinDropdown.Items.Where(i => i.Value != s.ID); - private void itemAdded(SkinInfo s) => skinDropdown.Items = skinDropdown.Items.Append(new KeyValuePair(s.ToString(), s.ID)); + private void itemRemoved(SkinInfo s) => Schedule(() => skinDropdown.Items = skinDropdown.Items.Where(i => i.ID != s.ID).ToArray()); + + private void itemAdded(SkinInfo s, bool existing, bool silent) + { + if (existing) + return; + + Schedule(() => skinDropdown.Items = skinDropdown.Items.Append(s).ToArray()); + } protected override void Dispose(bool isDisposing) { @@ -83,5 +95,15 @@ namespace osu.Game.Overlays.Settings.Sections { public override string TooltipText => Current.Value.ToString(@"0.##x"); } + + private class SkinSettingsDropdown : SettingsDropdown + { + protected override OsuDropdown CreateDropdown() => new SkinDropdownControl { Items = Items }; + + private class SkinDropdownControl : DropdownControl + { + protected override string GenerateItemText(SkinInfo item) => item.ToString(); + } + } } } diff --git a/osu.Game/Overlays/Settings/SettingsDropdown.cs b/osu.Game/Overlays/Settings/SettingsDropdown.cs index 33a8af7d91..1c3eb6c245 100644 --- a/osu.Game/Overlays/Settings/SettingsDropdown.cs +++ b/osu.Game/Overlays/Settings/SettingsDropdown.cs @@ -2,36 +2,41 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System.Collections.Generic; +using System.Linq; using osu.Framework.Graphics; -using osu.Framework.Graphics.UserInterface; using osu.Game.Graphics.UserInterface; namespace osu.Game.Overlays.Settings { public class SettingsDropdown : SettingsItem { - private Dropdown dropdown; + protected new OsuDropdown Control => (OsuDropdown)base.Control; - private IEnumerable> items = new KeyValuePair[] { }; - public IEnumerable> Items + private IEnumerable items = Enumerable.Empty(); + + public IEnumerable Items { - get - { - return items; - } + get => items; set { items = value; - if (dropdown != null) - dropdown.Items = value; + + if (Control != null) + Control.Items = value; } } - protected override Drawable CreateControl() => dropdown = new OsuDropdown + protected sealed override Drawable CreateControl() => CreateDropdown(); + + protected virtual OsuDropdown CreateDropdown() => new DropdownControl { Items = Items }; + + protected class DropdownControl : OsuDropdown { - Margin = new MarginPadding { Top = 5 }, - RelativeSizeAxes = Axes.X, - Items = Items, - }; + public DropdownControl() + { + Margin = new MarginPadding { Top = 5 }; + RelativeSizeAxes = Axes.X; + } + } } } diff --git a/osu.Game/Overlays/Settings/SettingsEnumDropdown.cs b/osu.Game/Overlays/Settings/SettingsEnumDropdown.cs index 64811137a6..2a98b80816 100644 --- a/osu.Game/Overlays/Settings/SettingsEnumDropdown.cs +++ b/osu.Game/Overlays/Settings/SettingsEnumDropdown.cs @@ -8,10 +8,15 @@ namespace osu.Game.Overlays.Settings { public class SettingsEnumDropdown : SettingsDropdown { - protected override Drawable CreateControl() => new OsuEnumDropdown + protected override OsuDropdown CreateDropdown() => new DropdownControl(); + + protected class DropdownControl : OsuEnumDropdown { - Margin = new MarginPadding { Top = 5 }, - RelativeSizeAxes = Axes.X, - }; + public DropdownControl() + { + Margin = new MarginPadding { Top = 5 }; + RelativeSizeAxes = Axes.X; + } + } } } diff --git a/osu.Game/Overlays/Settings/SettingsFooter.cs b/osu.Game/Overlays/Settings/SettingsFooter.cs index 909fc20446..ac1548f00d 100644 --- a/osu.Game/Overlays/Settings/SettingsFooter.cs +++ b/osu.Game/Overlays/Settings/SettingsFooter.cs @@ -9,8 +9,8 @@ using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Rulesets; -using OpenTK; -using OpenTK.Graphics; +using osuTK; +using osuTK.Graphics; using DebugUtils = osu.Game.Utils.DebugUtils; namespace osu.Game.Overlays.Settings diff --git a/osu.Game/Overlays/Settings/SettingsItem.cs b/osu.Game/Overlays/Settings/SettingsItem.cs index 4f947cd812..1d7e6350ae 100644 --- a/osu.Game/Overlays/Settings/SettingsItem.cs +++ b/osu.Game/Overlays/Settings/SettingsItem.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; using osu.Framework.Allocation; -using OpenTK.Graphics; +using osuTK.Graphics; using osu.Framework.Configuration; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; @@ -15,7 +15,7 @@ using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; -using OpenTK; +using osuTK; namespace osu.Game.Overlays.Settings { diff --git a/osu.Game/Overlays/Settings/SettingsSection.cs b/osu.Game/Overlays/Settings/SettingsSection.cs index 58714208c0..b7fe748bad 100644 --- a/osu.Game/Overlays/Settings/SettingsSection.cs +++ b/osu.Game/Overlays/Settings/SettingsSection.cs @@ -1,8 +1,8 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using OpenTK; -using OpenTK.Graphics; +using osuTK; +using osuTK.Graphics; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; diff --git a/osu.Game/Overlays/Settings/SettingsSubsection.cs b/osu.Game/Overlays/Settings/SettingsSubsection.cs index 9296972749..94a660d1c8 100644 --- a/osu.Game/Overlays/Settings/SettingsSubsection.cs +++ b/osu.Game/Overlays/Settings/SettingsSubsection.cs @@ -1,7 +1,7 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using OpenTK; +using osuTK; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Graphics.Sprites; diff --git a/osu.Game/Overlays/Settings/SettingsTextBox.cs b/osu.Game/Overlays/Settings/SettingsTextBox.cs index ce9218bbe7..fbfb5481c5 100644 --- a/osu.Game/Overlays/Settings/SettingsTextBox.cs +++ b/osu.Game/Overlays/Settings/SettingsTextBox.cs @@ -8,6 +8,10 @@ namespace osu.Game.Overlays.Settings { public class SettingsTextBox : SettingsItem { - protected override Drawable CreateControl() => new OsuTextBox(); + protected override Drawable CreateControl() => new OsuTextBox + { + Margin = new MarginPadding { Top = 5 }, + RelativeSizeAxes = Axes.X, + }; } } diff --git a/osu.Game/Overlays/Settings/Sidebar.cs b/osu.Game/Overlays/Settings/Sidebar.cs index 862011b6e2..e43d4682b9 100644 --- a/osu.Game/Overlays/Settings/Sidebar.cs +++ b/osu.Game/Overlays/Settings/Sidebar.cs @@ -4,8 +4,8 @@ using System; using System.Linq; using osu.Framework; -using OpenTK; -using OpenTK.Graphics; +using osuTK; +using osuTK.Graphics; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; diff --git a/osu.Game/Overlays/Settings/SidebarButton.cs b/osu.Game/Overlays/Settings/SidebarButton.cs index b6d1cf609e..1b23273c3c 100644 --- a/osu.Game/Overlays/Settings/SidebarButton.cs +++ b/osu.Game/Overlays/Settings/SidebarButton.cs @@ -2,8 +2,8 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System; -using OpenTK; -using OpenTK.Graphics; +using osuTK; +using osuTK.Graphics; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; diff --git a/osu.Game/Overlays/SettingsOverlay.cs b/osu.Game/Overlays/SettingsOverlay.cs index c971ab5005..df81421ea7 100644 --- a/osu.Game/Overlays/SettingsOverlay.cs +++ b/osu.Game/Overlays/SettingsOverlay.cs @@ -4,8 +4,8 @@ using System; using System.Collections.Generic; using System.Linq; -using OpenTK; -using OpenTK.Graphics; +using osuTK; +using osuTK.Graphics; using osu.Framework.Allocation; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; diff --git a/osu.Game/Overlays/Social/FilterControl.cs b/osu.Game/Overlays/Social/FilterControl.cs index 1b9ce1033f..72c3791f6f 100644 --- a/osu.Game/Overlays/Social/FilterControl.cs +++ b/osu.Game/Overlays/Social/FilterControl.cs @@ -1,7 +1,7 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using OpenTK.Graphics; +using osuTK.Graphics; using osu.Framework.Graphics; using osu.Game.Graphics; using osu.Game.Overlays.SearchableList; diff --git a/osu.Game/Overlays/Social/Header.cs b/osu.Game/Overlays/Social/Header.cs index df97bdf448..a23a96cdf4 100644 --- a/osu.Game/Overlays/Social/Header.cs +++ b/osu.Game/Overlays/Social/Header.cs @@ -2,7 +2,7 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using osu.Game.Overlays.SearchableList; -using OpenTK.Graphics; +using osuTK.Graphics; using osu.Framework.Graphics; using osu.Game.Graphics.Sprites; using osu.Framework.Graphics.Containers; diff --git a/osu.Game/Overlays/Social/SocialPanel.cs b/osu.Game/Overlays/Social/SocialPanel.cs index cfee639d53..4dfeda9041 100644 --- a/osu.Game/Overlays/Social/SocialPanel.cs +++ b/osu.Game/Overlays/Social/SocialPanel.cs @@ -1,8 +1,8 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using OpenTK; -using OpenTK.Graphics; +using osuTK; +using osuTK.Graphics; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; diff --git a/osu.Game/Overlays/SocialOverlay.cs b/osu.Game/Overlays/SocialOverlay.cs index 222035ab65..bd6cd3cba6 100644 --- a/osu.Game/Overlays/SocialOverlay.cs +++ b/osu.Game/Overlays/SocialOverlay.cs @@ -4,8 +4,8 @@ using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; -using OpenTK; -using OpenTK.Graphics; +using osuTK; +using osuTK.Graphics; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Graphics; diff --git a/osu.Game/Overlays/Toolbar/Toolbar.cs b/osu.Game/Overlays/Toolbar/Toolbar.cs index 611b42383e..341f5c3fd1 100644 --- a/osu.Game/Overlays/Toolbar/Toolbar.cs +++ b/osu.Game/Overlays/Toolbar/Toolbar.cs @@ -7,7 +7,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Game.Graphics; -using OpenTK; +using osuTK; using osu.Framework.Graphics.Shapes; using osu.Framework.Allocation; using osu.Framework.Configuration; diff --git a/osu.Game/Overlays/Toolbar/ToolbarButton.cs b/osu.Game/Overlays/Toolbar/ToolbarButton.cs index 5cb26974e6..a9d8be6ff2 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarButton.cs @@ -8,8 +8,8 @@ using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; using osu.Game.Graphics.Backgrounds; using osu.Game.Graphics.Sprites; -using OpenTK; -using OpenTK.Graphics; +using osuTK; +using osuTK.Graphics; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; using osu.Game.Graphics.Containers; diff --git a/osu.Game/Overlays/Toolbar/ToolbarNotificationButton.cs b/osu.Game/Overlays/Toolbar/ToolbarNotificationButton.cs index ef7e5f07bb..42d9527432 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarNotificationButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarNotificationButton.cs @@ -8,8 +8,8 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; -using OpenTK; -using OpenTK.Graphics; +using osuTK; +using osuTK.Graphics; namespace osu.Game.Overlays.Toolbar { diff --git a/osu.Game/Overlays/Toolbar/ToolbarRulesetButton.cs b/osu.Game/Overlays/Toolbar/ToolbarRulesetButton.cs index bbdf796e7a..b0801d95b0 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarRulesetButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarRulesetButton.cs @@ -3,7 +3,7 @@ using osu.Framework.Graphics.Containers; using osu.Game.Rulesets; -using OpenTK.Graphics; +using osuTK.Graphics; namespace osu.Game.Overlays.Toolbar { diff --git a/osu.Game/Overlays/Toolbar/ToolbarRulesetSelector.cs b/osu.Game/Overlays/Toolbar/ToolbarRulesetSelector.cs index fa35e53531..19777bfbeb 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarRulesetSelector.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarRulesetSelector.cs @@ -6,9 +6,9 @@ using osu.Framework.Allocation; using osu.Framework.Caching; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using OpenTK; -using OpenTK.Input; -using OpenTK.Graphics; +using osuTK; +using osuTK.Input; +using osuTK.Graphics; using osu.Framework.Configuration; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; diff --git a/osu.Game/Overlays/Toolbar/ToolbarUserArea.cs b/osu.Game/Overlays/Toolbar/ToolbarUserArea.cs index c30d58b0d6..d0cb3c0ff2 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarUserArea.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarUserArea.cs @@ -4,7 +4,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using OpenTK; +using osuTK; using RectangleF = osu.Framework.Graphics.Primitives.RectangleF; namespace osu.Game.Overlays.Toolbar diff --git a/osu.Game/Overlays/Toolbar/ToolbarUserButton.cs b/osu.Game/Overlays/Toolbar/ToolbarUserButton.cs index b01a4c48b6..017d748600 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarUserButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarUserButton.cs @@ -7,8 +7,8 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Online.API; using osu.Game.Users; -using OpenTK; -using OpenTK.Graphics; +using osuTK; +using osuTK.Graphics; namespace osu.Game.Overlays.Toolbar { diff --git a/osu.Game/Overlays/UserProfileOverlay.cs b/osu.Game/Overlays/UserProfileOverlay.cs index c106446fe0..c15f464c7c 100644 --- a/osu.Game/Overlays/UserProfileOverlay.cs +++ b/osu.Game/Overlays/UserProfileOverlay.cs @@ -16,8 +16,8 @@ using osu.Game.Online.API.Requests; using osu.Game.Overlays.Profile; using osu.Game.Overlays.Profile.Sections; using osu.Game.Users; -using OpenTK; -using OpenTK.Graphics; +using osuTK; +using osuTK.Graphics; namespace osu.Game.Overlays { diff --git a/osu.Game/Overlays/Volume/MuteButton.cs b/osu.Game/Overlays/Volume/MuteButton.cs index a099a10096..e31b349827 100644 --- a/osu.Game/Overlays/Volume/MuteButton.cs +++ b/osu.Game/Overlays/Volume/MuteButton.cs @@ -11,8 +11,8 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; using osu.Game.Graphics; -using OpenTK; -using OpenTK.Graphics; +using osuTK; +using osuTK.Graphics; namespace osu.Game.Overlays.Volume { diff --git a/osu.Game/Overlays/Volume/VolumeMeter.cs b/osu.Game/Overlays/Volume/VolumeMeter.cs index 86be652c8c..1263ecd303 100644 --- a/osu.Game/Overlays/Volume/VolumeMeter.cs +++ b/osu.Game/Overlays/Volume/VolumeMeter.cs @@ -15,8 +15,8 @@ using osu.Framework.Input.Events; using osu.Framework.MathUtils; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; -using OpenTK; -using OpenTK.Graphics; +using osuTK; +using osuTK.Graphics; namespace osu.Game.Overlays.Volume { @@ -25,7 +25,7 @@ namespace osu.Game.Overlays.Volume private CircularProgress volumeCircle; private CircularProgress volumeCircleGlow; - public BindableDouble Bindable { get; } = new BindableDouble { MinValue = 0, MaxValue = 1 }; + public BindableDouble Bindable { get; } = new BindableDouble { MinValue = 0, MaxValue = 1, Precision = 0.01 }; private readonly float circleSize; private readonly Color4 meterColour; private readonly string name; @@ -222,21 +222,25 @@ namespace osu.Game.Overlays.Volume private set => Bindable.Value = value; } - private const float adjust_step = 0.05f; + private const double adjust_step = 0.05; public void Increase(double amount = 1, bool isPrecise = false) => adjust(amount, isPrecise); public void Decrease(double amount = 1, bool isPrecise = false) => adjust(-amount, isPrecise); // because volume precision is set to 0.01, this local is required to keep track of more precise adjustments and only apply when possible. - private double adjustAccumulator; + private double scrollAccumulation; private void adjust(double delta, bool isPrecise) { - adjustAccumulator += delta * adjust_step * (isPrecise ? 0.1 : 1); - if (Math.Abs(adjustAccumulator) < Bindable.Precision) - return; - Volume += adjustAccumulator; - adjustAccumulator = 0; + scrollAccumulation += delta * adjust_step * (isPrecise ? 0.1 : 1); + + var precision = Bindable.Precision; + + while (Precision.AlmostBigger(Math.Abs(scrollAccumulation), precision)) + { + Volume += Math.Sign(scrollAccumulation) * precision; + scrollAccumulation = scrollAccumulation < 0 ? Math.Min(0, scrollAccumulation + precision) : Math.Max(0, scrollAccumulation - precision); + } } protected override bool OnScroll(ScrollEvent e) diff --git a/osu.Game/Overlays/VolumeOverlay.cs b/osu.Game/Overlays/VolumeOverlay.cs index d45d097a09..cbe61b5896 100644 --- a/osu.Game/Overlays/VolumeOverlay.cs +++ b/osu.Game/Overlays/VolumeOverlay.cs @@ -14,8 +14,8 @@ using osu.Framework.Threading; using osu.Game.Graphics; using osu.Game.Input.Bindings; using osu.Game.Overlays.Volume; -using OpenTK; -using OpenTK.Graphics; +using osuTK; +using osuTK.Graphics; namespace osu.Game.Overlays { diff --git a/osu.Game/Rulesets/Replays/Legacy/LegacyReplayFrame.cs b/osu.Game/Replays/Legacy/LegacyReplayFrame.cs similarity index 94% rename from osu.Game/Rulesets/Replays/Legacy/LegacyReplayFrame.cs rename to osu.Game/Replays/Legacy/LegacyReplayFrame.cs index 5bd56e0cc0..ac9fd96ae6 100644 --- a/osu.Game/Rulesets/Replays/Legacy/LegacyReplayFrame.cs +++ b/osu.Game/Replays/Legacy/LegacyReplayFrame.cs @@ -1,9 +1,10 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using OpenTK; +using osu.Game.Rulesets.Replays; +using osuTK; -namespace osu.Game.Rulesets.Replays.Legacy +namespace osu.Game.Replays.Legacy { public class LegacyReplayFrame : ReplayFrame { diff --git a/osu.Game/Rulesets/Replays/Legacy/ReplayButtonState.cs b/osu.Game/Replays/Legacy/ReplayButtonState.cs similarity index 88% rename from osu.Game/Rulesets/Replays/Legacy/ReplayButtonState.cs rename to osu.Game/Replays/Legacy/ReplayButtonState.cs index ee09414287..ffeff55e96 100644 --- a/osu.Game/Rulesets/Replays/Legacy/ReplayButtonState.cs +++ b/osu.Game/Replays/Legacy/ReplayButtonState.cs @@ -3,7 +3,7 @@ using System; -namespace osu.Game.Rulesets.Replays.Legacy +namespace osu.Game.Replays.Legacy { [Flags] public enum ReplayButtonState diff --git a/osu.Game/Rulesets/Replays/Replay.cs b/osu.Game/Replays/Replay.cs similarity index 77% rename from osu.Game/Rulesets/Replays/Replay.cs rename to osu.Game/Replays/Replay.cs index 8fbe4c8194..bb6d9e7637 100644 --- a/osu.Game/Rulesets/Replays/Replay.cs +++ b/osu.Game/Replays/Replay.cs @@ -2,13 +2,12 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System.Collections.Generic; -using osu.Game.Users; +using osu.Game.Rulesets.Replays; -namespace osu.Game.Rulesets.Replays +namespace osu.Game.Replays { public class Replay { - public User User; public List Frames = new List(); } } diff --git a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs index 0de0a620e3..25fd49ff9b 100644 --- a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs +++ b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs @@ -66,7 +66,7 @@ namespace osu.Game.Rulesets.Difficulty { case 0: // Initial-case: Empty current set - yield return new NoModMod(); + yield return new ModNoMod(); break; case 1: yield return currentSet.Single(); diff --git a/osu.Game/Rulesets/Difficulty/PerformanceCalculator.cs b/osu.Game/Rulesets/Difficulty/PerformanceCalculator.cs index ba783ee87b..48a68928de 100644 --- a/osu.Game/Rulesets/Difficulty/PerformanceCalculator.cs +++ b/osu.Game/Rulesets/Difficulty/PerformanceCalculator.cs @@ -7,7 +7,7 @@ using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Scoring; +using osu.Game.Scoring; namespace osu.Game.Rulesets.Difficulty { @@ -17,11 +17,11 @@ namespace osu.Game.Rulesets.Difficulty protected readonly Ruleset Ruleset; protected readonly IBeatmap Beatmap; - protected readonly Score Score; + protected readonly ScoreInfo Score; protected double TimeRate { get; private set; } = 1; - protected PerformanceCalculator(Ruleset ruleset, WorkingBeatmap beatmap, Score score) + protected PerformanceCalculator(Ruleset ruleset, WorkingBeatmap beatmap, ScoreInfo score) { Ruleset = ruleset; Score = score; diff --git a/osu.Game/Rulesets/Edit/EditRulesetContainer.cs b/osu.Game/Rulesets/Edit/EditRulesetContainer.cs new file mode 100644 index 0000000000..6f81d47431 --- /dev/null +++ b/osu.Game/Rulesets/Edit/EditRulesetContainer.cs @@ -0,0 +1,106 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Linq; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.UI; + +namespace osu.Game.Rulesets.Edit +{ + public abstract class EditRulesetContainer : CompositeDrawable + { + /// + /// The contained by this . + /// + public abstract Playfield Playfield { get; } + + internal EditRulesetContainer() + { + RelativeSizeAxes = Axes.Both; + } + + /// + /// Adds a to the and displays a visual representation of it. + /// + /// The to add. + /// The visual representation of . + internal abstract DrawableHitObject Add(HitObject hitObject); + + /// + /// Removes a from the and the display. + /// + /// The to remove. + /// The visual representation of the removed . + internal abstract DrawableHitObject Remove(HitObject hitObject); + } + + public class EditRulesetContainer : EditRulesetContainer + where TObject : HitObject + { + public override Playfield Playfield => rulesetContainer.Playfield; + + private Ruleset ruleset => rulesetContainer.Ruleset; + private Beatmap beatmap => rulesetContainer.Beatmap; + + private readonly RulesetContainer rulesetContainer; + + public EditRulesetContainer(RulesetContainer rulesetContainer) + { + this.rulesetContainer = rulesetContainer; + + InternalChild = rulesetContainer; + + Playfield.DisplayJudgements.Value = false; + } + + internal override DrawableHitObject Add(HitObject hitObject) + { + var tObject = (TObject)hitObject; + + // Add to beatmap, preserving sorting order + var insertionIndex = beatmap.HitObjects.FindLastIndex(h => h.StartTime <= hitObject.StartTime); + beatmap.HitObjects.Insert(insertionIndex + 1, tObject); + + // Process object + var processor = ruleset.CreateBeatmapProcessor(beatmap); + + processor?.PreProcess(); + tObject.ApplyDefaults(beatmap.ControlPointInfo, beatmap.BeatmapInfo.BaseDifficulty); + processor?.PostProcess(); + + // Add visual representation + var drawableObject = rulesetContainer.GetVisualRepresentation(tObject); + + rulesetContainer.Playfield.Add(drawableObject); + rulesetContainer.Playfield.PostProcess(); + + return drawableObject; + } + + internal override DrawableHitObject Remove(HitObject hitObject) + { + var tObject = (TObject)hitObject; + + // Remove from beatmap + beatmap.HitObjects.Remove(tObject); + + // Process the beatmap + var processor = ruleset.CreateBeatmapProcessor(beatmap); + + processor?.PreProcess(); + processor?.PostProcess(); + + // Remove visual representation + var drawableObject = Playfield.AllHitObjects.Single(d => d.HitObject == hitObject); + + rulesetContainer.Playfield.Remove(drawableObject); + rulesetContainer.Playfield.PostProcess(); + + return drawableObject; + } + } +} diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index 8060ac742a..da7c3dda6a 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -8,35 +8,41 @@ using osu.Framework.Allocation; using osu.Framework.Configuration; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Input; using osu.Framework.Logging; using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Rulesets.Configuration; using osu.Game.Rulesets.Edit.Tools; +using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.UI; -using osu.Game.Screens.Edit.Screens.Compose.Layers; -using osu.Game.Screens.Edit.Screens.Compose.RadioButtons; +using osu.Game.Screens.Edit.Components.RadioButtons; +using osu.Game.Screens.Edit.Compose.Components; namespace osu.Game.Rulesets.Edit { public abstract class HitObjectComposer : CompositeDrawable { - private readonly Ruleset ruleset; + public IEnumerable HitObjects => RulesetContainer.Playfield.AllHitObjects; - public IEnumerable HitObjects => rulesetContainer.Playfield.AllHitObjects; + protected readonly Ruleset Ruleset; + + protected readonly IBindable Beatmap = new Bindable(); - protected ICompositionTool CurrentTool { get; private set; } protected IRulesetConfigManager Config { get; private set; } private readonly List layerContainers = new List(); - private readonly IBindable beatmap = new Bindable(); - private RulesetContainer rulesetContainer; + protected EditRulesetContainer RulesetContainer { get; private set; } - protected HitObjectComposer(Ruleset ruleset) + private BlueprintContainer blueprintContainer; + + private InputManager inputManager; + + internal HitObjectComposer(Ruleset ruleset) { - this.ruleset = ruleset; + Ruleset = ruleset; RelativeSizeAxes = Axes.Both; } @@ -44,12 +50,12 @@ namespace osu.Game.Rulesets.Edit [BackgroundDependencyLoader] private void load(IBindableBeatmap beatmap, IFrameBasedClock framedClock) { - this.beatmap.BindTo(beatmap); + Beatmap.BindTo(beatmap); try { - rulesetContainer = CreateRulesetContainer(ruleset, beatmap.Value); - rulesetContainer.Clock = framedClock; + RulesetContainer = CreateRulesetContainer(); + RulesetContainer.Clock = framedClock; } catch (Exception e) { @@ -57,14 +63,11 @@ namespace osu.Game.Rulesets.Edit return; } - var layerBelowRuleset = new BorderLayer - { - RelativeSizeAxes = Axes.Both, - Child = CreateLayerContainer() - }; + var layerBelowRuleset = CreateLayerContainer(); + layerBelowRuleset.Child = new EditorPlayfieldBorder { RelativeSizeAxes = Axes.Both }; var layerAboveRuleset = CreateLayerContainer(); - layerAboveRuleset.Child = new HitObjectMaskLayer(); + layerAboveRuleset.Child = blueprintContainer = new BlueprintContainer(); layerContainers.Add(layerBelowRuleset); layerContainers.Add(layerAboveRuleset); @@ -94,7 +97,7 @@ namespace osu.Game.Rulesets.Edit Children = new Drawable[] { layerBelowRuleset, - rulesetContainer, + RulesetContainer, layerAboveRuleset } } @@ -107,64 +110,88 @@ namespace osu.Game.Rulesets.Edit }; toolboxCollection.Items = - CompositionTools.Select(t => new RadioButton(t.Name, () => setCompositionTool(t))) - .Prepend(new RadioButton("Select", () => setCompositionTool(null))) + CompositionTools.Select(t => new RadioButton(t.Name, () => blueprintContainer.CurrentTool = t)) + .Prepend(new RadioButton("Select", () => blueprintContainer.CurrentTool = null)) .ToList(); toolboxCollection.Items[0].Select(); } + protected override void LoadComplete() + { + base.LoadComplete(); + + inputManager = GetContainingInputManager(); + } + protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) { var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); dependencies.CacheAs(this); - Config = dependencies.Get().GetConfigFor(ruleset); + Config = dependencies.Get().GetConfigFor(Ruleset); return dependencies; } - protected override void LoadComplete() - { - base.LoadComplete(); - - rulesetContainer.Playfield.DisplayJudgements.Value = false; - } - protected override void UpdateAfterChildren() { base.UpdateAfterChildren(); layerContainers.ForEach(l => { - l.Anchor = rulesetContainer.Playfield.Anchor; - l.Origin = rulesetContainer.Playfield.Origin; - l.Position = rulesetContainer.Playfield.Position; - l.Size = rulesetContainer.Playfield.Size; + l.Anchor = RulesetContainer.Playfield.Anchor; + l.Origin = RulesetContainer.Playfield.Origin; + l.Position = RulesetContainer.Playfield.Position; + l.Size = RulesetContainer.Playfield.Size; }); } - private void setCompositionTool(ICompositionTool tool) => CurrentTool = tool; - - protected virtual RulesetContainer CreateRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap) => ruleset.CreateRulesetContainerWith(beatmap); - - protected abstract IReadOnlyList CompositionTools { get; } + /// + /// Whether the user's cursor is currently in an area of the that is valid for placement. + /// + public virtual bool CursorInPlacementArea => RulesetContainer.Playfield.ReceivePositionalInputAt(inputManager.CurrentState.Mouse.Position); /// - /// Creates a for a specific . + /// Adds a to the and visualises it. + /// + /// The to add. + public void Add(HitObject hitObject) => blueprintContainer.AddBlueprintFor(RulesetContainer.Add(hitObject)); + + public void Remove(HitObject hitObject) => blueprintContainer.RemoveBlueprintFor(RulesetContainer.Remove(hitObject)); + + internal abstract EditRulesetContainer CreateRulesetContainer(); + + protected abstract IReadOnlyList CompositionTools { get; } + + /// + /// Creates a for a specific . /// /// The to create the overlay for. - public virtual HitObjectMask CreateMaskFor(DrawableHitObject hitObject) => null; + public virtual SelectionBlueprint CreateBlueprintFor(DrawableHitObject hitObject) => null; /// - /// Creates a which outlines s - /// and handles hitobject pattern adjustments. + /// Creates a which outlines s and handles movement of selections. /// - public virtual MaskSelection CreateMaskSelection() => new MaskSelection(); + public virtual SelectionHandler CreateSelectionHandler() => new SelectionHandler(); /// /// Creates a which provides a layer above or below the . /// protected virtual Container CreateLayerContainer() => new Container { RelativeSizeAxes = Axes.Both }; } + + public abstract class HitObjectComposer : HitObjectComposer + where TObject : HitObject + { + protected HitObjectComposer(Ruleset ruleset) + : base(ruleset) + { + } + + internal override EditRulesetContainer CreateRulesetContainer() + => new EditRulesetContainer(CreateRulesetContainer(Ruleset, Beatmap.Value)); + + protected abstract RulesetContainer CreateRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap); + } } diff --git a/osu.Game/Rulesets/Edit/PlacementBlueprint.cs b/osu.Game/Rulesets/Edit/PlacementBlueprint.cs new file mode 100644 index 0000000000..1d568313ca --- /dev/null +++ b/osu.Game/Rulesets/Edit/PlacementBlueprint.cs @@ -0,0 +1,137 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using osu.Framework; +using osu.Framework.Allocation; +using osu.Framework.Configuration; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Input.Events; +using osu.Framework.Timing; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Objects; +using osu.Game.Screens.Edit.Compose; +using osuTK; + +namespace osu.Game.Rulesets.Edit +{ + /// + /// A blueprint which governs the creation of a new to actualisation. + /// + public abstract class PlacementBlueprint : CompositeDrawable, IStateful + { + /// + /// Invoked when has changed. + /// + public event Action StateChanged; + + /// + /// Whether the is currently being placed, but has not necessarily finished being placed. + /// + public bool PlacementBegun { get; private set; } + + /// + /// The that is being placed. + /// + protected readonly HitObject HitObject; + + protected IClock EditorClock { get; private set; } + + private readonly IBindable beatmap = new Bindable(); + + [Resolved] + private IPlacementHandler placementHandler { get; set; } + + protected PlacementBlueprint(HitObject hitObject) + { + HitObject = hitObject; + + RelativeSizeAxes = Axes.Both; + + // This is required to allow the blueprint's position to be updated via OnMouseMove/Handle + // on the same frame it is made visible via a PlacementState change. + AlwaysPresent = true; + + Alpha = 0; + } + + [BackgroundDependencyLoader] + private void load(IBindableBeatmap beatmap, IAdjustableClock clock) + { + this.beatmap.BindTo(beatmap); + + EditorClock = clock; + + ApplyDefaultsToHitObject(); + } + + private PlacementState state; + + public PlacementState State + { + get => state; + set + { + if (state == value) + return; + state = value; + + if (state == PlacementState.Shown) + Show(); + else + Hide(); + + StateChanged?.Invoke(value); + } + } + + /// + /// Signals that the placement of has started. + /// + protected void BeginPlacement() + { + placementHandler.BeginPlacement(HitObject); + PlacementBegun = true; + } + + /// + /// Signals that the placement of has finished. + /// This will destroy this , and add the to the . + /// + protected void EndPlacement() + { + if (!PlacementBegun) + BeginPlacement(); + placementHandler.EndPlacement(HitObject); + } + + /// + /// Invokes , refreshing and parameters for the . + /// + protected void ApplyDefaultsToHitObject() => HitObject.ApplyDefaults(beatmap.Value.Beatmap.ControlPointInfo, beatmap.Value.Beatmap.BeatmapInfo.BaseDifficulty); + + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => Parent?.ReceivePositionalInputAt(screenSpacePos) ?? false; + + protected override bool Handle(UIEvent e) + { + base.Handle(e); + + switch (e) + { + case ScrollEvent _: + return false; + case MouseEvent _: + return true; + default: + return false; + } + } + } + + public enum PlacementState + { + Hidden, + Shown, + } +} diff --git a/osu.Game/Rulesets/Edit/HitObjectMask.cs b/osu.Game/Rulesets/Edit/SelectionBlueprint.cs similarity index 67% rename from osu.Game/Rulesets/Edit/HitObjectMask.cs rename to osu.Game/Rulesets/Edit/SelectionBlueprint.cs index 636ea418f3..ec4df01e55 100644 --- a/osu.Game/Rulesets/Edit/HitObjectMask.cs +++ b/osu.Game/Rulesets/Edit/SelectionBlueprint.cs @@ -3,44 +3,45 @@ using System; using osu.Framework; +using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Primitives; using osu.Framework.Input.Events; using osu.Framework.Input.States; using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets.Objects.Drawables; -using OpenTK; +using osuTK; namespace osu.Game.Rulesets.Edit { /// - /// A mask placed above a adding editing functionality. + /// A blueprint placed above a adding editing functionality. /// - public class HitObjectMask : CompositeDrawable, IStateful + public abstract class SelectionBlueprint : CompositeDrawable, IStateful { /// - /// Invoked when this has been selected. + /// Invoked when this has been selected. /// - public event Action Selected; + public event Action Selected; /// - /// Invoked when this has been deselected. + /// Invoked when this has been deselected. /// - public event Action Deselected; + public event Action Deselected; /// - /// Invoked when this has requested selection. + /// Invoked when this has requested selection. /// Will fire even if already selected. Does not actually perform selection. /// - public event Action SelectionRequested; + public event Action SelectionRequested; /// - /// Invoked when this has requested drag. + /// Invoked when this has requested drag. /// - public event Action DragRequested; + public event Action DragRequested; /// - /// The which this applies to. + /// The which this applies to. /// public readonly DrawableHitObject HitObject; @@ -48,10 +49,12 @@ namespace osu.Game.Rulesets.Edit public override bool HandlePositionalInput => ShouldBeAlive; public override bool RemoveWhenNotAlive => false; - public HitObjectMask(DrawableHitObject hitObject) + protected SelectionBlueprint(DrawableHitObject hitObject) { HitObject = hitObject; + RelativeSizeAxes = Axes.Both; + AlwaysPresent = true; Alpha = 0; } @@ -83,17 +86,19 @@ namespace osu.Game.Rulesets.Edit } /// - /// Selects this , causing it to become visible. + /// Selects this , causing it to become visible. /// public void Select() => State = SelectionState.Selected; /// - /// Deselects this , causing it to become invisible. + /// Deselects this , causing it to become invisible. /// public void Deselect() => State = SelectionState.NotSelected; public bool IsSelected => State == SelectionState.Selected; + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => HitObject.ReceivePositionalInputAt(screenSpacePos); + private bool selectionRequested; protected override bool OnMouseDown(MouseDownEvent e) @@ -125,18 +130,18 @@ namespace osu.Game.Rulesets.Edit protected override bool OnDrag(DragEvent e) { - DragRequested?.Invoke(this, e.Delta, e.CurrentState); + DragRequested?.Invoke(this, e); return true; } /// - /// The screen-space point that causes this to be selected. + /// The screen-space point that causes this to be selected. /// - public virtual Vector2 SelectionPoint => ScreenSpaceDrawQuad.Centre; + public virtual Vector2 SelectionPoint => HitObject.ScreenSpaceDrawQuad.Centre; /// - /// The screen-space quad that outlines this for selections. + /// The screen-space quad that outlines this for selections. /// - public virtual Quad SelectionQuad => ScreenSpaceDrawQuad; + public virtual Quad SelectionQuad => HitObject.ScreenSpaceDrawQuad; } } diff --git a/osu.Game/Rulesets/Edit/Tools/HitObjectCompositionTool.cs b/osu.Game/Rulesets/Edit/Tools/HitObjectCompositionTool.cs index 78ad236e74..1cb3c4c451 100644 --- a/osu.Game/Rulesets/Edit/Tools/HitObjectCompositionTool.cs +++ b/osu.Game/Rulesets/Edit/Tools/HitObjectCompositionTool.cs @@ -1,23 +1,17 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using osu.Game.Rulesets.Objects; - namespace osu.Game.Rulesets.Edit.Tools { - public class HitObjectCompositionTool : ICompositionTool - where T : HitObject + public abstract class HitObjectCompositionTool { - public string Name { get; } + public readonly string Name; - public HitObjectCompositionTool() - : this(typeof(T).Name) - { - } - - public HitObjectCompositionTool(string name) + protected HitObjectCompositionTool(string name) { Name = name; } + + public abstract PlacementBlueprint CreatePlacementBlueprint(); } } diff --git a/osu.Game/Rulesets/Edit/Tools/ICompositionTool.cs b/osu.Game/Rulesets/Edit/Tools/ICompositionTool.cs deleted file mode 100644 index ce8b139b43..0000000000 --- a/osu.Game/Rulesets/Edit/Tools/ICompositionTool.cs +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -namespace osu.Game.Rulesets.Edit.Tools -{ - public interface ICompositionTool - { - string Name { get; } - } -} diff --git a/osu.Game/Rulesets/Edit/Types/IHasEditablePosition.cs b/osu.Game/Rulesets/Edit/Types/IHasEditablePosition.cs deleted file mode 100644 index 7107b6c763..0000000000 --- a/osu.Game/Rulesets/Edit/Types/IHasEditablePosition.cs +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Game.Rulesets.Objects.Types; -using OpenTK; - -namespace osu.Game.Rulesets.Edit.Types -{ - public interface IHasEditablePosition : IHasPosition - { - void OffsetPosition(Vector2 offset); - } -} diff --git a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs index e8e775a20d..dd0152d957 100644 --- a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs +++ b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs @@ -1,7 +1,7 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using OpenTK; +using osuTK; using osu.Framework.Allocation; using osu.Framework.Extensions; using osu.Framework.Graphics; @@ -12,7 +12,7 @@ using osu.Game.Graphics.Sprites; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Scoring; using osu.Game.Skinning; -using OpenTK.Graphics; +using osuTK.Graphics; namespace osu.Game.Rulesets.Judgements { diff --git a/osu.Game/Rulesets/Mods/IApplicableToBeatmap.cs b/osu.Game/Rulesets/Mods/IApplicableToBeatmap.cs new file mode 100644 index 0000000000..1eb74ca76a --- /dev/null +++ b/osu.Game/Rulesets/Mods/IApplicableToBeatmap.cs @@ -0,0 +1,22 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Objects; + +namespace osu.Game.Rulesets.Mods +{ + /// + /// Interface for a that applies changes to a + /// after conversion and post-processing has completed. + /// + public interface IApplicableToBeatmap : IApplicableMod + where TObject : HitObject + { + /// + /// Applies this to a . + /// + /// The to apply to. + void ApplyToBeatmap(Beatmap beatmap); + } +} diff --git a/osu.Game/Rulesets/Mods/IMod.cs b/osu.Game/Rulesets/Mods/IMod.cs new file mode 100644 index 0000000000..d0c4ce2f4c --- /dev/null +++ b/osu.Game/Rulesets/Mods/IMod.cs @@ -0,0 +1,13 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +namespace osu.Game.Rulesets.Mods +{ + public interface IMod + { + /// + /// The shortened name of this mod. + /// + string Acronym { get; } + } +} diff --git a/osu.Game/Rulesets/Mods/Mod.cs b/osu.Game/Rulesets/Mods/Mod.cs index a991f7e7b0..14ee6b99ec 100644 --- a/osu.Game/Rulesets/Mods/Mod.cs +++ b/osu.Game/Rulesets/Mods/Mod.cs @@ -3,57 +3,67 @@ using osu.Game.Graphics; using System; +using Newtonsoft.Json; +using osu.Game.IO.Serialization; namespace osu.Game.Rulesets.Mods { /// /// The base class for gameplay modifiers. /// - public abstract class Mod + public abstract class Mod : IMod, IJsonSerializable { /// /// The name of this mod. /// + [JsonIgnore] public abstract string Name { get; } /// /// The shortened name of this mod. /// - public abstract string ShortenedName { get; } + public abstract string Acronym { get; } /// /// The icon of this mod. /// + [JsonIgnore] public virtual FontAwesome Icon => FontAwesome.fa_question; /// /// The type of this mod. /// + [JsonIgnore] public virtual ModType Type => ModType.Fun; /// /// The user readable description of this mod. /// + [JsonIgnore] public virtual string Description => string.Empty; /// /// The score multiplier of this mod. /// + [JsonIgnore] public abstract double ScoreMultiplier { get; } /// /// Returns true if this mod is implemented (and playable). /// + [JsonIgnore] public virtual bool HasImplementation => this is IApplicableMod; /// /// Returns if this mod is ranked. /// + [JsonIgnore] public virtual bool Ranked => false; /// /// The mods this mod cannot be enabled with. /// + [JsonIgnore] public virtual Type[] IncompatibleMods => new Type[] { }; } } diff --git a/osu.Game/Rulesets/Mods/ModAutoplay.cs b/osu.Game/Rulesets/Mods/ModAutoplay.cs index 5c03cb9736..932439618d 100644 --- a/osu.Game/Rulesets/Mods/ModAutoplay.cs +++ b/osu.Game/Rulesets/Mods/ModAutoplay.cs @@ -4,10 +4,10 @@ using System; using osu.Game.Beatmaps; using osu.Game.Graphics; +using osu.Game.Replays; using osu.Game.Rulesets.Objects; -using osu.Game.Rulesets.Replays; -using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; +using osu.Game.Scoring; namespace osu.Game.Rulesets.Mods { @@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Mods public abstract class ModAutoplay : Mod, IApplicableFailOverride { public override string Name => "Autoplay"; - public override string ShortenedName => "AT"; + public override string Acronym => "AT"; public override FontAwesome Icon => FontAwesome.fa_osu_mod_auto; public override ModType Type => ModType.Automation; public override string Description => "Watch a perfect automated play through the song."; diff --git a/osu.Game/Rulesets/Mods/ModBlinds.cs b/osu.Game/Rulesets/Mods/ModBlinds.cs index bf68300cd6..e4a17551ec 100644 --- a/osu.Game/Rulesets/Mods/ModBlinds.cs +++ b/osu.Game/Rulesets/Mods/ModBlinds.cs @@ -12,7 +12,7 @@ namespace osu.Game.Rulesets.Mods where T : HitObject { public override string Name => "Blinds"; - public override string ShortenedName => "BL"; + public override string Acronym => "BL"; public override FontAwesome Icon => FontAwesome.fa_adjust; public override ModType Type => ModType.DifficultyIncrease; public override string Description => "Play with blinds on your screen."; diff --git a/osu.Game/Rulesets/Mods/ModCinema.cs b/osu.Game/Rulesets/Mods/ModCinema.cs index 421e3a54e4..5c4040ee4c 100644 --- a/osu.Game/Rulesets/Mods/ModCinema.cs +++ b/osu.Game/Rulesets/Mods/ModCinema.cs @@ -8,7 +8,7 @@ namespace osu.Game.Rulesets.Mods public class ModCinema : ModAutoplay { public override string Name => "Cinema"; - public override string ShortenedName => "CN"; + public override string Acronym => "CN"; public override bool HasImplementation => false; public override FontAwesome Icon => FontAwesome.fa_osu_mod_cinema; public override string Description => "Watch the video without visual distractions."; diff --git a/osu.Game/Rulesets/Mods/ModDaycore.cs b/osu.Game/Rulesets/Mods/ModDaycore.cs index f11d0c76ed..5e3ec60be3 100644 --- a/osu.Game/Rulesets/Mods/ModDaycore.cs +++ b/osu.Game/Rulesets/Mods/ModDaycore.cs @@ -10,7 +10,7 @@ namespace osu.Game.Rulesets.Mods public abstract class ModDaycore : ModHalfTime { public override string Name => "Daycore"; - public override string ShortenedName => "DC"; + public override string Acronym => "DC"; public override FontAwesome Icon => FontAwesome.fa_question; public override string Description => "Whoaaaaa..."; diff --git a/osu.Game/Rulesets/Mods/ModDoubleTime.cs b/osu.Game/Rulesets/Mods/ModDoubleTime.cs index 0d44495fce..5c1d732995 100644 --- a/osu.Game/Rulesets/Mods/ModDoubleTime.cs +++ b/osu.Game/Rulesets/Mods/ModDoubleTime.cs @@ -10,7 +10,7 @@ namespace osu.Game.Rulesets.Mods public abstract class ModDoubleTime : Mod, IApplicableToClock { public override string Name => "Double Time"; - public override string ShortenedName => "DT"; + public override string Acronym => "DT"; public override FontAwesome Icon => FontAwesome.fa_osu_mod_doubletime; public override ModType Type => ModType.DifficultyIncrease; public override string Description => "Zoooooooooom..."; diff --git a/osu.Game/Rulesets/Mods/ModEasy.cs b/osu.Game/Rulesets/Mods/ModEasy.cs index 93abb82e5b..03781bea47 100644 --- a/osu.Game/Rulesets/Mods/ModEasy.cs +++ b/osu.Game/Rulesets/Mods/ModEasy.cs @@ -10,7 +10,7 @@ namespace osu.Game.Rulesets.Mods public abstract class ModEasy : Mod, IApplicableToDifficulty { public override string Name => "Easy"; - public override string ShortenedName => "EZ"; + public override string Acronym => "EZ"; public override FontAwesome Icon => FontAwesome.fa_osu_mod_easy; public override ModType Type => ModType.DifficultyReduction; public override double ScoreMultiplier => 0.5; diff --git a/osu.Game/Rulesets/Mods/ModFlashlight.cs b/osu.Game/Rulesets/Mods/ModFlashlight.cs index 223263195c..98da97d6fe 100644 --- a/osu.Game/Rulesets/Mods/ModFlashlight.cs +++ b/osu.Game/Rulesets/Mods/ModFlashlight.cs @@ -1,17 +1,160 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using System; +using System.Collections.Generic; +using osu.Framework.Allocation; +using osu.Framework.Configuration; +using osu.Framework.Graphics; +using osu.Framework.Graphics.OpenGL.Vertices; +using osu.Framework.Graphics.Primitives; +using osu.Framework.Graphics.Shaders; +using osu.Framework.Graphics.Textures; +using osu.Game.Beatmaps.Timing; using osu.Game.Graphics; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Scoring; +using osu.Game.Rulesets.UI; +using osuTK; +using osuTK.Graphics; namespace osu.Game.Rulesets.Mods { public abstract class ModFlashlight : Mod { public override string Name => "Flashlight"; - public override string ShortenedName => "FL"; + public override string Acronym => "FL"; public override FontAwesome Icon => FontAwesome.fa_osu_mod_flashlight; public override ModType Type => ModType.DifficultyIncrease; public override string Description => "Restricted view area."; public override bool Ranked => true; + + internal ModFlashlight() + { + } + } + + public abstract class ModFlashlight : ModFlashlight, IApplicableToRulesetContainer, IApplicableToScoreProcessor + where T : HitObject + { + public const double FLASHLIGHT_FADE_DURATION = 800; + protected readonly BindableInt Combo = new BindableInt(); + + public void ApplyToScoreProcessor(ScoreProcessor scoreProcessor) + { + Combo.BindTo(scoreProcessor.Combo); + } + + public virtual void ApplyToRulesetContainer(RulesetContainer rulesetContainer) + { + var flashlight = CreateFlashlight(); + flashlight.Combo = Combo; + flashlight.RelativeSizeAxes = Axes.Both; + flashlight.Colour = Color4.Black; + rulesetContainer.KeyBindingInputManager.Add(flashlight); + + flashlight.Breaks = rulesetContainer.Beatmap.Breaks; + } + + public abstract Flashlight CreateFlashlight(); + + public abstract class Flashlight : Drawable + { + internal BindableInt Combo; + private Shader shader; + + protected override DrawNode CreateDrawNode() => new FlashlightDrawNode(); + + public override bool RemoveCompletedTransforms => false; + + public List Breaks; + + protected override void ApplyDrawNode(DrawNode node) + { + base.ApplyDrawNode(node); + + var flashNode = (FlashlightDrawNode)node; + + flashNode.Shader = shader; + flashNode.ScreenSpaceDrawQuad = ScreenSpaceDrawQuad; + flashNode.FlashlightPosition = Vector2Extensions.Transform(FlashlightPosition, DrawInfo.Matrix); + flashNode.FlashlightSize = Vector2Extensions.Transform(FlashlightSize, DrawInfo.Matrix); + } + + [BackgroundDependencyLoader] + private void load(ShaderManager shaderManager) + { + shader = shaderManager.Load("PositionAndColour", FragmentShader); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + Combo.ValueChanged += OnComboChange; + + this.FadeInFromZero(FLASHLIGHT_FADE_DURATION); + + foreach (var breakPeriod in Breaks) + { + if (breakPeriod.Duration < FLASHLIGHT_FADE_DURATION * 2) continue; + + this.Delay(breakPeriod.StartTime + FLASHLIGHT_FADE_DURATION).FadeOutFromOne(FLASHLIGHT_FADE_DURATION); + this.Delay(breakPeriod.EndTime - FLASHLIGHT_FADE_DURATION).FadeInFromZero(FLASHLIGHT_FADE_DURATION); + } + } + + protected abstract void OnComboChange(int newCombo); + + protected abstract string FragmentShader { get; } + + private Vector2 flashlightPosition; + protected Vector2 FlashlightPosition + { + get => flashlightPosition; + set + { + if (flashlightPosition == value) return; + + flashlightPosition = value; + Invalidate(Invalidation.DrawNode); + } + } + + private Vector2 flashlightSize; + protected Vector2 FlashlightSize + { + get => flashlightSize; + set + { + if (flashlightSize == value) return; + + flashlightSize = value; + Invalidate(Invalidation.DrawNode); + } + } + } + + private class FlashlightDrawNode : DrawNode + { + public Shader Shader; + public Quad ScreenSpaceDrawQuad; + public Vector2 FlashlightPosition; + public Vector2 FlashlightSize; + + public override void Draw(Action vertexAction) + { + base.Draw(vertexAction); + + Shader.Bind(); + + Shader.GetUniform("flashlightPos").UpdateValue(ref FlashlightPosition); + Shader.GetUniform("flashlightSize").UpdateValue(ref FlashlightSize); + + Texture.WhitePixel.DrawQuad(ScreenSpaceDrawQuad, DrawColourInfo.Colour, vertexAction: vertexAction); + + Shader.Unbind(); + } + } } } diff --git a/osu.Game/Rulesets/Mods/ModHalfTime.cs b/osu.Game/Rulesets/Mods/ModHalfTime.cs index 3ec4fb9f9f..2d95b10db1 100644 --- a/osu.Game/Rulesets/Mods/ModHalfTime.cs +++ b/osu.Game/Rulesets/Mods/ModHalfTime.cs @@ -10,7 +10,7 @@ namespace osu.Game.Rulesets.Mods public abstract class ModHalfTime : Mod, IApplicableToClock { public override string Name => "Half Time"; - public override string ShortenedName => "HT"; + public override string Acronym => "HT"; public override FontAwesome Icon => FontAwesome.fa_osu_mod_halftime; public override ModType Type => ModType.DifficultyReduction; public override string Description => "Less zoom..."; diff --git a/osu.Game/Rulesets/Mods/ModHardRock.cs b/osu.Game/Rulesets/Mods/ModHardRock.cs index 6379be9bfc..14f997ae8c 100644 --- a/osu.Game/Rulesets/Mods/ModHardRock.cs +++ b/osu.Game/Rulesets/Mods/ModHardRock.cs @@ -10,7 +10,7 @@ namespace osu.Game.Rulesets.Mods public abstract class ModHardRock : Mod, IApplicableToDifficulty { public override string Name => "Hard Rock"; - public override string ShortenedName => "HR"; + public override string Acronym => "HR"; public override FontAwesome Icon => FontAwesome.fa_osu_mod_hardrock; public override ModType Type => ModType.DifficultyIncrease; public override string Description => "Everything just got a bit harder..."; diff --git a/osu.Game/Rulesets/Mods/ModHidden.cs b/osu.Game/Rulesets/Mods/ModHidden.cs index 9b09f0bd6d..b843171521 100644 --- a/osu.Game/Rulesets/Mods/ModHidden.cs +++ b/osu.Game/Rulesets/Mods/ModHidden.cs @@ -13,7 +13,7 @@ namespace osu.Game.Rulesets.Mods public abstract class ModHidden : Mod, IReadFromConfig, IApplicableToDrawableHitObjects { public override string Name => "Hidden"; - public override string ShortenedName => "HD"; + public override string Acronym => "HD"; public override FontAwesome Icon => FontAwesome.fa_osu_mod_hidden; public override ModType Type => ModType.DifficultyIncrease; public override bool Ranked => true; diff --git a/osu.Game/Rulesets/Mods/ModNightcore.cs b/osu.Game/Rulesets/Mods/ModNightcore.cs index 162d654965..f4fe1c3831 100644 --- a/osu.Game/Rulesets/Mods/ModNightcore.cs +++ b/osu.Game/Rulesets/Mods/ModNightcore.cs @@ -10,7 +10,7 @@ namespace osu.Game.Rulesets.Mods public abstract class ModNightcore : ModDoubleTime { public override string Name => "Nightcore"; - public override string ShortenedName => "NC"; + public override string Acronym => "NC"; public override FontAwesome Icon => FontAwesome.fa_osu_mod_nightcore; public override string Description => "Uguuuuuuuu..."; diff --git a/osu.Game/Rulesets/Mods/ModNoFail.cs b/osu.Game/Rulesets/Mods/ModNoFail.cs index 7510f62432..223af7d304 100644 --- a/osu.Game/Rulesets/Mods/ModNoFail.cs +++ b/osu.Game/Rulesets/Mods/ModNoFail.cs @@ -9,7 +9,7 @@ namespace osu.Game.Rulesets.Mods public abstract class ModNoFail : Mod, IApplicableFailOverride { public override string Name => "No Fail"; - public override string ShortenedName => "NF"; + public override string Acronym => "NF"; public override FontAwesome Icon => FontAwesome.fa_osu_mod_nofail; public override ModType Type => ModType.DifficultyReduction; public override string Description => "You can't fail, no matter what."; diff --git a/osu.Game/Rulesets/Mods/NoModMod.cs b/osu.Game/Rulesets/Mods/ModNoMod.cs similarity index 81% rename from osu.Game/Rulesets/Mods/NoModMod.cs rename to osu.Game/Rulesets/Mods/ModNoMod.cs index dcab3538da..c75849149b 100644 --- a/osu.Game/Rulesets/Mods/NoModMod.cs +++ b/osu.Game/Rulesets/Mods/ModNoMod.cs @@ -6,10 +6,10 @@ namespace osu.Game.Rulesets.Mods /// /// Indicates a type of mod that doesn't do anything. /// - public sealed class NoModMod : Mod + public sealed class ModNoMod : Mod { public override string Name => "No Mod"; - public override string ShortenedName => "NM"; + public override string Acronym => "NM"; public override double ScoreMultiplier => 1; } } diff --git a/osu.Game/Rulesets/Mods/ModPerfect.cs b/osu.Game/Rulesets/Mods/ModPerfect.cs index 802890866f..b7ded41024 100644 --- a/osu.Game/Rulesets/Mods/ModPerfect.cs +++ b/osu.Game/Rulesets/Mods/ModPerfect.cs @@ -9,7 +9,7 @@ namespace osu.Game.Rulesets.Mods public abstract class ModPerfect : ModSuddenDeath { public override string Name => "Perfect"; - public override string ShortenedName => "PF"; + public override string Acronym => "PF"; public override FontAwesome Icon => FontAwesome.fa_osu_mod_perfect; public override string Description => "SS or quit."; diff --git a/osu.Game/Rulesets/Mods/ModRelax.cs b/osu.Game/Rulesets/Mods/ModRelax.cs index 04aa295893..769cde6746 100644 --- a/osu.Game/Rulesets/Mods/ModRelax.cs +++ b/osu.Game/Rulesets/Mods/ModRelax.cs @@ -9,7 +9,7 @@ namespace osu.Game.Rulesets.Mods public abstract class ModRelax : Mod { public override string Name => "Relax"; - public override string ShortenedName => "RX"; + public override string Acronym => "RX"; public override FontAwesome Icon => FontAwesome.fa_osu_mod_relax; public override ModType Type => ModType.Automation; public override double ScoreMultiplier => 1; diff --git a/osu.Game/Rulesets/Mods/ModSuddenDeath.cs b/osu.Game/Rulesets/Mods/ModSuddenDeath.cs index 48f7d496a5..77d411d8fd 100644 --- a/osu.Game/Rulesets/Mods/ModSuddenDeath.cs +++ b/osu.Game/Rulesets/Mods/ModSuddenDeath.cs @@ -10,7 +10,7 @@ namespace osu.Game.Rulesets.Mods public abstract class ModSuddenDeath : Mod, IApplicableToScoreProcessor { public override string Name => "Sudden Death"; - public override string ShortenedName => "SD"; + public override string Acronym => "SD"; public override FontAwesome Icon => FontAwesome.fa_osu_mod_suddendeath; public override ModType Type => ModType.DifficultyIncrease; public override string Description => "Miss and fail."; diff --git a/osu.Game/Rulesets/Mods/MultiMod.cs b/osu.Game/Rulesets/Mods/MultiMod.cs index b65773e93f..26e09a59e0 100644 --- a/osu.Game/Rulesets/Mods/MultiMod.cs +++ b/osu.Game/Rulesets/Mods/MultiMod.cs @@ -9,7 +9,7 @@ namespace osu.Game.Rulesets.Mods public class MultiMod : Mod { public override string Name => string.Empty; - public override string ShortenedName => string.Empty; + public override string Acronym => string.Empty; public override string Description => string.Empty; public override double ScoreMultiplier => 0; diff --git a/osu.Game/Rulesets/Objects/BezierApproximator.cs b/osu.Game/Rulesets/Objects/BezierApproximator.cs deleted file mode 100644 index a1803e32f7..0000000000 --- a/osu.Game/Rulesets/Objects/BezierApproximator.cs +++ /dev/null @@ -1,151 +0,0 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.Collections.Generic; -using OpenTK; - -namespace osu.Game.Rulesets.Objects -{ - public readonly ref struct BezierApproximator - { - private readonly int count; - private readonly ReadOnlySpan controlPoints; - private readonly Vector2[] subdivisionBuffer1; - private readonly Vector2[] subdivisionBuffer2; - - private const float tolerance = 0.25f; - private const float tolerance_sq = tolerance * tolerance; - - public BezierApproximator(ReadOnlySpan controlPoints) - { - this.controlPoints = controlPoints; - count = controlPoints.Length; - - subdivisionBuffer1 = new Vector2[count]; - subdivisionBuffer2 = new Vector2[count * 2 - 1]; - } - - /// - /// Make sure the 2nd order derivative (approximated using finite elements) is within tolerable bounds. - /// NOTE: The 2nd order derivative of a 2d curve represents its curvature, so intuitively this function - /// checks (as the name suggests) whether our approximation is _locally_ "flat". More curvy parts - /// need to have a denser approximation to be more "flat". - /// - /// The control points to check for flatness. - /// Whether the control points are flat enough. - private static bool isFlatEnough(Vector2[] controlPoints) - { - for (int i = 1; i < controlPoints.Length - 1; i++) - if ((controlPoints[i - 1] - 2 * controlPoints[i] + controlPoints[i + 1]).LengthSquared > tolerance_sq * 4) - return false; - - return true; - } - - /// - /// Subdivides n control points representing a bezier curve into 2 sets of n control points, each - /// describing a bezier curve equivalent to a half of the original curve. Effectively this splits - /// the original curve into 2 curves which result in the original curve when pieced back together. - /// - /// The control points to split. - /// Output: The control points corresponding to the left half of the curve. - /// Output: The control points corresponding to the right half of the curve. - private void subdivide(Vector2[] controlPoints, Vector2[] l, Vector2[] r) - { - Vector2[] midpoints = subdivisionBuffer1; - - for (int i = 0; i < count; ++i) - midpoints[i] = controlPoints[i]; - - for (int i = 0; i < count; i++) - { - l[i] = midpoints[0]; - r[count - i - 1] = midpoints[count - i - 1]; - - for (int j = 0; j < count - i - 1; j++) - midpoints[j] = (midpoints[j] + midpoints[j + 1]) / 2; - } - } - - /// - /// This uses De Casteljau's algorithm to obtain an optimal - /// piecewise-linear approximation of the bezier curve with the same amount of points as there are control points. - /// - /// The control points describing the bezier curve to be approximated. - /// The points representing the resulting piecewise-linear approximation. - private void approximate(Vector2[] controlPoints, List output) - { - Vector2[] l = subdivisionBuffer2; - Vector2[] r = subdivisionBuffer1; - - subdivide(controlPoints, l, r); - - for (int i = 0; i < count - 1; ++i) - l[count + i] = r[i + 1]; - - output.Add(controlPoints[0]); - for (int i = 1; i < count - 1; ++i) - { - int index = 2 * i; - Vector2 p = 0.25f * (l[index - 1] + 2 * l[index] + l[index + 1]); - output.Add(p); - } - } - - /// - /// Creates a piecewise-linear approximation of a bezier curve, by adaptively repeatedly subdividing - /// the control points until their approximation error vanishes below a given threshold. - /// - /// A list of vectors representing the piecewise-linear approximation. - public List CreateBezier() - { - List output = new List(); - - if (count == 0) - return output; - - Stack toFlatten = new Stack(); - Stack freeBuffers = new Stack(); - - // "toFlatten" contains all the curves which are not yet approximated well enough. - // We use a stack to emulate recursion without the risk of running into a stack overflow. - // (More specifically, we iteratively and adaptively refine our curve with a - // Depth-first search - // over the tree resulting from the subdivisions we make.) - toFlatten.Push(controlPoints.ToArray()); - - Vector2[] leftChild = subdivisionBuffer2; - - while (toFlatten.Count > 0) - { - Vector2[] parent = toFlatten.Pop(); - if (isFlatEnough(parent)) - { - // If the control points we currently operate on are sufficiently "flat", we use - // an extension to De Casteljau's algorithm to obtain a piecewise-linear approximation - // of the bezier curve represented by our control points, consisting of the same amount - // of points as there are control points. - approximate(parent, output); - freeBuffers.Push(parent); - continue; - } - - // If we do not yet have a sufficiently "flat" (in other words, detailed) approximation we keep - // subdividing the curve we are currently operating on. - Vector2[] rightChild = freeBuffers.Count > 0 ? freeBuffers.Pop() : new Vector2[count]; - subdivide(parent, leftChild, rightChild); - - // We re-use the buffer of the parent for one of the children, so that we save one allocation per iteration. - for (int i = 0; i < count; ++i) - parent[i] = leftChild[i]; - - toFlatten.Push(rightChild); - toFlatten.Push(parent); - } - - output.Add(controlPoints[count - 1]); - return output; - } - } -} diff --git a/osu.Game/Rulesets/Objects/CatmullApproximator.cs b/osu.Game/Rulesets/Objects/CatmullApproximator.cs deleted file mode 100644 index 78f8e471f3..0000000000 --- a/osu.Game/Rulesets/Objects/CatmullApproximator.cs +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.Collections.Generic; -using OpenTK; - -namespace osu.Game.Rulesets.Objects -{ - public readonly ref struct CatmullApproximator - { - /// - /// The amount of pieces to calculate for each controlpoint quadruplet. - /// - private const int detail = 50; - - private readonly ReadOnlySpan controlPoints; - - public CatmullApproximator(ReadOnlySpan controlPoints) - { - this.controlPoints = controlPoints; - } - - /// - /// Creates a piecewise-linear approximation of a Catmull-Rom spline. - /// - /// A list of vectors representing the piecewise-linear approximation. - public List CreateCatmull() - { - var result = new List((controlPoints.Length - 1) * detail * 2); - - for (int i = 0; i < controlPoints.Length - 1; i++) - { - var v1 = i > 0 ? controlPoints[i - 1] : controlPoints[i]; - var v2 = controlPoints[i]; - var v3 = i < controlPoints.Length - 1 ? controlPoints[i + 1] : v2 + v2 - v1; - var v4 = i < controlPoints.Length - 2 ? controlPoints[i + 2] : v3 + v3 - v2; - - for (int c = 0; c < detail; c++) - { - result.Add(findPoint(ref v1, ref v2, ref v3, ref v4, (float)c / detail)); - result.Add(findPoint(ref v1, ref v2, ref v3, ref v4, (float)(c + 1) / detail)); - } - } - - return result; - } - - /// - /// Finds a point on the spline at the position of a parameter. - /// - /// The first vector. - /// The second vector. - /// The third vector. - /// The fourth vector. - /// The parameter at which to find the point on the spline, in the range [0, 1]. - /// The point on the spline at . - private Vector2 findPoint(ref Vector2 vec1, ref Vector2 vec2, ref Vector2 vec3, ref Vector2 vec4, float t) - { - float t2 = t * t; - float t3 = t * t2; - - Vector2 result; - result.X = 0.5f * (2f * vec2.X + (-vec1.X + vec3.X) * t + (2f * vec1.X - 5f * vec2.X + 4f * vec3.X - vec4.X) * t2 + (-vec1.X + 3f * vec2.X - 3f * vec3.X + vec4.X) * t3); - result.Y = 0.5f * (2f * vec2.Y + (-vec1.Y + vec3.Y) * t + (2f * vec1.Y - 5f * vec2.Y + 4f * vec3.Y - vec4.Y) * t2 + (-vec1.Y + 3f * vec2.Y - 3f * vec3.Y + vec4.Y) * t3); - - return result; - } - } -} diff --git a/osu.Game/Rulesets/Objects/CircularArcApproximator.cs b/osu.Game/Rulesets/Objects/CircularArcApproximator.cs deleted file mode 100644 index 28d7442aaf..0000000000 --- a/osu.Game/Rulesets/Objects/CircularArcApproximator.cs +++ /dev/null @@ -1,97 +0,0 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.Collections.Generic; -using osu.Framework.MathUtils; -using OpenTK; - -namespace osu.Game.Rulesets.Objects -{ - public readonly ref struct CircularArcApproximator - { - private const float tolerance = 0.1f; - - private readonly ReadOnlySpan controlPoints; - - public CircularArcApproximator(ReadOnlySpan controlPoints) - { - this.controlPoints = controlPoints; - } - - /// - /// Creates a piecewise-linear approximation of a circular arc curve. - /// - /// A list of vectors representing the piecewise-linear approximation. - public List CreateArc() - { - Vector2 a = controlPoints[0]; - Vector2 b = controlPoints[1]; - Vector2 c = controlPoints[2]; - - float aSq = (b - c).LengthSquared; - float bSq = (a - c).LengthSquared; - float cSq = (a - b).LengthSquared; - - // If we have a degenerate triangle where a side-length is almost zero, then give up and fall - // back to a more numerically stable method. - if (Precision.AlmostEquals(aSq, 0) || Precision.AlmostEquals(bSq, 0) || Precision.AlmostEquals(cSq, 0)) - return new List(); - - float s = aSq * (bSq + cSq - aSq); - float t = bSq * (aSq + cSq - bSq); - float u = cSq * (aSq + bSq - cSq); - - float sum = s + t + u; - - // If we have a degenerate triangle with an almost-zero size, then give up and fall - // back to a more numerically stable method. - if (Precision.AlmostEquals(sum, 0)) - return new List(); - - Vector2 centre = (s * a + t * b + u * c) / sum; - Vector2 dA = a - centre; - Vector2 dC = c - centre; - - float r = dA.Length; - - double thetaStart = Math.Atan2(dA.Y, dA.X); - double thetaEnd = Math.Atan2(dC.Y, dC.X); - - while (thetaEnd < thetaStart) - thetaEnd += 2 * Math.PI; - - double dir = 1; - double thetaRange = thetaEnd - thetaStart; - - // Decide in which direction to draw the circle, depending on which side of - // AC B lies. - Vector2 orthoAtoC = c - a; - orthoAtoC = new Vector2(orthoAtoC.Y, -orthoAtoC.X); - if (Vector2.Dot(orthoAtoC, b - a) < 0) - { - dir = -dir; - thetaRange = 2 * Math.PI - thetaRange; - } - - // We select the amount of points for the approximation by requiring the discrete curvature - // to be smaller than the provided tolerance. The exact angle required to meet the tolerance - // is: 2 * Math.Acos(1 - TOLERANCE / r) - // The special case is required for extremely short sliders where the radius is smaller than - // the tolerance. This is a pathological rather than a realistic case. - int amountPoints = 2 * r <= tolerance ? 2 : Math.Max(2, (int)Math.Ceiling(thetaRange / (2 * Math.Acos(1 - tolerance / r)))); - - List output = new List(amountPoints); - - for (int i = 0; i < amountPoints; ++i) - { - double fract = (double)i / (amountPoints - 1); - double theta = thetaStart + dir * fract * thetaRange; - Vector2 o = new Vector2((float)Math.Cos(theta), (float)Math.Sin(theta)) * r; - output.Add(centre + o); - } - - return output; - } - } -} diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index bcf84b375f..e0728826df 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -7,7 +7,6 @@ using System.Linq; using osu.Framework.Allocation; using osu.Framework.Configuration; using osu.Framework.Extensions.TypeExtensions; -using osu.Framework.Graphics; using osu.Framework.Graphics.Primitives; using osu.Game.Audio; using osu.Game.Graphics; @@ -15,7 +14,7 @@ using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Scoring; using osu.Game.Skinning; -using OpenTK.Graphics; +using osuTK.Graphics; namespace osu.Game.Rulesets.Objects.Drawables { @@ -85,6 +84,8 @@ namespace osu.Game.Rulesets.Objects.Drawables public override bool RemoveCompletedTransforms => false; protected override bool RequiresChildrenUpdate => true; + public override bool IsPresent => base.IsPresent || State.Value == ArmedState.Idle && Clock?.CurrentTime >= LifetimeStart; + public readonly Bindable State = new Bindable(); protected DrawableHitObject(HitObject hitObject) @@ -146,6 +147,7 @@ namespace osu.Game.Rulesets.Objects.Drawables /// /// Plays all the hit sounds for this . + /// This is invoked automatically when this is hit. /// public void PlaySamples() => Samples?.Play(); @@ -167,13 +169,7 @@ namespace osu.Game.Rulesets.Objects.Drawables } } - public override bool UpdateSubTreeMasking(Drawable source, RectangleF maskingBounds) - { - if (!AllJudged) - return false; - - return base.UpdateSubTreeMasking(source, maskingBounds); - } + protected override bool ComputeIsMaskedAway(RectangleF maskingBounds) => AllJudged && base.ComputeIsMaskedAway(maskingBounds); protected override void UpdateAfterChildren() { diff --git a/osu.Game/Rulesets/Objects/HitObject.cs b/osu.Game/Rulesets/Objects/HitObject.cs index f5613e927f..010fc450e0 100644 --- a/osu.Game/Rulesets/Objects/HitObject.cs +++ b/osu.Game/Rulesets/Objects/HitObject.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using Newtonsoft.Json; -using osu.Framework.Lists; using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; @@ -20,6 +19,11 @@ namespace osu.Game.Rulesets.Objects /// public class HitObject { + /// + /// A small adjustment to the start time of control points to account for rounding/precision errors. + /// + private const double control_point_leniency = 1; + /// /// The time at which the HitObject starts. /// @@ -56,7 +60,7 @@ namespace osu.Game.Rulesets.Objects /// public HitWindows HitWindows { get; set; } - private readonly SortedList nestedHitObjects = new SortedList(compareObjects); + private readonly List nestedHitObjects = new List(); [JsonIgnore] public IReadOnlyList NestedHitObjects => nestedHitObjects; @@ -70,10 +74,15 @@ namespace osu.Game.Rulesets.Objects { ApplyDefaultsToSelf(controlPointInfo, difficulty); + // This is done here since ApplyDefaultsToSelf may be used to determine the end time + SampleControlPoint = controlPointInfo.SamplePointAt(((this as IHasEndTime)?.EndTime ?? StartTime) + control_point_leniency); + nestedHitObjects.Clear(); CreateNestedHitObjects(); + nestedHitObjects.Sort((h1, h2) => h1.StartTime.CompareTo(h2.StartTime)); + foreach (var h in nestedHitObjects) { h.HitWindows = HitWindows; @@ -83,11 +92,7 @@ namespace osu.Game.Rulesets.Objects protected virtual void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) { - SampleControlPoint samplePoint = controlPointInfo.SamplePointAt(StartTime); - EffectControlPoint effectPoint = controlPointInfo.EffectPointAt(StartTime); - - Kiai = effectPoint.KiaiMode; - SampleControlPoint = samplePoint; + Kiai = controlPointInfo.EffectPointAt(StartTime + control_point_leniency).KiaiMode; if (HitWindows == null) HitWindows = CreateHitWindows(); @@ -114,7 +119,5 @@ namespace osu.Game.Rulesets.Objects /// /// protected virtual HitWindows CreateHitWindows() => new HitWindows(); - - private static int compareObjects(HitObject first, HitObject second) => first.StartTime.CompareTo(second.StartTime); } } diff --git a/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHitObjectParser.cs index 9c9fc2e742..e8962cd5ec 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHitObjectParser.cs @@ -1,7 +1,7 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using OpenTK; +using osuTK; using osu.Game.Audio; using osu.Game.Rulesets.Objects.Types; using System.Collections.Generic; @@ -37,7 +37,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Catch }; } - protected override HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, Vector2[] controlPoints, double length, CurveType curveType, int repeatCount, List> repeatSamples) + protected override HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, Vector2[] controlPoints, double length, PathType pathType, int repeatCount, List> nodeSamples) { newCombo |= forceNewCombo; comboOffset += extraComboOffset; @@ -50,10 +50,8 @@ namespace osu.Game.Rulesets.Objects.Legacy.Catch X = position.X, NewCombo = FirstObject || newCombo, ComboOffset = comboOffset, - ControlPoints = controlPoints, - Distance = length, - CurveType = curveType, - RepeatSamples = repeatSamples, + Path = new SliderPath(pathType, controlPoints, length), + NodeSamples = nodeSamples, RepeatCount = repeatCount }; } diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs index 965e76d27a..85eb29033e 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs @@ -1,7 +1,7 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using OpenTK; +using osuTK; using osu.Game.Rulesets.Objects.Types; using System; using System.Collections.Generic; @@ -70,7 +70,7 @@ namespace osu.Game.Rulesets.Objects.Legacy } else if (type.HasFlag(ConvertHitObjectType.Slider)) { - CurveType curveType = CurveType.Catmull; + PathType pathType = PathType.Catmull; double length = 0; string[] pointSplit = split[5].Split('|'); @@ -90,16 +90,16 @@ namespace osu.Game.Rulesets.Objects.Legacy switch (t) { case @"C": - curveType = CurveType.Catmull; + pathType = PathType.Catmull; break; case @"B": - curveType = CurveType.Bezier; + pathType = PathType.Bezier; break; case @"L": - curveType = CurveType.Linear; + pathType = PathType.Linear; break; case @"P": - curveType = CurveType.PerfectCurve; + pathType = PathType.PerfectCurve; break; } @@ -113,8 +113,8 @@ namespace osu.Game.Rulesets.Objects.Legacy // osu-stable special-cased colinear perfect curves to a CurveType.Linear bool isLinear(Vector2[] p) => Precision.AlmostEquals(0, (p[1].Y - p[0].Y) * (p[2].X - p[0].X) - (p[1].X - p[0].X) * (p[2].Y - p[0].Y)); - if (points.Length == 3 && curveType == CurveType.PerfectCurve && isLinear(points)) - curveType = CurveType.Linear; + if (points.Length == 3 && pathType == PathType.PerfectCurve && isLinear(points)) + pathType = PathType.Linear; int repeatCount = Convert.ToInt32(split[6], CultureInfo.InvariantCulture); @@ -178,7 +178,10 @@ namespace osu.Game.Rulesets.Objects.Legacy for (int i = 0; i < nodes; i++) nodeSamples.Add(convertSoundType(nodeSoundTypes[i], nodeBankInfos[i])); - result = CreateSlider(pos, combo, comboOffset, points, length, curveType, repeatCount, nodeSamples); + result = CreateSlider(pos, combo, comboOffset, points, length, pathType, repeatCount, nodeSamples); + + // The samples are played when the slider ends, which is the last node + result.Samples = nodeSamples[nodeSamples.Count - 1]; } else if (type.HasFlag(ConvertHitObjectType.Spinner)) { @@ -210,7 +213,9 @@ namespace osu.Game.Rulesets.Objects.Legacy } result.StartTime = Convert.ToDouble(split[2], CultureInfo.InvariantCulture) + Offset; - result.Samples = convertSoundType(soundType, bankInfo); + + if (result.Samples.Count == 0) + result.Samples = convertSoundType(soundType, bankInfo); FirstObject = false; @@ -240,7 +245,7 @@ namespace osu.Game.Rulesets.Objects.Legacy stringAddBank = null; bankInfo.Normal = stringBank; - bankInfo.Add = stringAddBank; + bankInfo.Add = string.IsNullOrEmpty(stringAddBank) ? stringBank : stringAddBank; if (split.Length > 2) bankInfo.CustomSampleBank = int.Parse(split[2]); @@ -268,11 +273,11 @@ namespace osu.Game.Rulesets.Objects.Legacy /// When starting a new combo, the offset of the new combo relative to the current one. /// The slider control points. /// The slider length. - /// The slider curve type. + /// The slider curve type. /// The slider repeat count. - /// The samples to be played when the repeat nodes are hit. This includes the head and tail of the slider. + /// The samples to be played when the slider nodes are hit. This includes the head and tail of the slider. /// The hit object. - protected abstract HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, Vector2[] controlPoints, double length, CurveType curveType, int repeatCount, List> repeatSamples); + protected abstract HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, Vector2[] controlPoints, double length, PathType pathType, int repeatCount, List> nodeSamples); /// /// Creates a legacy Spinner-type hit object. diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs index 93c49ea3ce..0512a97354 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs @@ -3,7 +3,6 @@ using osu.Game.Rulesets.Objects.Types; using System.Collections.Generic; -using OpenTK; using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; @@ -20,13 +19,11 @@ namespace osu.Game.Rulesets.Objects.Legacy /// /// s don't need a curve since they're converted to ruleset-specific hitobjects. /// - public SliderCurve Curve { get; } = null; - public Vector2[] ControlPoints { get; set; } - public CurveType CurveType { get; set; } + public SliderPath Path { get; set; } - public double Distance { get; set; } + public double Distance => Path.Distance; - public List> RepeatSamples { get; set; } + public List> NodeSamples { get; set; } public int RepeatCount { get; set; } public double EndTime => StartTime + this.SpanCount() * Distance / Velocity; diff --git a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHitObjectParser.cs index 68e05f6223..b81fed9f03 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHitObjectParser.cs @@ -1,7 +1,7 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using OpenTK; +using osuTK; using osu.Game.Audio; using osu.Game.Rulesets.Objects.Types; using System.Collections.Generic; @@ -26,15 +26,13 @@ namespace osu.Game.Rulesets.Objects.Legacy.Mania }; } - protected override HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, Vector2[] controlPoints, double length, CurveType curveType, int repeatCount, List> repeatSamples) + protected override HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, Vector2[] controlPoints, double length, PathType pathType, int repeatCount, List> nodeSamples) { return new ConvertSlider { X = position.X, - ControlPoints = controlPoints, - Distance = length, - CurveType = curveType, - RepeatSamples = repeatSamples, + Path = new SliderPath(pathType, controlPoints, length), + NodeSamples = nodeSamples, RepeatCount = repeatCount }; } diff --git a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHit.cs b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHit.cs index 0062e83a28..c8d10816bd 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHit.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHit.cs @@ -2,7 +2,7 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using osu.Game.Rulesets.Objects.Types; -using OpenTK; +using osuTK; namespace osu.Game.Rulesets.Objects.Legacy.Osu { diff --git a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHitObjectParser.cs index f3c815fc32..f65452858a 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHitObjectParser.cs @@ -2,7 +2,7 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System; -using OpenTK; +using osuTK; using osu.Game.Rulesets.Objects.Types; using System.Collections.Generic; using osu.Game.Audio; @@ -38,7 +38,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Osu }; } - protected override HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, Vector2[] controlPoints, double length, CurveType curveType, int repeatCount, List> repeatSamples) + protected override HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, Vector2[] controlPoints, double length, PathType pathType, int repeatCount, List> nodeSamples) { newCombo |= forceNewCombo; comboOffset += extraComboOffset; @@ -51,10 +51,8 @@ namespace osu.Game.Rulesets.Objects.Legacy.Osu Position = position, NewCombo = FirstObject || newCombo, ComboOffset = comboOffset, - ControlPoints = controlPoints, - Distance = Math.Max(0, length), - CurveType = curveType, - RepeatSamples = repeatSamples, + Path = new SliderPath(pathType, controlPoints, Math.Max(0, length)), + NodeSamples = nodeSamples, RepeatCount = repeatCount }; } diff --git a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSlider.cs b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSlider.cs index 45f7bc9e67..bceca0675e 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSlider.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSlider.cs @@ -2,7 +2,7 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using osu.Game.Rulesets.Objects.Types; -using OpenTK; +using osuTK; namespace osu.Game.Rulesets.Objects.Legacy.Osu { diff --git a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSpinner.cs b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSpinner.cs index 3b349d9704..49f27b01f6 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSpinner.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSpinner.cs @@ -2,7 +2,7 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using osu.Game.Rulesets.Objects.Types; -using OpenTK; +using osuTK; namespace osu.Game.Rulesets.Objects.Legacy.Osu { diff --git a/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHitObjectParser.cs index 985a032640..16ed49ce2b 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHitObjectParser.cs @@ -1,7 +1,7 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using OpenTK; +using osuTK; using osu.Game.Rulesets.Objects.Types; using System.Collections.Generic; using osu.Game.Audio; @@ -23,14 +23,12 @@ namespace osu.Game.Rulesets.Objects.Legacy.Taiko return new ConvertHit(); } - protected override HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, Vector2[] controlPoints, double length, CurveType curveType, int repeatCount, List> repeatSamples) + protected override HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, Vector2[] controlPoints, double length, PathType pathType, int repeatCount, List> nodeSamples) { return new ConvertSlider { - ControlPoints = controlPoints, - Distance = length, - CurveType = curveType, - RepeatSamples = repeatSamples, + Path = new SliderPath(pathType, controlPoints, length), + NodeSamples = nodeSamples, RepeatCount = repeatCount }; } diff --git a/osu.Game/Rulesets/Objects/SliderCurve.cs b/osu.Game/Rulesets/Objects/SliderPath.cs similarity index 51% rename from osu.Game/Rulesets/Objects/SliderCurve.cs rename to osu.Game/Rulesets/Objects/SliderPath.cs index dfccdf68f2..fe38fd4f65 100644 --- a/osu.Game/Rulesets/Objects/SliderCurve.cs +++ b/osu.Game/Rulesets/Objects/SliderPath.cs @@ -4,53 +4,161 @@ using System; using System.Collections.Generic; using System.Linq; +using Newtonsoft.Json; using osu.Framework.MathUtils; using osu.Game.Rulesets.Objects.Types; -using OpenTK; +using osuTK; namespace osu.Game.Rulesets.Objects { - public class SliderCurve + public struct SliderPath : IEquatable { - public double Distance; + /// + /// The user-set distance of the path. If non-null, will match this value, + /// and the path will be shortened/lengthened to match this length. + /// + public readonly double? ExpectedDistance; - public Vector2[] ControlPoints; + /// + /// The type of path. + /// + public readonly PathType Type; - public CurveType CurveType = CurveType.PerfectCurve; + [JsonProperty] + private Vector2[] controlPoints; - public Vector2 Offset; + private List calculatedPath; + private List cumulativeLength; - private readonly List calculatedPath = new List(); - private readonly List cumulativeLength = new List(); + private bool isInitialised; + + /// + /// Creates a new . + /// + /// The type of path. + /// The control points of the path. + /// A user-set distance of the path that may be shorter or longer than the true distance between all + /// . The path will be shortened/lengthened to match this length. + /// If null, the path will use the true distance between all . + [JsonConstructor] + public SliderPath(PathType type, Vector2[] controlPoints, double? expectedDistance = null) + { + this = default; + this.controlPoints = controlPoints; + + Type = type; + ExpectedDistance = expectedDistance; + + ensureInitialised(); + } + + /// + /// The control points of the path. + /// + [JsonIgnore] + public ReadOnlySpan ControlPoints + { + get + { + ensureInitialised(); + return controlPoints.AsSpan(); + } + } + + /// + /// The distance of the path after lengthening/shortening to account for . + /// + [JsonIgnore] + public double Distance + { + get + { + ensureInitialised(); + return cumulativeLength.Count == 0 ? 0 : cumulativeLength[cumulativeLength.Count - 1]; + } + } + + /// + /// Computes the slider path until a given progress that ranges from 0 (beginning of the slider) + /// to 1 (end of the slider) and stores the generated path in the given list. + /// + /// The list to be filled with the computed path. + /// Start progress. Ranges from 0 (beginning of the slider) to 1 (end of the slider). + /// End progress. Ranges from 0 (beginning of the slider) to 1 (end of the slider). + public void GetPathToProgress(List path, double p0, double p1) + { + ensureInitialised(); + + double d0 = progressToDistance(p0); + double d1 = progressToDistance(p1); + + path.Clear(); + + int i = 0; + for (; i < calculatedPath.Count && cumulativeLength[i] < d0; ++i) + { + } + + path.Add(interpolateVertices(i, d0)); + + for (; i < calculatedPath.Count && cumulativeLength[i] <= d1; ++i) + path.Add(calculatedPath[i]); + + path.Add(interpolateVertices(i, d1)); + } + + /// + /// Computes the position on the slider at a given progress that ranges from 0 (beginning of the path) + /// to 1 (end of the path). + /// + /// Ranges from 0 (beginning of the path) to 1 (end of the path). + /// + public Vector2 PositionAt(double progress) + { + ensureInitialised(); + + double d = progressToDistance(progress); + return interpolateVertices(indexOfDistance(d), d); + } + + private void ensureInitialised() + { + if (isInitialised) + return; + isInitialised = true; + + controlPoints = controlPoints ?? Array.Empty(); + calculatedPath = new List(); + cumulativeLength = new List(); + + calculatePath(); + calculateCumulativeLength(); + } private List calculateSubpath(ReadOnlySpan subControlPoints) { - switch (CurveType) + switch (Type) { - case CurveType.Linear: - var result = new List(subControlPoints.Length); - foreach (var c in subControlPoints) - result.Add(c); - - return result; - case CurveType.PerfectCurve: + case PathType.Linear: + return PathApproximator.ApproximateLinear(subControlPoints); + case PathType.PerfectCurve: //we can only use CircularArc iff we have exactly three control points and no dissection. if (ControlPoints.Length != 3 || subControlPoints.Length != 3) break; // Here we have exactly 3 control points. Attempt to fit a circular arc. - List subpath = new CircularArcApproximator(subControlPoints).CreateArc(); + List subpath = PathApproximator.ApproximateCircularArc(subControlPoints); // If for some reason a circular arc could not be fit to the 3 given points, fall back to a numerically stable bezier approximation. if (subpath.Count == 0) break; return subpath; - case CurveType.Catmull: - return new CatmullApproximator(subControlPoints).CreateCatmull(); + case PathType.Catmull: + return PathApproximator.ApproximateCatmull(subControlPoints); } - return new BezierApproximator(subControlPoints).CreateBezier(); + return PathApproximator.ApproximateBezier(subControlPoints); } private void calculatePath() @@ -70,7 +178,7 @@ namespace osu.Game.Rulesets.Objects if (i == ControlPoints.Length - 1 || ControlPoints[i] == ControlPoints[i + 1]) { - ReadOnlySpan cpSpan = ControlPoints.AsSpan().Slice(start, end - start); + ReadOnlySpan cpSpan = ControlPoints.Slice(start, end - start); foreach (Vector2 t in calculateSubpath(cpSpan)) if (calculatedPath.Count == 0 || calculatedPath.Last() != t) @@ -81,7 +189,7 @@ namespace osu.Game.Rulesets.Objects } } - private void calculateCumulativeLengthAndTrimPath() + private void calculateCumulativeLength() { double l = 0; @@ -93,14 +201,13 @@ namespace osu.Game.Rulesets.Objects Vector2 diff = calculatedPath[i + 1] - calculatedPath[i]; double d = diff.Length; - // Shorten slider curves that are too long compared to what's - // in the .osu file. - if (Distance - l < d) + // Shorted slider paths that are too long compared to the expected distance + if (ExpectedDistance.HasValue && ExpectedDistance - l < d) { - calculatedPath[i + 1] = calculatedPath[i] + diff * (float)((Distance - l) / d); + calculatedPath[i + 1] = calculatedPath[i] + diff * (float)((ExpectedDistance - l) / d); calculatedPath.RemoveRange(i + 2, calculatedPath.Count - 2 - i); - l = Distance; + l = ExpectedDistance.Value; cumulativeLength.Add(l); break; } @@ -109,9 +216,8 @@ namespace osu.Game.Rulesets.Objects cumulativeLength.Add(l); } - // Lengthen slider curves that are too short compared to what's - // in the .osu file. - if (l < Distance && calculatedPath.Count > 1) + // Lengthen slider paths that are too short compared to the expected distance + if (ExpectedDistance.HasValue && l < ExpectedDistance && calculatedPath.Count > 1) { Vector2 diff = calculatedPath[calculatedPath.Count - 1] - calculatedPath[calculatedPath.Count - 2]; double d = diff.Length; @@ -119,17 +225,11 @@ namespace osu.Game.Rulesets.Objects if (d <= 0) return; - calculatedPath[calculatedPath.Count - 1] += diff * (float)((Distance - l) / d); - cumulativeLength[calculatedPath.Count - 1] = Distance; + calculatedPath[calculatedPath.Count - 1] += diff * (float)((ExpectedDistance - l) / d); + cumulativeLength[calculatedPath.Count - 1] = ExpectedDistance.Value; } } - public void Calculate() - { - calculatePath(); - calculateCumulativeLengthAndTrimPath(); - } - private int indexOfDistance(double d) { int i = cumulativeLength.BinarySearch(d); @@ -150,7 +250,7 @@ namespace osu.Game.Rulesets.Objects if (i <= 0) return calculatedPath.First(); - else if (i >= calculatedPath.Count) + if (i >= calculatedPath.Count) return calculatedPath.Last(); Vector2 p0 = calculatedPath[i - 1]; @@ -167,47 +267,20 @@ namespace osu.Game.Rulesets.Objects return p0 + (p1 - p0) * (float)w; } - /// - /// Computes the slider curve until a given progress that ranges from 0 (beginning of the slider) - /// to 1 (end of the slider) and stores the generated path in the given list. - /// - /// The list to be filled with the computed curve. - /// Start progress. Ranges from 0 (beginning of the slider) to 1 (end of the slider). - /// End progress. Ranges from 0 (beginning of the slider) to 1 (end of the slider). - public void GetPathToProgress(List path, double p0, double p1) + public bool Equals(SliderPath other) { - if (calculatedPath.Count == 0 && ControlPoints.Length > 0) - Calculate(); + if (ControlPoints == null && other.ControlPoints != null) + return false; + if (other.ControlPoints == null && ControlPoints != null) + return false; - double d0 = progressToDistance(p0); - double d1 = progressToDistance(p1); - - path.Clear(); - - int i = 0; - for (; i < calculatedPath.Count && cumulativeLength[i] < d0; ++i) { } - - path.Add(interpolateVertices(i, d0) + Offset); - - for (; i < calculatedPath.Count && cumulativeLength[i] <= d1; ++i) - path.Add(calculatedPath[i] + Offset); - - path.Add(interpolateVertices(i, d1) + Offset); + return ControlPoints.SequenceEqual(other.ControlPoints) && ExpectedDistance.Equals(other.ExpectedDistance) && Type == other.Type; } - /// - /// Computes the position on the slider at a given progress that ranges from 0 (beginning of the curve) - /// to 1 (end of the curve). - /// - /// Ranges from 0 (beginning of the curve) to 1 (end of the curve). - /// - public Vector2 PositionAt(double progress) + public override bool Equals(object obj) { - if (calculatedPath.Count == 0 && ControlPoints.Length > 0) - Calculate(); - - double d = progressToDistance(progress); - return interpolateVertices(indexOfDistance(d), d) + Offset; + if (ReferenceEquals(null, obj)) return false; + return obj is SliderPath other && Equals(other); } } } diff --git a/osu.Game/Rulesets/Objects/Types/IHasCurve.cs b/osu.Game/Rulesets/Objects/Types/IHasCurve.cs index 69b2f722e7..a6e2c15f5a 100644 --- a/osu.Game/Rulesets/Objects/Types/IHasCurve.cs +++ b/osu.Game/Rulesets/Objects/Types/IHasCurve.cs @@ -1,7 +1,7 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using OpenTK; +using osuTK; namespace osu.Game.Rulesets.Objects.Types { @@ -13,17 +13,7 @@ namespace osu.Game.Rulesets.Objects.Types /// /// The curve. /// - SliderCurve Curve { get; } - - /// - /// The control points that shape the curve. - /// - Vector2[] ControlPoints { get; } - - /// - /// The type of curve. - /// - CurveType CurveType { get; } + SliderPath Path { get; } } public static class HasCurveExtensions @@ -35,7 +25,7 @@ namespace osu.Game.Rulesets.Objects.Types /// [0, 1] where 0 is the start time of the and 1 is the end time of the . /// The position on the curve. public static Vector2 CurvePositionAt(this IHasCurve obj, double progress) - => obj.Curve.PositionAt(obj.ProgressAt(progress)); + => obj.Path.PositionAt(obj.ProgressAt(progress)); /// /// Computes the progress along the curve relative to how much of the has been completed. diff --git a/osu.Game/Rulesets/Objects/Types/IHasPosition.cs b/osu.Game/Rulesets/Objects/Types/IHasPosition.cs index eabae611d5..d14e4cc1fb 100644 --- a/osu.Game/Rulesets/Objects/Types/IHasPosition.cs +++ b/osu.Game/Rulesets/Objects/Types/IHasPosition.cs @@ -1,7 +1,7 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using OpenTK; +using osuTK; namespace osu.Game.Rulesets.Objects.Types { diff --git a/osu.Game/Rulesets/Objects/Types/IHasRepeats.cs b/osu.Game/Rulesets/Objects/Types/IHasRepeats.cs index 7d918ff160..ea8784db47 100644 --- a/osu.Game/Rulesets/Objects/Types/IHasRepeats.cs +++ b/osu.Game/Rulesets/Objects/Types/IHasRepeats.cs @@ -17,9 +17,15 @@ namespace osu.Game.Rulesets.Objects.Types int RepeatCount { get; } /// - /// The samples to be played when each repeat node is hit (0 -> first repeat node, 1 -> second repeat node, etc). + /// The samples to be played when each node of the is hit.
+ /// 0: The first node.
+ /// 1: The first repeat.
+ /// 2: The second repeat.
+ /// ...
+ /// n-1: The last repeat.
+ /// n: The last node. ///
- List> RepeatSamples { get; } + List> NodeSamples { get; } } public static class HasRepeatsExtensions diff --git a/osu.Game/Rulesets/Objects/Types/CurveType.cs b/osu.Game/Rulesets/Objects/Types/PathType.cs similarity index 91% rename from osu.Game/Rulesets/Objects/Types/CurveType.cs rename to osu.Game/Rulesets/Objects/Types/PathType.cs index 1cee6202b6..5156302fe1 100644 --- a/osu.Game/Rulesets/Objects/Types/CurveType.cs +++ b/osu.Game/Rulesets/Objects/Types/PathType.cs @@ -3,7 +3,7 @@ namespace osu.Game.Rulesets.Objects.Types { - public enum CurveType + public enum PathType { Catmull, Bezier, diff --git a/osu.Game/Rulesets/Replays/AutoGenerator.cs b/osu.Game/Rulesets/Replays/AutoGenerator.cs index 3ac0c3297c..1b6f17a5e4 100644 --- a/osu.Game/Rulesets/Replays/AutoGenerator.cs +++ b/osu.Game/Rulesets/Replays/AutoGenerator.cs @@ -3,6 +3,7 @@ using osu.Game.Rulesets.Objects; using osu.Game.Beatmaps; +using osu.Game.Replays; namespace osu.Game.Rulesets.Replays { diff --git a/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs b/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs index edad5cff80..abe1a1520d 100644 --- a/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs +++ b/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs @@ -5,8 +5,9 @@ using System; using System.Collections.Generic; using osu.Framework.Input.StateChanges; using osu.Game.Input.Handlers; -using OpenTK; -using OpenTK.Input; +using osu.Game.Replays; +using osuTK; +using osuTK.Input; namespace osu.Game.Rulesets.Replays { diff --git a/osu.Game/Rulesets/Replays/IAutoGenerator.cs b/osu.Game/Rulesets/Replays/IAutoGenerator.cs index 4ef5f16f39..90aea199c8 100644 --- a/osu.Game/Rulesets/Replays/IAutoGenerator.cs +++ b/osu.Game/Rulesets/Replays/IAutoGenerator.cs @@ -1,6 +1,8 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using osu.Game.Replays; + namespace osu.Game.Rulesets.Replays { public interface IAutoGenerator diff --git a/osu.Game/Rulesets/Replays/Types/IConvertibleReplayFrame.cs b/osu.Game/Rulesets/Replays/Types/IConvertibleReplayFrame.cs index fdd528f296..9ece4b80d5 100644 --- a/osu.Game/Rulesets/Replays/Types/IConvertibleReplayFrame.cs +++ b/osu.Game/Rulesets/Replays/Types/IConvertibleReplayFrame.cs @@ -2,7 +2,7 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using osu.Game.Beatmaps; -using osu.Game.Rulesets.Replays.Legacy; +using osu.Game.Replays.Legacy; namespace osu.Game.Rulesets.Replays.Types { diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs index 82d9945ef7..53e3e40374 100644 --- a/osu.Game/Rulesets/Ruleset.cs +++ b/osu.Game/Rulesets/Ruleset.cs @@ -12,12 +12,12 @@ using osu.Game.Overlays.Settings; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Replays.Types; -using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; using osu.Game.Beatmaps.Legacy; using osu.Game.Configuration; using osu.Game.Rulesets.Configuration; using osu.Game.Rulesets.Difficulty; +using osu.Game.Scoring; namespace osu.Game.Rulesets { @@ -73,7 +73,7 @@ namespace osu.Game.Rulesets public abstract DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap); - public virtual PerformanceCalculator CreatePerformanceCalculator(WorkingBeatmap beatmap, Score score) => null; + public virtual PerformanceCalculator CreatePerformanceCalculator(WorkingBeatmap beatmap, ScoreInfo score) => null; public virtual HitObjectComposer CreateHitObjectComposer() => null; diff --git a/osu.Game/Rulesets/RulesetInfo.cs b/osu.Game/Rulesets/RulesetInfo.cs index a6a311f6eb..6c21b9fd2f 100644 --- a/osu.Game/Rulesets/RulesetInfo.cs +++ b/osu.Game/Rulesets/RulesetInfo.cs @@ -2,15 +2,12 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System; -using System.ComponentModel.DataAnnotations.Schema; using Newtonsoft.Json; namespace osu.Game.Rulesets { public class RulesetInfo : IEquatable { - [DatabaseGenerated(DatabaseGeneratedOption.Identity)] - [JsonIgnore] public int? ID { get; set; } public string Name { get; set; } diff --git a/osu.Game/Rulesets/Scoring/Legacy/LegacyScoreParser.cs b/osu.Game/Rulesets/Scoring/Legacy/LegacyScoreParser.cs deleted file mode 100644 index a90cd79186..0000000000 --- a/osu.Game/Rulesets/Scoring/Legacy/LegacyScoreParser.cs +++ /dev/null @@ -1,189 +0,0 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.IO; -using osu.Game.Beatmaps; -using osu.Game.IO.Legacy; -using osu.Game.Rulesets.Replays; -using osu.Game.Rulesets.Replays.Legacy; -using osu.Game.Users; -using SharpCompress.Compressors.LZMA; -using osu.Game.Beatmaps.Legacy; -using System.Linq; - -namespace osu.Game.Rulesets.Scoring.Legacy -{ - public abstract class LegacyScoreParser - { - private IBeatmap currentBeatmap; - private Ruleset currentRuleset; - - public Score Parse(Stream stream) - { - Score score; - - using (SerializationReader sr = new SerializationReader(stream)) - { - currentRuleset = GetRuleset(sr.ReadByte()); - score = new Score { Ruleset = currentRuleset.RulesetInfo }; - - /* score.Pass = true;*/ - var version = sr.ReadInt32(); - - /* score.FileChecksum = */ - currentBeatmap = GetBeatmap(sr.ReadString()).Beatmap; - score.Beatmap = currentBeatmap.BeatmapInfo; - - /* score.PlayerName = */ - score.User = new User { Username = sr.ReadString() }; - /* var localScoreChecksum = */ - sr.ReadString(); - - var count300 = sr.ReadUInt16(); - var count100 = sr.ReadUInt16(); - var count50 = sr.ReadUInt16(); - var countGeki = sr.ReadUInt16(); - var countKatu = sr.ReadUInt16(); - var countMiss = sr.ReadUInt16(); - - score.Statistics[HitResult.Great] = count300; - score.Statistics[HitResult.Good] = count100; - score.Statistics[HitResult.Meh] = count50; - score.Statistics[HitResult.Perfect] = countGeki; - score.Statistics[HitResult.Ok] = countKatu; - score.Statistics[HitResult.Miss] = countMiss; - - score.TotalScore = sr.ReadInt32(); - score.MaxCombo = sr.ReadUInt16(); - /* score.Perfect = */ - sr.ReadBoolean(); - /* score.EnabledMods = (Mods)*/ - score.Mods = currentRuleset.ConvertLegacyMods((LegacyMods)sr.ReadInt32()).ToArray(); - /* score.HpGraphString = */ - sr.ReadString(); - /* score.Date = */ - sr.ReadDateTime(); - - var compressedReplay = sr.ReadByteArray(); - - if (version >= 20140721) - /*OnlineId =*/ - sr.ReadInt64(); - else if (version >= 20121008) - /*OnlineId =*/ - sr.ReadInt32(); - - switch (score.Ruleset.ID) - { - case 0: - { - int totalHits = count50 + count100 + count300 + countMiss; - score.Accuracy = totalHits > 0 ? (double)(count50 * 50 + count100 * 100 + count300 * 300) / (totalHits * 300) : 1; - break; - } - case 1: - { - int totalHits = count50 + count100 + count300 + countMiss; - score.Accuracy = totalHits > 0 ? (double)(count100 * 150 + count300 * 300) / (totalHits * 300) : 1; - break; - } - case 2: - { - int totalHits = count50 + count100 + count300 + countMiss + countKatu; - score.Accuracy = totalHits > 0 ? (double)(count50 + count100 + count300 ) / totalHits : 1; - break; - } - case 3: - { - int totalHits = count50 + count100 + count300 + countMiss + countGeki + countKatu; - score.Accuracy = totalHits > 0 ? (double)(count50 * 50 + count100 * 100 + countKatu * 200 + (count300 + countGeki) * 300) / (totalHits * 300) : 1; - break; - } - } - - using (var replayInStream = new MemoryStream(compressedReplay)) - { - byte[] properties = new byte[5]; - if (replayInStream.Read(properties, 0, 5) != 5) - throw new IOException("input .lzma is too short"); - long outSize = 0; - for (int i = 0; i < 8; i++) - { - int v = replayInStream.ReadByte(); - if (v < 0) - throw new IOException("Can't Read 1"); - outSize |= (long)(byte)v << (8 * i); - } - - long compressedSize = replayInStream.Length - replayInStream.Position; - - using (var lzma = new LzmaStream(properties, replayInStream, compressedSize, outSize)) - using (var reader = new StreamReader(lzma)) - { - score.Replay = new Replay { User = score.User }; - readLegacyReplay(score.Replay, reader); - } - } - } - - return score; - } - - private void readLegacyReplay(Replay replay, StreamReader reader) - { - float lastTime = 0; - - foreach (var l in reader.ReadToEnd().Split(',')) - { - var split = l.Split('|'); - - if (split.Length < 4) - continue; - - if (split[0] == "-12345") - { - // Todo: The seed is provided in split[3], which we'll need to use at some point - continue; - } - - var diff = float.Parse(split[0]); - lastTime += diff; - - // Todo: At some point we probably want to rewind and play back the negative-time frames - // but for now we'll achieve equal playback to stable by skipping negative frames - if (diff < 0) - continue; - - replay.Frames.Add(convertFrame(new LegacyReplayFrame(lastTime, float.Parse(split[1]), float.Parse(split[2]), (ReplayButtonState)int.Parse(split[3])))); - } - } - - private ReplayFrame convertFrame(LegacyReplayFrame legacyFrame) - { - var convertible = currentRuleset.CreateConvertibleReplayFrame(); - if (convertible == null) - throw new InvalidOperationException($"Legacy replay cannot be converted for the ruleset: {currentRuleset.Description}"); - convertible.ConvertFrom(legacyFrame, currentBeatmap); - - var frame = (ReplayFrame)convertible; - frame.Time = legacyFrame.Time; - - return frame; - } - - /// - /// Retrieves the for a specific id. - /// - /// The id. - /// The . - protected abstract Ruleset GetRuleset(int rulesetId); - - /// - /// Retrieves the corresponding to an MD5 hash. - /// - /// The MD5 hash. - /// The . - protected abstract WorkingBeatmap GetBeatmap(string md5Hash); - } -} diff --git a/osu.Game/Rulesets/Scoring/Score.cs b/osu.Game/Rulesets/Scoring/Score.cs deleted file mode 100644 index 02f528791a..0000000000 --- a/osu.Game/Rulesets/Scoring/Score.cs +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.Collections.Generic; -using Newtonsoft.Json; -using osu.Game.Beatmaps; -using osu.Game.Rulesets.Mods; -using osu.Game.Users; -using osu.Game.Rulesets.Replays; - -namespace osu.Game.Rulesets.Scoring -{ - public class Score - { - public ScoreRank Rank { get; set; } - - public double TotalScore { get; set; } - - public double Accuracy { get; set; } - - public double Health { get; set; } = 1; - - public double? PP { get; set; } - - public int MaxCombo { get; set; } - - public int Combo { get; set; } - - public RulesetInfo Ruleset { get; set; } - - public Mod[] Mods { get; set; } = { }; - - public User User; - - [JsonIgnore] - public Replay Replay; - - public BeatmapInfo Beatmap; - - public long OnlineScoreID; - - public DateTimeOffset Date; - - public Dictionary Statistics = new Dictionary(); - } -} diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index b0cea7009e..0ebea9c2d0 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -9,6 +9,7 @@ using osu.Game.Beatmaps; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.UI; +using osu.Game.Scoring; namespace osu.Game.Rulesets.Scoring { @@ -157,15 +158,14 @@ namespace osu.Game.Rulesets.Scoring /// /// Retrieve a score populated with data for the current play this processor is responsible for. /// - public virtual void PopulateScore(Score score) + public virtual void PopulateScore(ScoreInfo score) { - score.TotalScore = TotalScore; + score.TotalScore = (int)Math.Round(TotalScore); score.Combo = Combo; score.MaxCombo = HighestCombo; - score.Accuracy = Accuracy; + score.Accuracy = Math.Round(Accuracy, 4); score.Rank = Rank; score.Date = DateTimeOffset.Now; - score.Health = Health; } } diff --git a/osu.Game/Rulesets/Scoring/ScoreStore.cs b/osu.Game/Rulesets/Scoring/ScoreStore.cs deleted file mode 100644 index 091cb29a71..0000000000 --- a/osu.Game/Rulesets/Scoring/ScoreStore.cs +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.IO; -using osu.Framework.Logging; -using osu.Framework.Platform; -using osu.Game.Beatmaps; -using osu.Game.Database; -using osu.Game.IPC; -using osu.Game.Rulesets.Scoring.Legacy; - -namespace osu.Game.Rulesets.Scoring -{ - public class ScoreStore : DatabaseBackedStore, ICanAcceptFiles - { - private readonly BeatmapManager beatmaps; - private readonly RulesetStore rulesets; - - private const string replay_folder = @"replays"; - - public event Action ScoreImported; - - // ReSharper disable once NotAccessedField.Local (we should keep a reference to this so it is not finalised) - private ScoreIPCChannel ipc; - - public ScoreStore(DatabaseContextFactory factory, IIpcHost importHost = null, BeatmapManager beatmaps = null, RulesetStore rulesets = null) : base(factory) - { - this.beatmaps = beatmaps; - this.rulesets = rulesets; - - if (importHost != null) - ipc = new ScoreIPCChannel(importHost, this); - } - - public string[] HandledExtensions => new[] { ".osr" }; - - public void Import(params string[] paths) - { - foreach (var path in paths) - { - var score = ReadReplayFile(path); - if (score != null) - ScoreImported?.Invoke(score); - } - } - - public Score ReadReplayFile(string replayFilename) - { - if (File.Exists(replayFilename)) - { - using (var stream = File.OpenRead(replayFilename)) - return new DatabasedLegacyScoreParser(rulesets, beatmaps).Parse(stream); - } - - Logger.Log($"Replay file {replayFilename} cannot be found", LoggingTarget.Information, LogLevel.Error); - return null; - } - } -} diff --git a/osu.Game/Rulesets/UI/ModIcon.cs b/osu.Game/Rulesets/UI/ModIcon.cs index 92e9a4831f..b5c8da8879 100644 --- a/osu.Game/Rulesets/UI/ModIcon.cs +++ b/osu.Game/Rulesets/UI/ModIcon.cs @@ -2,14 +2,14 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System; -using OpenTK.Graphics; +using osuTK.Graphics; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; using osu.Game.Graphics; using osu.Game.Rulesets.Mods; -using OpenTK; +using osuTK; namespace osu.Game.Rulesets.UI { diff --git a/osu.Game/Rulesets/UI/Playfield.cs b/osu.Game/Rulesets/UI/Playfield.cs index 886eb3ac0e..1e9a19ae70 100644 --- a/osu.Game/Rulesets/UI/Playfield.cs +++ b/osu.Game/Rulesets/UI/Playfield.cs @@ -12,7 +12,7 @@ using osu.Framework.Configuration; using osu.Framework.Graphics.Containers; using osu.Game.Beatmaps; using osu.Game.Rulesets.Mods; -using OpenTK; +using osuTK; namespace osu.Game.Rulesets.UI { @@ -80,7 +80,7 @@ namespace osu.Game.Rulesets.UI /// Remove a DrawableHitObject from this Playfield. ///
/// The DrawableHitObject to remove. - public virtual void Remove(DrawableHitObject h) => HitObjectContainer.Remove(h); + public virtual bool Remove(DrawableHitObject h) => HitObjectContainer.Remove(h); /// /// Registers a as a nested . diff --git a/osu.Game/Rulesets/UI/RulesetContainer.cs b/osu.Game/Rulesets/UI/RulesetContainer.cs index 3c67e78799..72e32290e2 100644 --- a/osu.Game/Rulesets/UI/RulesetContainer.cs +++ b/osu.Game/Rulesets/UI/RulesetContainer.cs @@ -19,8 +19,8 @@ using osu.Framework.Input; using osu.Game.Configuration; using osu.Game.Input.Handlers; using osu.Game.Overlays; +using osu.Game.Replays; using osu.Game.Rulesets.Configuration; -using osu.Game.Rulesets.Replays; using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.UI @@ -79,7 +79,7 @@ namespace osu.Game.Rulesets.UI /// public readonly CursorContainer Cursor; - protected readonly Ruleset Ruleset; + public readonly Ruleset Ruleset; protected IRulesetConfigManager Config { get; private set; } @@ -250,6 +250,8 @@ namespace osu.Game.Rulesets.UI KeyBindingInputManager = CreateInputManager(); KeyBindingInputManager.RelativeSizeAxes = Axes.Both; + + applyBeatmapMods(Mods); } [BackgroundDependencyLoader] @@ -267,16 +269,29 @@ namespace osu.Game.Rulesets.UI KeyBindingInputManager.Add(Cursor); // Apply mods - applyMods(Mods, config); + applyRulesetMods(Mods, config); loadObjects(); } + /// + /// Applies the active mods to the Beatmap. + /// + /// + private void applyBeatmapMods(IEnumerable mods) + { + if (mods == null) + return; + + foreach (var mod in mods.OfType>()) + mod.ApplyToBeatmap(Beatmap); + } + /// /// Applies the active mods to this RulesetContainer. /// /// - private void applyMods(IEnumerable mods, OsuConfigManager config) + private void applyRulesetMods(IEnumerable mods, OsuConfigManager config) { if (mods == null) return; @@ -302,17 +317,7 @@ namespace osu.Game.Rulesets.UI private void loadObjects() { foreach (TObject h in Beatmap.HitObjects) - { - var drawableObject = GetVisualRepresentation(h); - - if (drawableObject == null) - continue; - - drawableObject.OnNewResult += (_, r) => OnNewResult?.Invoke(r); - drawableObject.OnRevertResult += (_, r) => OnRevertResult?.Invoke(r); - - Playfield.Add(drawableObject); - } + AddRepresentation(h); Playfield.PostProcess(); @@ -325,12 +330,29 @@ namespace osu.Game.Rulesets.UI ///
public IEnumerable ActiveMods { get => Mods; } + /// + /// Creates and adds the visual representation of a to this . + /// + /// The to add the visual representation for. + internal void AddRepresentation(TObject hitObject) + { + var drawableObject = GetVisualRepresentation(hitObject); + + if (drawableObject == null) + return; + + drawableObject.OnNewResult += (_, r) => OnNewResult?.Invoke(r); + drawableObject.OnRevertResult += (_, r) => OnRevertResult?.Invoke(r); + + Playfield.Add(drawableObject); + } + /// /// Creates a DrawableHitObject from a HitObject. /// /// The HitObject to make drawable. /// The DrawableHitObject. - protected abstract DrawableHitObject GetVisualRepresentation(TObject h); + public abstract DrawableHitObject GetVisualRepresentation(TObject h); } /// diff --git a/osu.Game/Rulesets/UI/RulesetInputManager.cs b/osu.Game/Rulesets/UI/RulesetInputManager.cs index 340833c090..a76942e3fa 100644 --- a/osu.Game/Rulesets/UI/RulesetInputManager.cs +++ b/osu.Game/Rulesets/UI/RulesetInputManager.cs @@ -16,7 +16,7 @@ using osu.Game.Configuration; using osu.Game.Input.Bindings; using osu.Game.Input.Handlers; using osu.Game.Screens.Play; -using OpenTK.Input; +using osuTK.Input; using static osu.Game.Input.Handlers.ReplayInputHandler; using JoystickState = osu.Framework.Input.States.JoystickState; using KeyboardState = osu.Framework.Input.States.KeyboardState; diff --git a/osu.Game/Rulesets/UI/Scrolling/Algorithms/ConstantScrollAlgorithm.cs b/osu.Game/Rulesets/UI/Scrolling/Algorithms/ConstantScrollAlgorithm.cs new file mode 100644 index 0000000000..5628fb51f3 --- /dev/null +++ b/osu.Game/Rulesets/UI/Scrolling/Algorithms/ConstantScrollAlgorithm.cs @@ -0,0 +1,27 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +namespace osu.Game.Rulesets.UI.Scrolling.Algorithms +{ + public class ConstantScrollAlgorithm : IScrollAlgorithm + { + public double GetDisplayStartTime(double time, double timeRange) => time - timeRange; + + public float GetLength(double startTime, double endTime, double timeRange, float scrollLength) + { + // At the hitobject's end time, the hitobject will be positioned such that its end rests at the origin. + // This results in a negative-position value, and the absolute of it indicates the length of the hitobject. + return -PositionAt(startTime, endTime, timeRange, scrollLength); + } + + public float PositionAt(double time, double currentTime, double timeRange, float scrollLength) + => (float)((time - currentTime) / timeRange * scrollLength); + + public double TimeAt(float position, double currentTime, double timeRange, float scrollLength) + => position * timeRange / scrollLength + currentTime; + + public void Reset() + { + } + } +} diff --git a/osu.Game/Rulesets/UI/Scrolling/Algorithms/IScrollAlgorithm.cs b/osu.Game/Rulesets/UI/Scrolling/Algorithms/IScrollAlgorithm.cs new file mode 100644 index 0000000000..2ece9bef9b --- /dev/null +++ b/osu.Game/Rulesets/UI/Scrolling/Algorithms/IScrollAlgorithm.cs @@ -0,0 +1,54 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +namespace osu.Game.Rulesets.UI.Scrolling.Algorithms +{ + public interface IScrollAlgorithm + { + /// + /// Given a point in time, computes the time at which it enters the time range. + /// + /// + /// E.g. For a constant time range of 5000ms, the time at which t=7000ms enters the time range is 2000ms. + /// + /// The point in time. + /// The amount of visible time. + /// The time at which enters . + double GetDisplayStartTime(double time, double timeRange); + + /// + /// Computes the spatial length within a start and end time. + /// + /// The start time. + /// The end time. + /// The amount of visible time. + /// The absolute spatial length through . + /// The absolute spatial length. + float GetLength(double startTime, double endTime, double timeRange, float scrollLength); + + /// + /// Given the current time, computes the spatial position of a point in time. + /// + /// The time to compute the spatial position of. + /// The current time. + /// The amount of visible time. + /// The absolute spatial length through . + /// The absolute spatial position. + float PositionAt(double time, double currentTime, double timeRange, float scrollLength); + + /// + /// Computes the time which brings a point to a provided spatial position given the current time. + /// + /// The absolute spatial position. + /// The current time. + /// The amount of visible time. + /// The absolute spatial length through . + /// The time at which == . + double TimeAt(float position, double currentTime, double timeRange, float scrollLength); + + /// + /// Resets this to a default state. + /// + void Reset(); + } +} diff --git a/osu.Game/Rulesets/UI/Scrolling/Algorithms/OverlappingScrollAlgorithm.cs b/osu.Game/Rulesets/UI/Scrolling/Algorithms/OverlappingScrollAlgorithm.cs new file mode 100644 index 0000000000..231aaa3c02 --- /dev/null +++ b/osu.Game/Rulesets/UI/Scrolling/Algorithms/OverlappingScrollAlgorithm.cs @@ -0,0 +1,93 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Lists; +using osu.Game.Rulesets.Timing; +using osuTK; + +namespace osu.Game.Rulesets.UI.Scrolling.Algorithms +{ + public class OverlappingScrollAlgorithm : IScrollAlgorithm + { + private readonly MultiplierControlPoint searchPoint; + + private readonly SortedList controlPoints; + + public OverlappingScrollAlgorithm(SortedList controlPoints) + { + this.controlPoints = controlPoints; + + searchPoint = new MultiplierControlPoint(); + } + + public double GetDisplayStartTime(double time, double timeRange) + { + // The total amount of time that the hitobject will remain visible within the timeRange, which decreases as the speed multiplier increases + double visibleDuration = timeRange / controlPointAt(time).Multiplier; + return time - visibleDuration; + } + + public float GetLength(double startTime, double endTime, double timeRange, float scrollLength) + { + // At the hitobject's end time, the hitobject will be positioned such that its end rests at the origin. + // This results in a negative-position value, and the absolute of it indicates the length of the hitobject. + return -PositionAt(startTime, endTime, timeRange, scrollLength); + } + + public float PositionAt(double time, double currentTime, double timeRange, float scrollLength) + => (float)((time - currentTime) / timeRange * controlPointAt(time).Multiplier * scrollLength); + + public double TimeAt(float position, double currentTime, double timeRange, float scrollLength) + { + // Find the control point relating to the position. + // Note: Due to velocity adjustments, overlapping control points will provide multiple valid time values for a single position + // As such, this operation provides unexpected results by using the latter of the control points. + + int i = 0; + float pos = 0; + + for (; i < controlPoints.Count; i++) + { + float lastPos = pos; + pos = PositionAt(controlPoints[i].StartTime, currentTime, timeRange, scrollLength); + + if (pos > position) + { + i--; + pos = lastPos; + break; + } + } + + i = MathHelper.Clamp(i, 0, controlPoints.Count - 1); + + return controlPoints[i].StartTime + (position - pos) * timeRange / controlPoints[i].Multiplier / scrollLength; + } + + public void Reset() + { + } + + /// + /// Finds the which affects the speed of hitobjects at a specific time. + /// + /// The time which the should affect. + /// The . + private MultiplierControlPoint controlPointAt(double time) + { + if (controlPoints.Count == 0) + return new MultiplierControlPoint(double.NegativeInfinity); + + if (time < controlPoints[0].StartTime) + return controlPoints[0]; + + searchPoint.StartTime = time; + int index = controlPoints.BinarySearch(searchPoint); + + if (index < 0) + index = ~index - 1; + + return controlPoints[index]; + } + } +} diff --git a/osu.Game/Rulesets/UI/Scrolling/Algorithms/SequentialScrollAlgorithm.cs b/osu.Game/Rulesets/UI/Scrolling/Algorithms/SequentialScrollAlgorithm.cs new file mode 100644 index 0000000000..8f8f546992 --- /dev/null +++ b/osu.Game/Rulesets/UI/Scrolling/Algorithms/SequentialScrollAlgorithm.cs @@ -0,0 +1,117 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using osu.Game.Rulesets.Timing; + +namespace osu.Game.Rulesets.UI.Scrolling.Algorithms +{ + public class SequentialScrollAlgorithm : IScrollAlgorithm + { + private readonly Dictionary positionCache; + + private readonly IReadOnlyList controlPoints; + + public SequentialScrollAlgorithm(IReadOnlyList controlPoints) + { + this.controlPoints = controlPoints; + + positionCache = new Dictionary(); + } + + public double GetDisplayStartTime(double time, double timeRange) => time - timeRange - 1000; + + public float GetLength(double startTime, double endTime, double timeRange, float scrollLength) + { + var objectLength = relativePositionAtCached(endTime, timeRange) - relativePositionAtCached(startTime, timeRange); + return (float)(objectLength * scrollLength); + } + + public float PositionAt(double time, double currentTime, double timeRange, float scrollLength) + { + // Caching is not used here as currentTime is unlikely to have been previously cached + double timelinePosition = relativePositionAt(currentTime, timeRange); + return (float)((relativePositionAtCached(time, timeRange) - timelinePosition) * scrollLength); + } + + public double TimeAt(float position, double currentTime, double timeRange, float scrollLength) + { + // Convert the position to a length relative to time = 0 + double length = position / scrollLength + relativePositionAt(currentTime, timeRange); + + // We need to consider all timing points until the specified time and not just the currently-active one, + // since each timing point individually affects the positions of _all_ hitobjects after its start time + for (int i = 0; i < controlPoints.Count; i++) + { + var current = controlPoints[i]; + var next = i < controlPoints.Count - 1 ? controlPoints[i + 1] : null; + + // Duration of the current control point + var currentDuration = (next?.StartTime ?? double.PositiveInfinity) - current.StartTime; + + // Figure out the length of control point + var currentLength = currentDuration / timeRange * current.Multiplier; + + if (currentLength > length) + { + // The point is within this control point + return current.StartTime + length * timeRange / current.Multiplier; + } + + length -= currentLength; + } + + return 0; // Should never occur + } + + private double relativePositionAtCached(double time, double timeRange) + { + if (!positionCache.TryGetValue(time, out double existing)) + positionCache[time] = existing = relativePositionAt(time, timeRange); + return existing; + } + + public void Reset() => positionCache.Clear(); + + /// + /// Finds the position which corresponds to a point in time. + /// This is a non-linear operation that depends on all the control points up to and including the one active at the time value. + /// + /// The time to find the position at. + /// The amount of time visualised by the scrolling area. + /// A positive value indicating the position at . + private double relativePositionAt(double time, double timeRange) + { + if (controlPoints.Count == 0) + return time / timeRange; + + double length = 0; + + // We need to consider all timing points until the specified time and not just the currently-active one, + // since each timing point individually affects the positions of _all_ hitobjects after its start time + for (int i = 0; i < controlPoints.Count; i++) + { + var current = controlPoints[i]; + var next = i < controlPoints.Count - 1 ? controlPoints[i + 1] : null; + + // We don't need to consider any control points beyond the current time, since it will not yet + // affect any hitobjects + if (i > 0 && current.StartTime > time) + continue; + + // Duration of the current control point + var currentDuration = (next?.StartTime ?? double.PositiveInfinity) - current.StartTime; + + // We want to consider the minimal amount of time that this control point has affected, + // which may be either its duration, or the amount of time that has passed within it + var durationInCurrent = Math.Min(currentDuration, time - current.StartTime); + + // Figure out how much of the time range the duration represents, and adjust it by the speed multiplier + length += durationInCurrent / timeRange * current.Multiplier; + } + + return length; + } + } +} diff --git a/osu.Game.Rulesets.Mania/UI/IScrollingInfo.cs b/osu.Game/Rulesets/UI/Scrolling/IScrollingInfo.cs similarity index 54% rename from osu.Game.Rulesets.Mania/UI/IScrollingInfo.cs rename to osu.Game/Rulesets/UI/Scrolling/IScrollingInfo.cs index ee65e9f1a5..21cbd855a9 100644 --- a/osu.Game.Rulesets.Mania/UI/IScrollingInfo.cs +++ b/osu.Game/Rulesets/UI/Scrolling/IScrollingInfo.cs @@ -3,9 +3,9 @@ using osu.Framework.Configuration; using osu.Game.Rulesets.Objects; -using osu.Game.Rulesets.UI.Scrolling; +using osu.Game.Rulesets.UI.Scrolling.Algorithms; -namespace osu.Game.Rulesets.Mania.UI +namespace osu.Game.Rulesets.UI.Scrolling { public interface IScrollingInfo { @@ -13,5 +13,15 @@ namespace osu.Game.Rulesets.Mania.UI /// The direction s should scroll in. /// IBindable Direction { get; } + + /// + /// + /// + IBindable TimeRange { get; } + + /// + /// The algorithm which controls positions and sizes. + /// + IScrollAlgorithm Algorithm { get; } } } diff --git a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs index 7307fc0ead..00642b3d41 100644 --- a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs +++ b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs @@ -1,58 +1,39 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using osu.Framework.Allocation; using osu.Framework.Caching; using osu.Framework.Configuration; using osu.Framework.Graphics; -using osu.Framework.Lists; -using osu.Game.Configuration; using osu.Game.Rulesets.Objects.Drawables; -using osu.Game.Rulesets.Timing; -using osu.Game.Rulesets.UI.Scrolling.Visualisers; +using osu.Game.Rulesets.Objects.Types; namespace osu.Game.Rulesets.UI.Scrolling { public class ScrollingHitObjectContainer : HitObjectContainer { - /// - /// The duration required to scroll through one length of the before any control point adjustments. - /// - public readonly BindableDouble TimeRange = new BindableDouble - { - MinValue = 0, - MaxValue = double.MaxValue - }; + private readonly IBindable timeRange = new BindableDouble(); - /// - /// The control points that adjust the scrolling speed. - /// - protected readonly SortedList ControlPoints = new SortedList(); + private readonly IBindable direction = new Bindable(); - public readonly Bindable Direction = new Bindable(); + [Resolved] + private IScrollingInfo scrollingInfo { get; set; } private Cached initialStateCache = new Cached(); - private readonly ISpeedChangeVisualiser speedChangeVisualiser; - - public ScrollingHitObjectContainer(SpeedChangeVisualisationMethod visualisationMethod) + public ScrollingHitObjectContainer() { RelativeSizeAxes = Axes.Both; + } - TimeRange.ValueChanged += _ => initialStateCache.Invalidate(); - Direction.ValueChanged += _ => initialStateCache.Invalidate(); + [BackgroundDependencyLoader] + private void load() + { + direction.BindTo(scrollingInfo.Direction); + timeRange.BindTo(scrollingInfo.TimeRange); - switch (visualisationMethod) - { - case SpeedChangeVisualisationMethod.Sequential: - speedChangeVisualiser = new SequentialSpeedChangeVisualiser(ControlPoints); - break; - case SpeedChangeVisualisationMethod.Overlapping: - speedChangeVisualiser = new OverlappingSpeedChangeVisualiser(ControlPoints); - break; - case SpeedChangeVisualisationMethod.Constant: - speedChangeVisualiser = new ConstantSpeedChangeVisualiser(); - break; - } + direction.ValueChanged += _ => initialStateCache.Invalidate(); + timeRange.ValueChanged += _ => initialStateCache.Invalidate(); } public override void Add(DrawableHitObject hitObject) @@ -69,20 +50,6 @@ namespace osu.Game.Rulesets.UI.Scrolling return result; } - public void AddControlPoint(MultiplierControlPoint controlPoint) - { - ControlPoints.Add(controlPoint); - initialStateCache.Invalidate(); - } - - public bool RemoveControlPoint(MultiplierControlPoint controlPoint) - { - var result = ControlPoints.Remove(controlPoint); - if (result) - initialStateCache.Invalidate(); - return result; - } - public override bool Invalidate(Invalidation invalidation = Invalidation.All, Drawable source = null, bool shallPropagate = true) { if ((invalidation & (Invalidation.RequiredParentSizeToFit | Invalidation.DrawInfo)) > 0) @@ -91,23 +58,87 @@ namespace osu.Game.Rulesets.UI.Scrolling return base.Invalidate(invalidation, source, shallPropagate); } + private float scrollLength; + protected override void Update() { base.Update(); if (!initialStateCache.IsValid) { - speedChangeVisualiser.ComputeInitialStates(Objects, Direction, TimeRange, DrawSize); + switch (direction.Value) + { + case ScrollingDirection.Up: + case ScrollingDirection.Down: + scrollLength = DrawSize.Y; + break; + default: + scrollLength = DrawSize.X; + break; + } + + scrollingInfo.Algorithm.Reset(); + + foreach (var obj in Objects) + computeInitialStateRecursive(obj); initialStateCache.Validate(); } } + private void computeInitialStateRecursive(DrawableHitObject hitObject) + { + hitObject.LifetimeStart = scrollingInfo.Algorithm.GetDisplayStartTime(hitObject.HitObject.StartTime, timeRange.Value); + + if (hitObject.HitObject is IHasEndTime endTime) + { + switch (direction.Value) + { + case ScrollingDirection.Up: + case ScrollingDirection.Down: + hitObject.Height = scrollingInfo.Algorithm.GetLength(hitObject.HitObject.StartTime, endTime.EndTime, timeRange.Value, scrollLength); + break; + case ScrollingDirection.Left: + case ScrollingDirection.Right: + hitObject.Width = scrollingInfo.Algorithm.GetLength(hitObject.HitObject.StartTime, endTime.EndTime, timeRange.Value, scrollLength); + break; + } + } + + foreach (var obj in hitObject.NestedHitObjects) + { + computeInitialStateRecursive(obj); + + // Nested hitobjects don't need to scroll, but they do need accurate positions + updatePosition(obj, hitObject.HitObject.StartTime); + } + } + protected override void UpdateAfterChildrenLife() { base.UpdateAfterChildrenLife(); - // We need to calculate this as soon as possible after lifetimes so that hitobjects get the final say in their positions - speedChangeVisualiser.UpdatePositions(AliveObjects, Direction, Time.Current, TimeRange, DrawSize); + // We need to calculate hitobject positions as soon as possible after lifetimes so that hitobjects get the final say in their positions + foreach (var obj in AliveObjects) + updatePosition(obj, Time.Current); + } + + private void updatePosition(DrawableHitObject hitObject, double currentTime) + { + switch (direction.Value) + { + case ScrollingDirection.Up: + hitObject.Y = scrollingInfo.Algorithm.PositionAt(hitObject.HitObject.StartTime, currentTime, timeRange.Value, scrollLength); + break; + case ScrollingDirection.Down: + hitObject.Y = -scrollingInfo.Algorithm.PositionAt(hitObject.HitObject.StartTime, currentTime, timeRange.Value, scrollLength); + break; + case ScrollingDirection.Left: + hitObject.X = scrollingInfo.Algorithm.PositionAt(hitObject.HitObject.StartTime, currentTime, timeRange.Value, scrollLength); + break; + case ScrollingDirection.Right: + hitObject.X = -scrollingInfo.Algorithm.PositionAt(hitObject.HitObject.StartTime, currentTime, timeRange.Value, scrollLength); + break; + } } } } diff --git a/osu.Game/Rulesets/UI/Scrolling/ScrollingPlayfield.cs b/osu.Game/Rulesets/UI/Scrolling/ScrollingPlayfield.cs index a1fc13ce4d..0eb67b8bb1 100644 --- a/osu.Game/Rulesets/UI/Scrolling/ScrollingPlayfield.cs +++ b/osu.Game/Rulesets/UI/Scrolling/ScrollingPlayfield.cs @@ -3,10 +3,6 @@ using osu.Framework.Allocation; using osu.Framework.Configuration; -using osu.Framework.Graphics; -using osu.Framework.Input.Bindings; -using osu.Game.Configuration; -using osu.Game.Input.Bindings; using osu.Game.Rulesets.Objects.Drawables; namespace osu.Game.Rulesets.UI.Scrolling @@ -14,88 +10,19 @@ namespace osu.Game.Rulesets.UI.Scrolling /// /// A type of specialized towards scrolling s. /// - public abstract class ScrollingPlayfield : Playfield, IKeyBindingHandler + public abstract class ScrollingPlayfield : Playfield { - /// - /// The default span of time visible by the length of the scrolling axes. - /// This is clamped between and . - /// - private const double time_span_default = 1500; + protected readonly IBindable Direction = new Bindable(); - /// - /// The minimum span of time that may be visible by the length of the scrolling axes. - /// - private const double time_span_min = 50; - - /// - /// The maximum span of time that may be visible by the length of the scrolling axes. - /// - private const double time_span_max = 10000; - - /// - /// The step increase/decrease of the span of time visible by the length of the scrolling axes. - /// - private const double time_span_step = 200; - - /// - /// The span of time that is visible by the length of the scrolling axes. - /// For example, only hit objects with start time less than or equal to 1000 will be visible with = 1000. - /// - public readonly BindableDouble VisibleTimeRange = new BindableDouble(time_span_default) - { - Default = time_span_default, - MinValue = time_span_min, - MaxValue = time_span_max - }; - - /// - /// Whether the player can change . - /// - protected virtual bool UserScrollSpeedAdjustment => true; - - /// - /// The container that contains the s. - /// - public new ScrollingHitObjectContainer HitObjects => (ScrollingHitObjectContainer)HitObjectContainer; - - /// - /// The direction in which s in this should scroll. - /// - protected readonly Bindable Direction = new Bindable(); - - protected virtual SpeedChangeVisualisationMethod VisualisationMethod => SpeedChangeVisualisationMethod.Sequential; + [Resolved] + private IScrollingInfo scrollingInfo { get; set; } [BackgroundDependencyLoader] private void load() { - HitObjects.TimeRange.BindTo(VisibleTimeRange); + Direction.BindTo(scrollingInfo.Direction); } - public bool OnPressed(GlobalAction action) - { - if (!UserScrollSpeedAdjustment) - return false; - - switch (action) - { - case GlobalAction.IncreaseScrollSpeed: - this.TransformBindableTo(VisibleTimeRange, VisibleTimeRange - time_span_step, 200, Easing.OutQuint); - return true; - case GlobalAction.DecreaseScrollSpeed: - this.TransformBindableTo(VisibleTimeRange, VisibleTimeRange + time_span_step, 200, Easing.OutQuint); - return true; - } - - return false; - } - - public bool OnReleased(GlobalAction action) => false; - - protected sealed override HitObjectContainer CreateHitObjectContainer() - { - var container = new ScrollingHitObjectContainer(VisualisationMethod); - container.Direction.BindTo(Direction); - return container; - } + protected sealed override HitObjectContainer CreateHitObjectContainer() => new ScrollingHitObjectContainer(); } } diff --git a/osu.Game/Rulesets/UI/Scrolling/ScrollingRulesetContainer.cs b/osu.Game/Rulesets/UI/Scrolling/ScrollingRulesetContainer.cs index 41cdd6c06f..83b9e31a46 100644 --- a/osu.Game/Rulesets/UI/Scrolling/ScrollingRulesetContainer.cs +++ b/osu.Game/Rulesets/UI/Scrolling/ScrollingRulesetContainer.cs @@ -4,13 +4,18 @@ using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; -using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Framework.Configuration; +using osu.Framework.Graphics; +using osu.Framework.Input.Bindings; using osu.Framework.Lists; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Configuration; +using osu.Game.Input.Bindings; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Timing; +using osu.Game.Rulesets.UI.Scrolling.Algorithms; namespace osu.Game.Rulesets.UI.Scrolling { @@ -18,20 +23,82 @@ namespace osu.Game.Rulesets.UI.Scrolling /// A type of that supports a . /// s inside this will scroll within the playfield. /// - public abstract class ScrollingRulesetContainer : RulesetContainer + public abstract class ScrollingRulesetContainer : RulesetContainer, IKeyBindingHandler where TObject : HitObject where TPlayfield : ScrollingPlayfield { + /// + /// The default span of time visible by the length of the scrolling axes. + /// This is clamped between and . + /// + private const double time_span_default = 1500; + + /// + /// The minimum span of time that may be visible by the length of the scrolling axes. + /// + private const double time_span_min = 50; + + /// + /// The maximum span of time that may be visible by the length of the scrolling axes. + /// + private const double time_span_max = 10000; + + /// + /// The step increase/decrease of the span of time visible by the length of the scrolling axes. + /// + private const double time_span_step = 200; + + protected readonly Bindable Direction = new Bindable(); + + /// + /// The span of time that is visible by the length of the scrolling axes. + /// For example, only hit objects with start time less than or equal to 1000 will be visible with = 1000. + /// + protected readonly BindableDouble TimeRange = new BindableDouble(time_span_default) + { + Default = time_span_default, + MinValue = time_span_min, + MaxValue = time_span_max + }; + + protected virtual ScrollVisualisationMethod VisualisationMethod => ScrollVisualisationMethod.Sequential; + + /// + /// Whether the player can change . + /// + protected virtual bool UserScrollSpeedAdjustment => true; + /// /// Provides the default s that adjust the scrolling rate of s /// inside this . /// /// - protected readonly SortedList DefaultControlPoints = new SortedList(Comparer.Default); + private readonly SortedList controlPoints = new SortedList(Comparer.Default); + + protected IScrollingInfo ScrollingInfo => scrollingInfo; + + [Cached(Type = typeof(IScrollingInfo))] + private readonly LocalScrollingInfo scrollingInfo; protected ScrollingRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap) : base(ruleset, beatmap) { + scrollingInfo = new LocalScrollingInfo(); + scrollingInfo.Direction.BindTo(Direction); + scrollingInfo.TimeRange.BindTo(TimeRange); + + switch (VisualisationMethod) + { + case ScrollVisualisationMethod.Sequential: + scrollingInfo.Algorithm = new SequentialScrollAlgorithm(controlPoints); + break; + case ScrollVisualisationMethod.Overlapping: + scrollingInfo.Algorithm = new OverlappingScrollAlgorithm(controlPoints); + break; + case ScrollVisualisationMethod.Constant: + scrollingInfo.Algorithm = new ConstantScrollAlgorithm(); + break; + } } [BackgroundDependencyLoader] @@ -75,19 +142,40 @@ namespace osu.Game.Rulesets.UI.Scrolling // Collapse sections with the same start time .GroupBy(s => s.StartTime).Select(g => g.Last()).OrderBy(s => s.StartTime); - DefaultControlPoints.AddRange(timingChanges); + controlPoints.AddRange(timingChanges); // If we have no control points, add a default one - if (DefaultControlPoints.Count == 0) - DefaultControlPoints.Add(new MultiplierControlPoint { Velocity = Beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier }); - - DefaultControlPoints.ForEach(c => applySpeedAdjustment(c, Playfield)); + if (controlPoints.Count == 0) + controlPoints.Add(new MultiplierControlPoint { Velocity = Beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier }); } - private void applySpeedAdjustment(MultiplierControlPoint controlPoint, ScrollingPlayfield playfield) + public bool OnPressed(GlobalAction action) { - playfield.HitObjects.AddControlPoint(controlPoint); - playfield.NestedPlayfields?.OfType().ForEach(p => applySpeedAdjustment(controlPoint, p)); + if (!UserScrollSpeedAdjustment) + return false; + + switch (action) + { + case GlobalAction.IncreaseScrollSpeed: + this.TransformBindableTo(TimeRange, TimeRange - time_span_step, 200, Easing.OutQuint); + return true; + case GlobalAction.DecreaseScrollSpeed: + this.TransformBindableTo(TimeRange, TimeRange + time_span_step, 200, Easing.OutQuint); + return true; + } + + return false; + } + + public bool OnReleased(GlobalAction action) => false; + + private class LocalScrollingInfo : IScrollingInfo + { + public IBindable Direction { get; } = new Bindable(); + + public IBindable TimeRange { get; } = new BindableDouble(); + + public IScrollAlgorithm Algorithm { get; set; } } } } diff --git a/osu.Game/Rulesets/UI/Scrolling/Visualisers/ConstantSpeedChangeVisualiser.cs b/osu.Game/Rulesets/UI/Scrolling/Visualisers/ConstantSpeedChangeVisualiser.cs deleted file mode 100644 index 9e910d6b11..0000000000 --- a/osu.Game/Rulesets/UI/Scrolling/Visualisers/ConstantSpeedChangeVisualiser.cs +++ /dev/null @@ -1,67 +0,0 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.Collections.Generic; -using osu.Game.Rulesets.Objects.Drawables; -using osu.Game.Rulesets.Objects.Types; -using OpenTK; - -namespace osu.Game.Rulesets.UI.Scrolling.Visualisers -{ - public class ConstantSpeedChangeVisualiser : ISpeedChangeVisualiser - { - public void ComputeInitialStates(IEnumerable hitObjects, ScrollingDirection direction, double timeRange, Vector2 length) - { - foreach (var obj in hitObjects) - { - obj.LifetimeStart = obj.HitObject.StartTime - timeRange; - - if (obj.HitObject is IHasEndTime endTime) - { - var hitObjectLength = (endTime.EndTime - obj.HitObject.StartTime) / timeRange; - - switch (direction) - { - case ScrollingDirection.Up: - case ScrollingDirection.Down: - obj.Height = (float)(hitObjectLength * length.Y); - break; - case ScrollingDirection.Left: - case ScrollingDirection.Right: - obj.Width = (float)(hitObjectLength * length.X); - break; - } - } - - ComputeInitialStates(obj.NestedHitObjects, direction, timeRange, length); - - // Nested hitobjects don't need to scroll, but they do need accurate positions - UpdatePositions(obj.NestedHitObjects, direction, obj.HitObject.StartTime, timeRange, length); - } - } - - public void UpdatePositions(IEnumerable hitObjects, ScrollingDirection direction, double currentTime, double timeRange, Vector2 length) - { - foreach (var obj in hitObjects) - { - var position = (obj.HitObject.StartTime - currentTime) / timeRange; - - switch (direction) - { - case ScrollingDirection.Up: - obj.Y = (float)(position * length.Y); - break; - case ScrollingDirection.Down: - obj.Y = (float)(-position * length.Y); - break; - case ScrollingDirection.Left: - obj.X = (float)(position * length.X); - break; - case ScrollingDirection.Right: - obj.X = (float)(-position * length.X); - break; - } - } - } - } -} diff --git a/osu.Game/Rulesets/UI/Scrolling/Visualisers/ISpeedChangeVisualiser.cs b/osu.Game/Rulesets/UI/Scrolling/Visualisers/ISpeedChangeVisualiser.cs deleted file mode 100644 index 097e28b2dc..0000000000 --- a/osu.Game/Rulesets/UI/Scrolling/Visualisers/ISpeedChangeVisualiser.cs +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.Collections.Generic; -using osu.Game.Rulesets.Objects.Drawables; -using OpenTK; - -namespace osu.Game.Rulesets.UI.Scrolling.Visualisers -{ - public interface ISpeedChangeVisualiser - { - /// - /// Computes the states of s that remain constant while scrolling, such as lifetime and spatial length. - /// This is invoked once whenever or changes. - /// - /// The s whose states should be computed. - /// The scrolling direction. - /// The duration required to scroll through one length of the screen before any speed adjustments. - /// The length of the screen that is scrolled through. - void ComputeInitialStates(IEnumerable hitObjects, ScrollingDirection direction, double timeRange, Vector2 length); - - /// - /// Updates the positions of s, depending on the current time. This is invoked once per frame. - /// - /// The s whose positions should be computed. - /// The scrolling direction. - /// The current time. - /// The duration required to scroll through one length of the screen before any speed adjustments. - /// The length of the screen that is scrolled through. - void UpdatePositions(IEnumerable hitObjects, ScrollingDirection direction, double currentTime, double timeRange, Vector2 length); - } -} diff --git a/osu.Game/Rulesets/UI/Scrolling/Visualisers/OverlappingSpeedChangeVisualiser.cs b/osu.Game/Rulesets/UI/Scrolling/Visualisers/OverlappingSpeedChangeVisualiser.cs deleted file mode 100644 index d2b79e2fa7..0000000000 --- a/osu.Game/Rulesets/UI/Scrolling/Visualisers/OverlappingSpeedChangeVisualiser.cs +++ /dev/null @@ -1,120 +0,0 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.Collections.Generic; -using osu.Framework.Lists; -using osu.Game.Rulesets.Objects.Drawables; -using osu.Game.Rulesets.Objects.Types; -using osu.Game.Rulesets.Timing; -using OpenTK; - -namespace osu.Game.Rulesets.UI.Scrolling.Visualisers -{ - public class OverlappingSpeedChangeVisualiser : ISpeedChangeVisualiser - { - private readonly SortedList controlPoints; - - public OverlappingSpeedChangeVisualiser(SortedList controlPoints) - { - this.controlPoints = controlPoints; - } - - public void ComputeInitialStates(IEnumerable hitObjects, ScrollingDirection direction, double timeRange, Vector2 length) - { - foreach (var obj in hitObjects) - { - // The total amount of time that the hitobject will remain visible within the timeRange, which decreases as the speed multiplier increases - double visibleDuration = timeRange / controlPointAt(obj.HitObject.StartTime).Multiplier; - - obj.LifetimeStart = obj.HitObject.StartTime - visibleDuration; - - if (obj.HitObject is IHasEndTime endTime) - { - // At the hitobject's end time, the hitobject will be positioned such that its end rests at the origin. - // This results in a negative-position value, and the absolute of it indicates the length of the hitobject. - var hitObjectLength = -hitObjectPositionAt(obj, endTime.EndTime, timeRange); - - switch (direction) - { - case ScrollingDirection.Up: - case ScrollingDirection.Down: - obj.Height = (float)(hitObjectLength * length.Y); - break; - case ScrollingDirection.Left: - case ScrollingDirection.Right: - obj.Width = (float)(hitObjectLength * length.X); - break; - } - } - - ComputeInitialStates(obj.NestedHitObjects, direction, timeRange, length); - - // Nested hitobjects don't need to scroll, but they do need accurate positions - UpdatePositions(obj.NestedHitObjects, direction, obj.HitObject.StartTime, timeRange, length); - } - } - - public void UpdatePositions(IEnumerable hitObjects, ScrollingDirection direction, double currentTime, double timeRange, Vector2 length) - { - foreach (var obj in hitObjects) - { - var position = hitObjectPositionAt(obj, currentTime, timeRange); - - switch (direction) - { - case ScrollingDirection.Up: - obj.Y = (float)(position * length.Y); - break; - case ScrollingDirection.Down: - obj.Y = (float)(-position * length.Y); - break; - case ScrollingDirection.Left: - obj.X = (float)(position * length.X); - break; - case ScrollingDirection.Right: - obj.X = (float)(-position * length.X); - break; - } - } - } - - /// - /// Computes the position of a at a point in time. - /// - /// At t < startTime, position > 0.
- /// At t = startTime, position = 0.
- /// At t > startTime, position < 0. - ///
- ///
- /// The . - /// The time to find the position of at. - /// The amount of time visualised by the scrolling area. - /// The position of in the scrolling area at time = . - private double hitObjectPositionAt(DrawableHitObject obj, double time, double timeRange) - => (obj.HitObject.StartTime - time) / timeRange * controlPointAt(obj.HitObject.StartTime).Multiplier; - - private readonly MultiplierControlPoint searchPoint = new MultiplierControlPoint(); - - /// - /// Finds the which affects the speed of hitobjects at a specific time. - /// - /// The time which the should affect. - /// The . - private MultiplierControlPoint controlPointAt(double time) - { - if (controlPoints.Count == 0) - return new MultiplierControlPoint(double.NegativeInfinity); - - if (time < controlPoints[0].StartTime) - return controlPoints[0]; - - searchPoint.StartTime = time; - int index = controlPoints.BinarySearch(searchPoint); - - if (index < 0) - index = ~index - 1; - - return controlPoints[index]; - } - } -} diff --git a/osu.Game/Rulesets/UI/Scrolling/Visualisers/SequentialSpeedChangeVisualiser.cs b/osu.Game/Rulesets/UI/Scrolling/Visualisers/SequentialSpeedChangeVisualiser.cs deleted file mode 100644 index 25dea8dfbf..0000000000 --- a/osu.Game/Rulesets/UI/Scrolling/Visualisers/SequentialSpeedChangeVisualiser.cs +++ /dev/null @@ -1,124 +0,0 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.Collections.Generic; -using osu.Game.Rulesets.Objects.Drawables; -using osu.Game.Rulesets.Objects.Types; -using osu.Game.Rulesets.Timing; -using OpenTK; - -namespace osu.Game.Rulesets.UI.Scrolling.Visualisers -{ - public class SequentialSpeedChangeVisualiser : ISpeedChangeVisualiser - { - private readonly Dictionary hitObjectPositions = new Dictionary(); - - private readonly IReadOnlyList controlPoints; - - public SequentialSpeedChangeVisualiser(IReadOnlyList controlPoints) - { - this.controlPoints = controlPoints; - } - - public void ComputeInitialStates(IEnumerable hitObjects, ScrollingDirection direction, double timeRange, Vector2 length) - { - foreach (var obj in hitObjects) - { - // To reduce iterations when updating hitobject positions later on, their initial positions are cached - var startPosition = hitObjectPositions[obj] = positionAt(obj.HitObject.StartTime, timeRange); - - // Todo: This is approximate and will be incorrect in the case of extreme speed changes - obj.LifetimeStart = obj.HitObject.StartTime - timeRange - 1000; - - if (obj.HitObject is IHasEndTime endTime) - { - var hitObjectLength = positionAt(endTime.EndTime, timeRange) - startPosition; - - switch (direction) - { - case ScrollingDirection.Up: - case ScrollingDirection.Down: - obj.Height = (float)(hitObjectLength * length.Y); - break; - case ScrollingDirection.Left: - case ScrollingDirection.Right: - obj.Width = (float)(hitObjectLength * length.X); - break; - } - } - - ComputeInitialStates(obj.NestedHitObjects, direction, timeRange, length); - - // Nested hitobjects don't need to scroll, but they do need accurate positions - UpdatePositions(obj.NestedHitObjects, direction, obj.HitObject.StartTime, timeRange, length); - } - } - - public void UpdatePositions(IEnumerable hitObjects, ScrollingDirection direction, double currentTime, double timeRange, Vector2 length) - { - var timelinePosition = positionAt(currentTime, timeRange); - - foreach (var obj in hitObjects) - { - var finalPosition = hitObjectPositions[obj] - timelinePosition; - - switch (direction) - { - case ScrollingDirection.Up: - obj.Y = (float)(finalPosition * length.Y); - break; - case ScrollingDirection.Down: - obj.Y = (float)(-finalPosition * length.Y); - break; - case ScrollingDirection.Left: - obj.X = (float)(finalPosition * length.X); - break; - case ScrollingDirection.Right: - obj.X = (float)(-finalPosition * length.X); - break; - } - } - } - - /// - /// Finds the position which corresponds to a point in time. - /// This is a non-linear operation that depends on all the control points up to and including the one active at the time value. - /// - /// The time to find the position at. - /// The amount of time visualised by the scrolling area. - /// A positive value indicating the position at . - private double positionAt(double time, double timeRange) - { - if (controlPoints.Count == 0) - return time / timeRange; - - double length = 0; - - // We need to consider all timing points until the specified time and not just the currently-active one, - // since each timing point individually affects the positions of _all_ hitobjects after its start time - for (int i = 0; i < controlPoints.Count; i++) - { - var current = controlPoints[i]; - var next = i < controlPoints.Count - 1 ? controlPoints[i + 1] : null; - - // We don't need to consider any control points beyond the current time, since it will not yet - // affect any hitobjects - if (i > 0 && current.StartTime > time) - continue; - - // Duration of the current control point - var currentDuration = (next?.StartTime ?? double.PositiveInfinity) - current.StartTime; - - // We want to consider the minimal amount of time that this control point has affected, - // which may be either its duration, or the amount of time that has passed within it - var durationInCurrent = Math.Min(currentDuration, time - current.StartTime); - - // Figure out how much of the time range the duration represents, and adjust it by the speed multiplier - length += durationInCurrent / timeRange * current.Multiplier; - } - - return length; - } - } -} diff --git a/osu.Game/Rulesets/Scoring/Legacy/DatabasedLegacyScoreParser.cs b/osu.Game/Scoring/Legacy/DatabasedLegacyScoreParser.cs similarity index 94% rename from osu.Game/Rulesets/Scoring/Legacy/DatabasedLegacyScoreParser.cs rename to osu.Game/Scoring/Legacy/DatabasedLegacyScoreParser.cs index bfb2b7c13b..5866c04192 100644 --- a/osu.Game/Rulesets/Scoring/Legacy/DatabasedLegacyScoreParser.cs +++ b/osu.Game/Scoring/Legacy/DatabasedLegacyScoreParser.cs @@ -2,8 +2,9 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using osu.Game.Beatmaps; +using osu.Game.Rulesets; -namespace osu.Game.Rulesets.Scoring.Legacy +namespace osu.Game.Scoring.Legacy { /// /// A which retrieves the applicable and diff --git a/osu.Game/Scoring/Legacy/LegacyScoreParser.cs b/osu.Game/Scoring/Legacy/LegacyScoreParser.cs new file mode 100644 index 0000000000..3184f776a7 --- /dev/null +++ b/osu.Game/Scoring/Legacy/LegacyScoreParser.cs @@ -0,0 +1,277 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.IO; +using System.Linq; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.Legacy; +using osu.Game.IO.Legacy; +using osu.Game.Replays; +using osu.Game.Replays.Legacy; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Replays; +using osu.Game.Rulesets.Scoring; +using osu.Game.Users; +using SharpCompress.Compressors.LZMA; + +namespace osu.Game.Scoring.Legacy +{ + public abstract class LegacyScoreParser + { + private IBeatmap currentBeatmap; + private Ruleset currentRuleset; + + public Score Parse(Stream stream) + { + var score = new Score + { + ScoreInfo = new ScoreInfo(), + Replay = new Replay() + }; + + using (SerializationReader sr = new SerializationReader(stream)) + { + currentRuleset = GetRuleset(sr.ReadByte()); + score.ScoreInfo = new ScoreInfo { Ruleset = currentRuleset.RulesetInfo }; + + var version = sr.ReadInt32(); + + var workingBeatmap = GetBeatmap(sr.ReadString()); + if (workingBeatmap is DummyWorkingBeatmap) + throw new BeatmapNotFoundException(); + + currentBeatmap = workingBeatmap.Beatmap; + score.ScoreInfo.Beatmap = currentBeatmap.BeatmapInfo; + + score.ScoreInfo.User = new User { Username = sr.ReadString() }; + + // MD5Hash + sr.ReadString(); + + var count300 = (int)sr.ReadUInt16(); + var count100 = (int)sr.ReadUInt16(); + var count50 = (int)sr.ReadUInt16(); + var countGeki = (int)sr.ReadUInt16(); + var countKatu = (int)sr.ReadUInt16(); + var countMiss = (int)sr.ReadUInt16(); + + score.ScoreInfo.Statistics[HitResult.Great] = count300; + score.ScoreInfo.Statistics[HitResult.Good] = count100; + score.ScoreInfo.Statistics[HitResult.Meh] = count50; + score.ScoreInfo.Statistics[HitResult.Perfect] = countGeki; + score.ScoreInfo.Statistics[HitResult.Ok] = countKatu; + score.ScoreInfo.Statistics[HitResult.Miss] = countMiss; + + score.ScoreInfo.TotalScore = sr.ReadInt32(); + score.ScoreInfo.MaxCombo = sr.ReadUInt16(); + + /* score.Perfect = */ + sr.ReadBoolean(); + + score.ScoreInfo.Mods = currentRuleset.ConvertLegacyMods((LegacyMods)sr.ReadInt32()).ToArray(); + + /* score.HpGraphString = */ + sr.ReadString(); + + score.ScoreInfo.Date = sr.ReadDateTime(); + + var compressedReplay = sr.ReadByteArray(); + + if (version >= 20140721) + score.ScoreInfo.OnlineScoreID = sr.ReadInt64(); + else if (version >= 20121008) + score.ScoreInfo.OnlineScoreID = sr.ReadInt32(); + + if (compressedReplay?.Length > 0) + { + using (var replayInStream = new MemoryStream(compressedReplay)) + { + byte[] properties = new byte[5]; + if (replayInStream.Read(properties, 0, 5) != 5) + throw new IOException("input .lzma is too short"); + long outSize = 0; + for (int i = 0; i < 8; i++) + { + int v = replayInStream.ReadByte(); + if (v < 0) + throw new IOException("Can't Read 1"); + outSize |= (long)(byte)v << (8 * i); + } + + long compressedSize = replayInStream.Length - replayInStream.Position; + + using (var lzma = new LzmaStream(properties, replayInStream, compressedSize, outSize)) + using (var reader = new StreamReader(lzma)) + readLegacyReplay(score.Replay, reader); + } + } + } + + calculateAccuracy(score.ScoreInfo); + + return score; + } + + private void calculateAccuracy(ScoreInfo score) + { + int countMiss = score.Statistics[HitResult.Miss]; + int count50 = score.Statistics[HitResult.Meh]; + int count100 = score.Statistics[HitResult.Good]; + int count300 = score.Statistics[HitResult.Great]; + int countGeki = score.Statistics[HitResult.Perfect]; + int countKatu = score.Statistics[HitResult.Ok]; + + switch (score.Ruleset.ID) + { + case 0: + { + int totalHits = count50 + count100 + count300 + countMiss; + score.Accuracy = totalHits > 0 ? (double)(count50 * 50 + count100 * 100 + count300 * 300) / (totalHits * 300) : 1; + + float ratio300 = (float)count300 / totalHits; + float ratio50 = (float)count50 / totalHits; + + if (ratio300 == 1) + score.Rank = score.Mods.Any(m => m is ModHidden || m is ModFlashlight) ? ScoreRank.XH : ScoreRank.X; + else if (ratio300 > 0.9 && ratio50 <= 0.01 && countMiss == 0) + score.Rank = score.Mods.Any(m => m is ModHidden || m is ModFlashlight) ? ScoreRank.SH : ScoreRank.S; + else if (ratio300 > 0.8 && countMiss == 0 || ratio300 > 0.9) + score.Rank = ScoreRank.A; + else if (ratio300 > 0.7 && countMiss == 0 || ratio300 > 0.8) + score.Rank = ScoreRank.B; + else if (ratio300 > 0.6) + score.Rank = ScoreRank.C; + else + score.Rank = ScoreRank.D; + break; + } + case 1: + { + int totalHits = count50 + count100 + count300 + countMiss; + score.Accuracy = totalHits > 0 ? (double)(count100 * 150 + count300 * 300) / (totalHits * 300) : 1; + + float ratio300 = (float)count300 / totalHits; + float ratio50 = (float)count50 / totalHits; + + if (ratio300 == 1) + score.Rank = score.Mods.Any(m => m is ModHidden || m is ModFlashlight) ? ScoreRank.XH : ScoreRank.X; + else if (ratio300 > 0.9 && ratio50 <= 0.01 && countMiss == 0) + score.Rank = score.Mods.Any(m => m is ModHidden || m is ModFlashlight) ? ScoreRank.SH : ScoreRank.S; + else if (ratio300 > 0.8 && countMiss == 0 || ratio300 > 0.9) + score.Rank = ScoreRank.A; + else if (ratio300 > 0.7 && countMiss == 0 || ratio300 > 0.8) + score.Rank = ScoreRank.B; + else if (ratio300 > 0.6) + score.Rank = ScoreRank.C; + else + score.Rank = ScoreRank.D; + break; + } + case 2: + { + int totalHits = count50 + count100 + count300 + countMiss + countKatu; + score.Accuracy = totalHits > 0 ? (double)(count50 + count100 + count300) / totalHits : 1; + + if (score.Accuracy == 1) + score.Rank = score.Mods.Any(m => m is ModHidden || m is ModFlashlight) ? ScoreRank.XH : ScoreRank.X; + else if (score.Accuracy > 0.98) + score.Rank = score.Mods.Any(m => m is ModHidden || m is ModFlashlight) ? ScoreRank.SH : ScoreRank.S; + else if (score.Accuracy > 0.94) + score.Rank = ScoreRank.A; + else if (score.Accuracy > 0.9) + score.Rank = ScoreRank.B; + else if (score.Accuracy > 0.85) + score.Rank = ScoreRank.C; + else + score.Rank = ScoreRank.D; + break; + } + case 3: + { + int totalHits = count50 + count100 + count300 + countMiss + countGeki + countKatu; + score.Accuracy = totalHits > 0 ? (double)(count50 * 50 + count100 * 100 + countKatu * 200 + (count300 + countGeki) * 300) / (totalHits * 300) : 1; + + if (score.Accuracy == 1) + score.Rank = score.Mods.Any(m => m is ModHidden || m is ModFlashlight) ? ScoreRank.XH : ScoreRank.X; + else if (score.Accuracy > 0.95) + score.Rank = score.Mods.Any(m => m is ModHidden || m is ModFlashlight) ? ScoreRank.SH : ScoreRank.S; + else if (score.Accuracy > 0.9) + score.Rank = ScoreRank.A; + else if (score.Accuracy > 0.8) + score.Rank = ScoreRank.B; + else if (score.Accuracy > 0.7) + score.Rank = ScoreRank.C; + else + score.Rank = ScoreRank.D; + break; + } + } + } + + private void readLegacyReplay(Replay replay, StreamReader reader) + { + float lastTime = 0; + + foreach (var l in reader.ReadToEnd().Split(',')) + { + var split = l.Split('|'); + + if (split.Length < 4) + continue; + + if (split[0] == "-12345") + { + // Todo: The seed is provided in split[3], which we'll need to use at some point + continue; + } + + var diff = float.Parse(split[0]); + lastTime += diff; + + // Todo: At some point we probably want to rewind and play back the negative-time frames + // but for now we'll achieve equal playback to stable by skipping negative frames + if (diff < 0) + continue; + + replay.Frames.Add(convertFrame(new LegacyReplayFrame(lastTime, float.Parse(split[1]), float.Parse(split[2]), (ReplayButtonState)int.Parse(split[3])))); + } + } + + private ReplayFrame convertFrame(LegacyReplayFrame legacyFrame) + { + var convertible = currentRuleset.CreateConvertibleReplayFrame(); + if (convertible == null) + throw new InvalidOperationException($"Legacy replay cannot be converted for the ruleset: {currentRuleset.Description}"); + convertible.ConvertFrom(legacyFrame, currentBeatmap); + + var frame = (ReplayFrame)convertible; + frame.Time = legacyFrame.Time; + + return frame; + } + + /// + /// Retrieves the for a specific id. + /// + /// The id. + /// The . + protected abstract Ruleset GetRuleset(int rulesetId); + + /// + /// Retrieves the corresponding to an MD5 hash. + /// + /// The MD5 hash. + /// The . + protected abstract WorkingBeatmap GetBeatmap(string md5Hash); + + public class BeatmapNotFoundException : Exception + { + public BeatmapNotFoundException() + : base("No corresponding beatmap for the score could be found.") + { + } + } + } +} diff --git a/osu.Game/Scoring/LegacyDatabasedScore.cs b/osu.Game/Scoring/LegacyDatabasedScore.cs new file mode 100644 index 0000000000..768c5da7af --- /dev/null +++ b/osu.Game/Scoring/LegacyDatabasedScore.cs @@ -0,0 +1,25 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Linq; +using osu.Framework.IO.Stores; +using osu.Game.Beatmaps; +using osu.Game.Rulesets; +using osu.Game.Scoring.Legacy; + +namespace osu.Game.Scoring +{ + public class LegacyDatabasedScore : Score + { + public LegacyDatabasedScore(ScoreInfo score, RulesetStore rulesets, BeatmapManager beatmaps, IResourceStore store) + { + ScoreInfo = score; + + var replayFilename = score.Files.First(f => f.Filename.EndsWith(".osr", StringComparison.InvariantCultureIgnoreCase)).FileInfo.StoragePath; + + using (var stream = store.GetStream(replayFilename)) + Replay = new DatabasedLegacyScoreParser(rulesets, beatmaps).Parse(stream).Replay; + } + } +} diff --git a/osu.Game/Scoring/Score.cs b/osu.Game/Scoring/Score.cs new file mode 100644 index 0000000000..ffbee920cb --- /dev/null +++ b/osu.Game/Scoring/Score.cs @@ -0,0 +1,13 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Replays; + +namespace osu.Game.Scoring +{ + public class Score + { + public ScoreInfo ScoreInfo = new ScoreInfo(); + public Replay Replay = new Replay(); + } +} diff --git a/osu.Game/Scoring/ScoreFileInfo.cs b/osu.Game/Scoring/ScoreFileInfo.cs new file mode 100644 index 0000000000..e5595397b8 --- /dev/null +++ b/osu.Game/Scoring/ScoreFileInfo.cs @@ -0,0 +1,21 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.ComponentModel.DataAnnotations; +using osu.Game.Database; +using osu.Game.IO; + +namespace osu.Game.Scoring +{ + public class ScoreFileInfo : INamedFileInfo, IHasPrimaryKey + { + public int ID { get; set; } + + public int FileInfoID { get; set; } + + public FileInfo FileInfo { get; set; } + + [Required] + public string Filename { get; set; } + } +} diff --git a/osu.Game/Scoring/ScoreInfo.cs b/osu.Game/Scoring/ScoreInfo.cs new file mode 100644 index 0000000000..fb894e621e --- /dev/null +++ b/osu.Game/Scoring/ScoreInfo.cs @@ -0,0 +1,138 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations.Schema; +using System.Linq; +using Newtonsoft.Json; +using osu.Game.Beatmaps; +using osu.Game.Database; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Mods; +using osu.Game.Users; +using osu.Game.Rulesets.Scoring; + +namespace osu.Game.Scoring +{ + public class ScoreInfo : IHasFiles, IHasPrimaryKey, ISoftDelete + { + public int ID { get; set; } + + public ScoreRank Rank { get; set; } + + public int TotalScore { get; set; } + + [Column(TypeName="DECIMAL(1,4)")] + public double Accuracy { get; set; } + + public double? PP { get; set; } + + public int MaxCombo { get; set; } + + public int Combo { get; set; } + + public int RulesetID { get; set; } + + public virtual RulesetInfo Ruleset { get; set; } + + private Mod[] mods; + + [NotMapped] + public Mod[] Mods + { + get + { + if (mods != null) + return mods; + + if (modsJson == null) + return Array.Empty(); + + return getModsFromRuleset(JsonConvert.DeserializeObject(modsJson)); + } + set + { + modsJson = null; + mods = value; + } + } + + private Mod[] getModsFromRuleset(DeserializedMod[] mods) => Ruleset.CreateInstance().GetAllMods().Where(mod => mods.Any(d => d.Acronym == mod.Acronym)).ToArray(); + + private string modsJson; + + [Column("Mods")] + public string ModsJson + { + get + { + if (modsJson != null) + return modsJson; + + if (mods == null) + return null; + + return modsJson = JsonConvert.SerializeObject(mods); + } + set + { + modsJson = value; + + // we potentially can't update this yet due to Ruleset being late-bound, so instead update on read as necessary. + mods = null; + } + } + + [JsonIgnore] + public User User; + + [Column("User")] + public string UserString + { + get => User?.Username; + set => User = new User { Username = value }; + } + + [JsonIgnore] + public int BeatmapInfoID { get; set; } + + public virtual BeatmapInfo Beatmap { get; set; } + + public long? OnlineScoreID { get; set; } + + public DateTimeOffset Date { get; set; } + + [JsonIgnore] + public Dictionary Statistics = new Dictionary(); + + [Column("Statistics")] + public string StatisticsJson + { + get => JsonConvert.SerializeObject(Statistics); + set + { + if (value == null) + { + Statistics.Clear(); + return; + } + + Statistics = JsonConvert.DeserializeObject>(value); + } + } + + [JsonIgnore] + public List Files { get; set; } + + public string Hash { get; set; } + + public bool DeletePending { get; set; } + + [Serializable] + protected class DeserializedMod : IMod + { + public string Acronym { get; set; } + } + } +} diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs new file mode 100644 index 0000000000..663f441f2f --- /dev/null +++ b/osu.Game/Scoring/ScoreManager.cs @@ -0,0 +1,64 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using Microsoft.EntityFrameworkCore; +using osu.Framework.Logging; +using osu.Framework.Platform; +using osu.Game.Beatmaps; +using osu.Game.Database; +using osu.Game.IO.Archives; +using osu.Game.Rulesets; +using osu.Game.Scoring.Legacy; + +namespace osu.Game.Scoring +{ + public class ScoreManager : ArchiveModelManager + { + public override string[] HandledExtensions => new[] { ".osr" }; + + protected override string[] HashableFileTypes => new[] { ".osr" }; + + protected override string ImportFromStablePath => "Replays"; + + private readonly RulesetStore rulesets; + private readonly BeatmapManager beatmaps; + + public ScoreManager(RulesetStore rulesets, BeatmapManager beatmaps, Storage storage, IDatabaseContextFactory contextFactory, IIpcHost importHost = null) + : base(storage, contextFactory, new ScoreStore(contextFactory, storage), importHost) + { + this.rulesets = rulesets; + this.beatmaps = beatmaps; + } + + protected override ScoreInfo CreateModel(ArchiveReader archive) + { + if (archive == null) + return null; + + using (var stream = archive.GetStream(archive.Filenames.First(f => f.EndsWith(".osr")))) + { + try + { + return new DatabasedLegacyScoreParser(rulesets, beatmaps).Parse(stream).ScoreInfo; + } + catch (LegacyScoreParser.BeatmapNotFoundException e) + { + Logger.Log(e.Message, LoggingTarget.Information, LogLevel.Error); + return null; + } + } + } + + public Score GetScore(ScoreInfo score) => new LegacyDatabasedScore(score, rulesets, beatmaps, Files.Store); + + public List GetAllUsableScores() => ModelStore.ConsumableItems.Where(s => !s.DeletePending).ToList(); + + public IEnumerable QueryScores(Expression> query) => ModelStore.ConsumableItems.AsNoTracking().Where(query); + + public ScoreInfo Query(Expression> query) => ModelStore.ConsumableItems.AsNoTracking().FirstOrDefault(query); + } +} diff --git a/osu.Game/Rulesets/Scoring/ScoreRank.cs b/osu.Game/Scoring/ScoreRank.cs similarity index 94% rename from osu.Game/Rulesets/Scoring/ScoreRank.cs rename to osu.Game/Scoring/ScoreRank.cs index d4113bb08a..efc513c39c 100644 --- a/osu.Game/Rulesets/Scoring/ScoreRank.cs +++ b/osu.Game/Scoring/ScoreRank.cs @@ -3,7 +3,7 @@ using System.ComponentModel; -namespace osu.Game.Rulesets.Scoring +namespace osu.Game.Scoring { public enum ScoreRank { diff --git a/osu.Game/Scoring/ScoreStore.cs b/osu.Game/Scoring/ScoreStore.cs new file mode 100644 index 0000000000..8b99bc0e8d --- /dev/null +++ b/osu.Game/Scoring/ScoreStore.cs @@ -0,0 +1,24 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Linq; +using Microsoft.EntityFrameworkCore; +using osu.Framework.Platform; +using osu.Game.Database; + +namespace osu.Game.Scoring +{ + public class ScoreStore : MutableDatabaseBackedStore + { + public ScoreStore(IDatabaseContextFactory factory, Storage storage) + : base(factory, storage) + { + } + + protected override IQueryable AddIncludesForConsumption(IQueryable query) + => base.AddIncludesForConsumption(query) + .Include(s => s.Files).ThenInclude(f => f.FileInfo) + .Include(s => s.Beatmap) + .Include(s => s.Ruleset); + } +} diff --git a/osu.Game/Screens/BackgroundScreen.cs b/osu.Game/Screens/BackgroundScreen.cs index 7787b4ddc2..ed477c9d0b 100644 --- a/osu.Game/Screens/BackgroundScreen.cs +++ b/osu.Game/Screens/BackgroundScreen.cs @@ -6,7 +6,7 @@ using System.Threading; using osu.Framework.Screens; using osu.Framework.Graphics; using osu.Framework.Input.Events; -using OpenTK; +using osuTK; namespace osu.Game.Screens { diff --git a/osu.Game/Screens/BlurrableBackgroundScreen.cs b/osu.Game/Screens/BlurrableBackgroundScreen.cs index 92d32badc4..4b6abbf063 100644 --- a/osu.Game/Screens/BlurrableBackgroundScreen.cs +++ b/osu.Game/Screens/BlurrableBackgroundScreen.cs @@ -4,7 +4,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Transforms; using osu.Game.Graphics.Backgrounds; -using OpenTK; +using osuTK; namespace osu.Game.Screens { diff --git a/osu.Game/Screens/Edit/Screens/Compose/BindableBeatDivisor.cs b/osu.Game/Screens/Edit/BindableBeatDivisor.cs similarity index 96% rename from osu.Game/Screens/Edit/Screens/Compose/BindableBeatDivisor.cs rename to osu.Game/Screens/Edit/BindableBeatDivisor.cs index b7dce8c96e..3124482c73 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/BindableBeatDivisor.cs +++ b/osu.Game/Screens/Edit/BindableBeatDivisor.cs @@ -5,7 +5,7 @@ using System; using System.Linq; using osu.Framework.Configuration; -namespace osu.Game.Screens.Edit.Screens.Compose +namespace osu.Game.Screens.Edit { public class BindableBeatDivisor : BindableNumber { diff --git a/osu.Game/Screens/Edit/Components/CircularButton.cs b/osu.Game/Screens/Edit/Components/CircularButton.cs index a8ad242772..98c3a7888d 100644 --- a/osu.Game/Screens/Edit/Components/CircularButton.cs +++ b/osu.Game/Screens/Edit/Components/CircularButton.cs @@ -2,7 +2,7 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using osu.Game.Graphics.UserInterface; -using OpenTK; +using osuTK; namespace osu.Game.Screens.Edit.Components { diff --git a/osu.Game/Screens/Edit/Menus/EditorMenuBar.cs b/osu.Game/Screens/Edit/Components/Menus/EditorMenuBar.cs similarity index 98% rename from osu.Game/Screens/Edit/Menus/EditorMenuBar.cs rename to osu.Game/Screens/Edit/Components/Menus/EditorMenuBar.cs index af0a7b6694..a647a8d75f 100644 --- a/osu.Game/Screens/Edit/Menus/EditorMenuBar.cs +++ b/osu.Game/Screens/Edit/Components/Menus/EditorMenuBar.cs @@ -2,19 +2,18 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE 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.Framework.Input.Events; using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; -using OpenTK; -using OpenTK.Graphics; -using osu.Framework.Configuration; -using osu.Framework.Input.Events; -using osu.Game.Screens.Edit.Screens; +using osuTK; +using osuTK.Graphics; -namespace osu.Game.Screens.Edit.Menus +namespace osu.Game.Screens.Edit.Components.Menus { public class EditorMenuBar : OsuMenu { diff --git a/osu.Game/Screens/Edit/Menus/EditorMenuItem.cs b/osu.Game/Screens/Edit/Components/Menus/EditorMenuItem.cs similarity index 92% rename from osu.Game/Screens/Edit/Menus/EditorMenuItem.cs rename to osu.Game/Screens/Edit/Components/Menus/EditorMenuItem.cs index 0ef1ad8c6b..1c9253cce7 100644 --- a/osu.Game/Screens/Edit/Menus/EditorMenuItem.cs +++ b/osu.Game/Screens/Edit/Components/Menus/EditorMenuItem.cs @@ -4,7 +4,7 @@ using System; using osu.Game.Graphics.UserInterface; -namespace osu.Game.Screens.Edit.Menus +namespace osu.Game.Screens.Edit.Components.Menus { public class EditorMenuItem : OsuMenuItem { diff --git a/osu.Game/Screens/Edit/Menus/EditorMenuItemSpacer.cs b/osu.Game/Screens/Edit/Components/Menus/EditorMenuItemSpacer.cs similarity index 86% rename from osu.Game/Screens/Edit/Menus/EditorMenuItemSpacer.cs rename to osu.Game/Screens/Edit/Components/Menus/EditorMenuItemSpacer.cs index 91b40a2cfb..17ee88241e 100644 --- a/osu.Game/Screens/Edit/Menus/EditorMenuItemSpacer.cs +++ b/osu.Game/Screens/Edit/Components/Menus/EditorMenuItemSpacer.cs @@ -1,7 +1,7 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -namespace osu.Game.Screens.Edit.Menus +namespace osu.Game.Screens.Edit.Components.Menus { public class EditorMenuItemSpacer : EditorMenuItem { diff --git a/osu.Game/Screens/Edit/Menus/ScreenSelectionTabControl.cs b/osu.Game/Screens/Edit/Components/Menus/ScreenSelectionTabControl.cs similarity index 95% rename from osu.Game/Screens/Edit/Menus/ScreenSelectionTabControl.cs rename to osu.Game/Screens/Edit/Components/Menus/ScreenSelectionTabControl.cs index f58e5b39eb..56f6056bf9 100644 --- a/osu.Game/Screens/Edit/Menus/ScreenSelectionTabControl.cs +++ b/osu.Game/Screens/Edit/Components/Menus/ScreenSelectionTabControl.cs @@ -1,7 +1,6 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using OpenTK.Graphics; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; @@ -9,10 +8,10 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.UserInterface; using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; -using osu.Game.Screens.Edit.Screens; -using OpenTK; +using osuTK; +using osuTK.Graphics; -namespace osu.Game.Screens.Edit.Menus +namespace osu.Game.Screens.Edit.Components.Menus { public class ScreenSelectionTabControl : OsuTabControl { diff --git a/osu.Game/Screens/Edit/Components/PlaybackControl.cs b/osu.Game/Screens/Edit/Components/PlaybackControl.cs index 3c8288f04a..d2e51d1f57 100644 --- a/osu.Game/Screens/Edit/Components/PlaybackControl.cs +++ b/osu.Game/Screens/Edit/Components/PlaybackControl.cs @@ -1,8 +1,8 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using OpenTK; -using OpenTK.Graphics; +using osuTK; +using osuTK.Graphics; using osu.Framework.Allocation; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; @@ -13,6 +13,7 @@ using osu.Framework.Timing; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; +using osuTK.Input; namespace osu.Game.Screens.Edit.Components { @@ -63,6 +64,18 @@ namespace osu.Game.Screens.Edit.Components tabs.Current.ValueChanged += newValue => Beatmap.Value.Track.Tempo.Value = newValue; } + protected override bool OnKeyDown(KeyDownEvent e) + { + switch (e.Key) + { + case Key.Space: + togglePause(); + return true; + } + + return base.OnKeyDown(e); + } + private void togglePause() { if (adjustableClock.IsRunning) diff --git a/osu.Game/Screens/Edit/Screens/Compose/RadioButtons/DrawableRadioButton.cs b/osu.Game/Screens/Edit/Components/RadioButtons/DrawableRadioButton.cs similarity index 97% rename from osu.Game/Screens/Edit/Screens/Compose/RadioButtons/DrawableRadioButton.cs rename to osu.Game/Screens/Edit/Components/RadioButtons/DrawableRadioButton.cs index 2c7e2043fc..eb113efbab 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/RadioButtons/DrawableRadioButton.cs +++ b/osu.Game/Screens/Edit/Components/RadioButtons/DrawableRadioButton.cs @@ -2,8 +2,6 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System; -using OpenTK; -using OpenTK.Graphics; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; @@ -14,8 +12,10 @@ using osu.Framework.Input.Events; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; +using osuTK; +using osuTK.Graphics; -namespace osu.Game.Screens.Edit.Screens.Compose.RadioButtons +namespace osu.Game.Screens.Edit.Components.RadioButtons { public class DrawableRadioButton : TriangleButton { diff --git a/osu.Game/Screens/Edit/Screens/Compose/RadioButtons/RadioButton.cs b/osu.Game/Screens/Edit/Components/RadioButtons/RadioButton.cs similarity index 95% rename from osu.Game/Screens/Edit/Screens/Compose/RadioButtons/RadioButton.cs rename to osu.Game/Screens/Edit/Components/RadioButtons/RadioButton.cs index 09fe34bedc..c671fa71c2 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/RadioButtons/RadioButton.cs +++ b/osu.Game/Screens/Edit/Components/RadioButtons/RadioButton.cs @@ -4,7 +4,7 @@ using System; using osu.Framework.Configuration; -namespace osu.Game.Screens.Edit.Screens.Compose.RadioButtons +namespace osu.Game.Screens.Edit.Components.RadioButtons { public class RadioButton { diff --git a/osu.Game/Screens/Edit/Screens/Compose/RadioButtons/RadioButtonCollection.cs b/osu.Game/Screens/Edit/Components/RadioButtons/RadioButtonCollection.cs similarity index 95% rename from osu.Game/Screens/Edit/Screens/Compose/RadioButtons/RadioButtonCollection.cs rename to osu.Game/Screens/Edit/Components/RadioButtons/RadioButtonCollection.cs index 7ba16b3d3e..fdb5770ecf 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/RadioButtons/RadioButtonCollection.cs +++ b/osu.Game/Screens/Edit/Components/RadioButtons/RadioButtonCollection.cs @@ -2,12 +2,12 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System.Collections.Generic; -using OpenTK; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osuTK; -namespace osu.Game.Screens.Edit.Screens.Compose.RadioButtons +namespace osu.Game.Screens.Edit.Components.RadioButtons { public class RadioButtonCollection : CompositeDrawable { diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/MarkerPart.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/MarkerPart.cs index 4b57e1e92d..3f3826d55c 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/MarkerPart.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/MarkerPart.cs @@ -1,12 +1,13 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using OpenTK; +using osuTK; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; +using osu.Framework.Threading; using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Graphics; @@ -43,17 +44,23 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts return true; } + private ScheduledDelegate scheduledSeek; + /// /// Seeks the to the time closest to a position on the screen relative to the . /// /// The position in screen coordinates. private void seekToPosition(Vector2 screenPosition) { - if (Beatmap.Value == null) - return; + scheduledSeek?.Cancel(); + scheduledSeek = Schedule(() => + { + if (Beatmap.Value == null) + return; - float markerPos = MathHelper.Clamp(ToLocalSpace(screenPosition).X, 0, DrawWidth); - adjustableClock.Seek(markerPos / DrawWidth * Beatmap.Value.Track.Length); + float markerPos = MathHelper.Clamp(ToLocalSpace(screenPosition).X, 0, DrawWidth); + adjustableClock.Seek(markerPos / DrawWidth * Beatmap.Value.Track.Length); + }); } protected override void Update() diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/TimelinePart.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/TimelinePart.cs index 5628630d0e..104fb76f1c 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/TimelinePart.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/TimelinePart.cs @@ -3,7 +3,7 @@ using System; using osu.Framework.Allocation; -using OpenTK; +using osuTK; using osu.Framework.Configuration; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/SummaryTimeline.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/SummaryTimeline.cs index 77878288f9..0b4cba00f3 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/SummaryTimeline.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/SummaryTimeline.cs @@ -1,7 +1,7 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using OpenTK; +using osuTK; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Visualisations/PointVisualisation.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Visualisations/PointVisualisation.cs index 003d969ab8..40955195a4 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Visualisations/PointVisualisation.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Visualisations/PointVisualisation.cs @@ -1,7 +1,7 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using OpenTK; +using osuTK; using osu.Framework.Graphics; using osu.Framework.Graphics.Shapes; diff --git a/osu.Game/Screens/Edit/Screens/Compose/BeatDivisorControl.cs b/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs similarity index 99% rename from osu.Game/Screens/Edit/Screens/Compose/BeatDivisorControl.cs rename to osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs index e46be9f7c1..aa63b02013 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/BeatDivisorControl.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs @@ -15,11 +15,11 @@ using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; -using OpenTK; -using OpenTK.Graphics; -using OpenTK.Input; +using osuTK; +using osuTK.Graphics; +using osuTK.Input; -namespace osu.Game.Screens.Edit.Screens.Compose +namespace osu.Game.Screens.Edit.Compose.Components { public class BeatDivisorControl : CompositeDrawable { diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs new file mode 100644 index 0000000000..80bfe21367 --- /dev/null +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -0,0 +1,207 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// 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.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Primitives; +using osu.Framework.Input.Events; +using osu.Framework.Input.States; +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Edit.Tools; +using osu.Game.Rulesets.Objects.Drawables; + +namespace osu.Game.Screens.Edit.Compose.Components +{ + public class BlueprintContainer : CompositeDrawable + { + private SelectionBlueprintContainer selectionBlueprints; + + private Container placementBlueprintContainer; + private PlacementBlueprint currentPlacement; + + private SelectionHandler selectionHandler; + + private IEnumerable selections => selectionBlueprints.Children.Where(c => c.IsAlive); + + [Resolved] + private HitObjectComposer composer { get; set; } + + public BlueprintContainer() + { + RelativeSizeAxes = Axes.Both; + } + + [BackgroundDependencyLoader] + private void load() + { + selectionHandler = composer.CreateSelectionHandler(); + selectionHandler.DeselectAll = deselectAll; + + var dragBox = new DragBox(select); + dragBox.DragEnd += () => selectionHandler.UpdateVisibility(); + + InternalChildren = new[] + { + dragBox, + selectionHandler, + selectionBlueprints = new SelectionBlueprintContainer { RelativeSizeAxes = Axes.Both }, + placementBlueprintContainer = new Container { RelativeSizeAxes = Axes.Both }, + dragBox.CreateProxy() + }; + + foreach (var obj in composer.HitObjects) + AddBlueprintFor(obj); + } + + private HitObjectCompositionTool currentTool; + + /// + /// The current placement tool. + /// + public HitObjectCompositionTool CurrentTool + { + get => currentTool; + set + { + if (currentTool == value) + return; + currentTool = value; + + refreshTool(); + } + } + + /// + /// Adds a blueprint for a which adds movement support. + /// + /// The to create a blueprint for. + public void AddBlueprintFor(DrawableHitObject hitObject) + { + refreshTool(); + + var blueprint = composer.CreateBlueprintFor(hitObject); + if (blueprint == null) + return; + + blueprint.Selected += onBlueprintSelected; + blueprint.Deselected += onBlueprintDeselected; + blueprint.SelectionRequested += onSelectionRequested; + blueprint.DragRequested += onDragRequested; + + selectionBlueprints.Add(blueprint); + } + + /// + /// Removes a blueprint for a . + /// + /// The for which to remove the blueprint. + public void RemoveBlueprintFor(DrawableHitObject hitObject) + { + var blueprint = selectionBlueprints.Single(m => m.HitObject == hitObject); + if (blueprint == null) + return; + + blueprint.Deselect(); + + blueprint.Selected -= onBlueprintSelected; + blueprint.Deselected -= onBlueprintDeselected; + blueprint.SelectionRequested -= onSelectionRequested; + blueprint.DragRequested -= onDragRequested; + + selectionBlueprints.Remove(blueprint); + } + + protected override bool OnClick(ClickEvent e) + { + deselectAll(); + return true; + } + + protected override void Update() + { + base.Update(); + + if (currentPlacement != null) + { + if (composer.CursorInPlacementArea) + currentPlacement.State = PlacementState.Shown; + else if (currentPlacement?.PlacementBegun == false) + currentPlacement.State = PlacementState.Hidden; + } + } + + /// + /// Refreshes the current placement tool. + /// + private void refreshTool() + { + placementBlueprintContainer.Clear(); + currentPlacement = null; + + var blueprint = CurrentTool?.CreatePlacementBlueprint(); + if (blueprint != null) + placementBlueprintContainer.Child = currentPlacement = blueprint; + } + + /// + /// Select all masks in a given rectangle selection area. + /// + /// The rectangle to perform a selection on in screen-space coordinates. + private void select(RectangleF rect) + { + foreach (var blueprint in selections.ToList()) + { + if (blueprint.IsPresent && rect.Contains(blueprint.SelectionPoint)) + blueprint.Select(); + else + blueprint.Deselect(); + } + } + + /// + /// Deselects all selected s. + /// + private void deselectAll() => selections.ToList().ForEach(m => m.Deselect()); + + private void onBlueprintSelected(SelectionBlueprint blueprint) + { + selectionHandler.HandleSelected(blueprint); + selectionBlueprints.ChangeChildDepth(blueprint, 1); + } + + private void onBlueprintDeselected(SelectionBlueprint blueprint) + { + selectionHandler.HandleDeselected(blueprint); + selectionBlueprints.ChangeChildDepth(blueprint, 0); + } + + private void onSelectionRequested(SelectionBlueprint blueprint, InputState state) => selectionHandler.HandleSelectionRequested(blueprint, state); + + private void onDragRequested(SelectionBlueprint blueprint, DragEvent dragEvent) => selectionHandler.HandleDrag(blueprint, dragEvent); + + private class SelectionBlueprintContainer : Container + { + protected override int Compare(Drawable x, Drawable y) + { + if (!(x is SelectionBlueprint xBlueprint) || !(y is SelectionBlueprint yBlueprint)) + return base.Compare(x, y); + return Compare(xBlueprint, yBlueprint); + } + + public int Compare(SelectionBlueprint x, SelectionBlueprint y) + { + // dpeth is used to denote selected status (we always want selected blueprints to handle input first). + int d = x.Depth.CompareTo(y.Depth); + if (d != 0) + return d; + + // Put earlier hitobjects towards the end of the list, so they handle input first + int i = y.HitObject.HitObject.StartTime.CompareTo(x.HitObject.HitObject.StartTime); + return i == 0 ? CompareReverseChildID(x, y) : i; + } + } + } +} diff --git a/osu.Game/Screens/Edit/Screens/Compose/Layers/DragLayer.cs b/osu.Game/Screens/Edit/Compose/Components/DragBox.cs similarity index 82% rename from osu.Game/Screens/Edit/Screens/Compose/Layers/DragLayer.cs rename to osu.Game/Screens/Edit/Compose/Components/DragBox.cs index 981ddd989c..5051eb40e3 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/Layers/DragLayer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/DragBox.cs @@ -8,15 +8,14 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; -using osu.Game.Rulesets.Edit; -using OpenTK.Graphics; +using osuTK.Graphics; -namespace osu.Game.Screens.Edit.Screens.Compose.Layers +namespace osu.Game.Screens.Edit.Compose.Components { /// - /// A layer that handles and displays drag selection for a collection of s. + /// A box that displays the drag selection and provides selection events for users to handle. /// - public class DragLayer : CompositeDrawable + public class DragBox : CompositeDrawable { private readonly Action performSelection; @@ -28,10 +27,10 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers private Drawable box; /// - /// Creates a new . + /// Creates a new . /// - /// The selectable s. - public DragLayer(Action performSelection) + /// A delegate that performs drag selection. + public DragBox(Action performSelection) { this.performSelection = performSelection; @@ -47,7 +46,7 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers { Masking = true, BorderColour = Color4.White, - BorderThickness = MaskSelection.BORDER_RADIUS, + BorderThickness = SelectionHandler.BORDER_RADIUS, Child = new Box { RelativeSizeAxes = Axes.Both, diff --git a/osu.Game/Screens/Edit/Compose/Components/EditorPlayfieldBorder.cs b/osu.Game/Screens/Edit/Compose/Components/EditorPlayfieldBorder.cs new file mode 100644 index 0000000000..b62ea4cf81 --- /dev/null +++ b/osu.Game/Screens/Edit/Compose/Components/EditorPlayfieldBorder.cs @@ -0,0 +1,32 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osuTK.Graphics; + +namespace osu.Game.Screens.Edit.Compose.Components +{ + /// + /// Provides a border around the playfield. + /// + public class EditorPlayfieldBorder : CompositeDrawable + { + public EditorPlayfieldBorder() + { + RelativeSizeAxes = Axes.Both; + + Masking = true; + BorderColour = Color4.White; + BorderThickness = 2; + + InternalChild = new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0, + AlwaysPresent = true + }; + } + } +} diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs new file mode 100644 index 0000000000..f8ceeedba9 --- /dev/null +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -0,0 +1,184 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Input.Events; +using osu.Framework.Input.States; +using osu.Game.Graphics; +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Drawables; +using osuTK; +using osuTK.Input; + +namespace osu.Game.Screens.Edit.Compose.Components +{ + /// + /// A component which outlines s and handles movement of selections. + /// + public class SelectionHandler : CompositeDrawable + { + public const float BORDER_RADIUS = 2; + + protected IEnumerable SelectedBlueprints => selectedBlueprints; + private readonly List selectedBlueprints; + + protected IEnumerable SelectedHitObjects => selectedBlueprints.Select(b => b.HitObject.HitObject); + + private Drawable outline; + + [Resolved] + private IPlacementHandler placementHandler { get; set; } + + public SelectionHandler() + { + selectedBlueprints = new List(); + + RelativeSizeAxes = Axes.Both; + AlwaysPresent = true; + Alpha = 0; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + InternalChild = outline = new Container + { + Masking = true, + BorderThickness = BORDER_RADIUS, + BorderColour = colours.Yellow, + Child = new Box + { + RelativeSizeAxes = Axes.Both, + AlwaysPresent = true, + Alpha = 0 + } + }; + } + + #region User Input Handling + + /// + /// Handles the selected s being dragged. + /// + /// The that received the drag event. + /// The drag event. + public virtual void HandleDrag(SelectionBlueprint blueprint, DragEvent dragEvent) + { + } + + protected override bool OnKeyDown(KeyDownEvent e) + { + if (e.Repeat) + return base.OnKeyDown(e); + + switch (e.Key) + { + case Key.Delete: + foreach (var h in selectedBlueprints.ToList()) + placementHandler.Delete(h.HitObject.HitObject); + return true; + } + + return base.OnKeyDown(e); + } + + #endregion + + #region Selection Handling + + /// + /// Bind an action to deselect all selected blueprints. + /// + internal Action DeselectAll { private get; set; } + + /// + /// Handle a blueprint becoming selected. + /// + /// The blueprint. + internal void HandleSelected(SelectionBlueprint blueprint) => selectedBlueprints.Add(blueprint); + + /// + /// Handle a blueprint becoming deselected. + /// + /// The blueprint. + internal void HandleDeselected(SelectionBlueprint blueprint) + { + selectedBlueprints.Remove(blueprint); + + // We don't want to update visibility if > 0, since we may be deselecting blueprints during drag-selection + if (selectedBlueprints.Count == 0) + UpdateVisibility(); + } + + /// + /// Handle a blueprint requesting selection. + /// + /// The blueprint. + internal void HandleSelectionRequested(SelectionBlueprint blueprint, InputState state) + { + if (state.Keyboard.ControlPressed) + { + if (blueprint.IsSelected) + blueprint.Deselect(); + else + blueprint.Select(); + } + else + { + if (blueprint.IsSelected) + return; + + DeselectAll?.Invoke(); + blueprint.Select(); + } + + UpdateVisibility(); + } + + #endregion + + /// + /// Updates whether this is visible. + /// + internal void UpdateVisibility() + { + if (selectedBlueprints.Count > 0) + Show(); + else + Hide(); + } + + protected override void Update() + { + base.Update(); + + if (selectedBlueprints.Count == 0) + return; + + // Move the rectangle to cover the hitobjects + var topLeft = new Vector2(float.MaxValue, float.MaxValue); + var bottomRight = new Vector2(float.MinValue, float.MinValue); + + bool hasSelection = false; + + foreach (var blueprint in selectedBlueprints) + { + topLeft = Vector2.ComponentMin(topLeft, ToLocalSpace(blueprint.SelectionQuad.TopLeft)); + bottomRight = Vector2.ComponentMax(bottomRight, ToLocalSpace(blueprint.SelectionQuad.BottomRight)); + } + + topLeft -= new Vector2(5); + bottomRight += new Vector2(5); + + outline.Size = bottomRight - topLeft; + outline.Position = topLeft; + } + } +} diff --git a/osu.Game/Screens/Edit/Screens/Compose/Timeline/CentreMarker.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/CentreMarker.cs similarity index 95% rename from osu.Game/Screens/Edit/Screens/Compose/Timeline/CentreMarker.cs rename to osu.Game/Screens/Edit/Compose/Components/Timeline/CentreMarker.cs index 8e932f307d..620fc255c3 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/Timeline/CentreMarker.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/CentreMarker.cs @@ -6,9 +6,9 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; -using OpenTK; +using osuTK; -namespace osu.Game.Screens.Edit.Screens.Compose.Timeline +namespace osu.Game.Screens.Edit.Compose.Components.Timeline { public class CentreMarker : CompositeDrawable { diff --git a/osu.Game/Screens/Edit/Screens/Compose/Timeline/Timeline.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs similarity index 98% rename from osu.Game/Screens/Edit/Screens/Compose/Timeline/Timeline.cs rename to osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs index da95564975..0c626f2c54 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/Timeline/Timeline.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs @@ -12,7 +12,7 @@ using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Graphics; -namespace osu.Game.Screens.Edit.Screens.Compose.Timeline +namespace osu.Game.Screens.Edit.Compose.Components.Timeline { public class Timeline : ZoomableScrollContainer { diff --git a/osu.Game/Screens/Edit/Screens/Compose/Timeline/TimelineArea.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs similarity index 98% rename from osu.Game/Screens/Edit/Screens/Compose/Timeline/TimelineArea.cs rename to osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs index ecf760be8e..1e16a3264c 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/Timeline/TimelineArea.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs @@ -1,14 +1,14 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using OpenTK; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; +using osuTK; -namespace osu.Game.Screens.Edit.Screens.Compose.Timeline +namespace osu.Game.Screens.Edit.Compose.Components.Timeline { public class TimelineArea : CompositeDrawable { diff --git a/osu.Game/Screens/Edit/Screens/Compose/Timeline/TimelineButton.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineButton.cs similarity index 94% rename from osu.Game/Screens/Edit/Screens/Compose/Timeline/TimelineButton.cs rename to osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineButton.cs index 5928fbaa1b..df482fc1d9 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/Timeline/TimelineButton.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineButton.cs @@ -2,15 +2,15 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System; -using OpenTK; -using OpenTK.Graphics; using osu.Framework.Configuration; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; +using osuTK; +using osuTK.Graphics; -namespace osu.Game.Screens.Edit.Screens.Compose.Timeline +namespace osu.Game.Screens.Edit.Compose.Components.Timeline { public class TimelineButton : CompositeDrawable { diff --git a/osu.Game/Screens/Edit/Screens/Compose/Timeline/ZoomableScrollContainer.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs similarity index 98% rename from osu.Game/Screens/Edit/Screens/Compose/Timeline/ZoomableScrollContainer.cs rename to osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs index bbba439ca7..71cea595d0 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/Timeline/ZoomableScrollContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs @@ -7,9 +7,9 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Transforms; using osu.Framework.Input.Events; using osu.Framework.MathUtils; -using OpenTK; +using osuTK; -namespace osu.Game.Screens.Edit.Screens.Compose.Timeline +namespace osu.Game.Screens.Edit.Compose.Components.Timeline { public class ZoomableScrollContainer : ScrollContainer { diff --git a/osu.Game/Screens/Edit/Screens/Compose/Compose.cs b/osu.Game/Screens/Edit/Compose/ComposeScreen.cs similarity index 86% rename from osu.Game/Screens/Edit/Screens/Compose/Compose.cs rename to osu.Game/Screens/Edit/Compose/ComposeScreen.cs index a862485fd6..91332aa94f 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/Compose.cs +++ b/osu.Game/Screens/Edit/Compose/ComposeScreen.cs @@ -3,24 +3,28 @@ using JetBrains.Annotations; using osu.Framework.Allocation; -using OpenTK.Graphics; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Logging; -using osu.Game.Screens.Edit.Screens.Compose.Timeline; +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Objects; +using osu.Game.Screens.Edit.Compose.Components; +using osu.Game.Screens.Edit.Compose.Components.Timeline; +using osuTK.Graphics; -namespace osu.Game.Screens.Edit.Screens.Compose +namespace osu.Game.Screens.Edit.Compose { - public class Compose : EditorScreen + [Cached(Type = typeof(IPlacementHandler))] + public class ComposeScreen : EditorScreen, IPlacementHandler { private const float vertical_margins = 10; private const float horizontal_margins = 20; private readonly BindableBeatDivisor beatDivisor = new BindableBeatDivisor(); - private Container composerContainer; + private HitObjectComposer composer; [BackgroundDependencyLoader(true)] private void load([CanBeNull] BindableBeatDivisor beatDivisor) @@ -28,6 +32,8 @@ namespace osu.Game.Screens.Edit.Screens.Compose if (beatDivisor != null) this.beatDivisor.BindTo(beatDivisor); + Container composerContainer; + Children = new Drawable[] { new GridContainer @@ -101,7 +107,7 @@ namespace osu.Game.Screens.Edit.Screens.Compose return; } - var composer = ruleset.CreateHitObjectComposer(); + composer = ruleset.CreateHitObjectComposer(); if (composer == null) { Logger.Log($"Ruleset {ruleset.Description} doesn't support hitobject composition."); @@ -111,5 +117,13 @@ namespace osu.Game.Screens.Edit.Screens.Compose composerContainer.Child = composer; } + + public void BeginPlacement(HitObject hitObject) + { + } + + public void EndPlacement(HitObject hitObject) => composer.Add(hitObject); + + public void Delete(HitObject hitObject) => composer.Remove(hitObject); } } diff --git a/osu.Game/Screens/Edit/Compose/IPlacementHandler.cs b/osu.Game/Screens/Edit/Compose/IPlacementHandler.cs new file mode 100644 index 0000000000..f93b294536 --- /dev/null +++ b/osu.Game/Screens/Edit/Compose/IPlacementHandler.cs @@ -0,0 +1,28 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Rulesets.Objects; + +namespace osu.Game.Screens.Edit.Compose +{ + public interface IPlacementHandler + { + /// + /// Notifies that a placement has begun. + /// + /// The being placed. + void BeginPlacement(HitObject hitObject); + + /// + /// Notifies that a placement has finished. + /// + /// The that has been placed. + void EndPlacement(HitObject hitObject); + + /// + /// Deletes a . + /// + /// The to delete. + void Delete(HitObject hitObject); + } +} diff --git a/osu.Game/Screens/Edit/Screens/Design/Design.cs b/osu.Game/Screens/Edit/Design/DesignScreen.cs similarity index 92% rename from osu.Game/Screens/Edit/Screens/Design/Design.cs rename to osu.Game/Screens/Edit/Design/DesignScreen.cs index 052a1c1d90..7c482a9380 100644 --- a/osu.Game/Screens/Edit/Screens/Design/Design.cs +++ b/osu.Game/Screens/Edit/Design/DesignScreen.cs @@ -5,13 +5,13 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics.Sprites; -using OpenTK.Graphics; +using osuTK.Graphics; -namespace osu.Game.Screens.Edit.Screens.Design +namespace osu.Game.Screens.Edit.Design { - public class Design : EditorScreen + public class DesignScreen : EditorScreen { - public Design() + public DesignScreen() { Add(new Container { diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 62cf76ef69..9bccefc508 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -1,14 +1,14 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using OpenTK.Graphics; +using System; +using osuTK.Graphics; using osu.Framework.Screens; using osu.Game.Screens.Backgrounds; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; -using osu.Game.Screens.Edit.Menus; using osu.Game.Screens.Edit.Components.Timelines.Summary; using osu.Framework.Allocation; using osu.Framework.Graphics.UserInterface; @@ -16,10 +16,11 @@ using osu.Framework.Input.Events; using osu.Framework.Platform; using osu.Framework.Timing; using osu.Game.Graphics.UserInterface; -using osu.Game.Screens.Edit.Screens; -using osu.Game.Screens.Edit.Screens.Compose; -using osu.Game.Screens.Edit.Screens.Design; using osu.Game.Screens.Edit.Components; +using osu.Game.Screens.Edit.Components.Menus; +using osu.Game.Screens.Edit.Compose; +using osu.Game.Screens.Edit.Design; +using osuTK.Input; namespace osu.Game.Screens.Edit { @@ -157,37 +158,39 @@ namespace osu.Game.Screens.Edit bottomBackground.Colour = colours.Gray2; } - private void exportBeatmap() + protected override bool OnKeyDown(KeyDownEvent e) { - host.OpenFileExternally(Beatmap.Value.Save()); - } - - private void onModeChanged(EditorScreenMode mode) - { - currentScreen?.Exit(); - - switch (mode) + switch (e.Key) { - case EditorScreenMode.Compose: - currentScreen = new Compose(); - break; - case EditorScreenMode.Design: - currentScreen = new Design(); - break; - default: - currentScreen = new EditorScreen(); - break; + case Key.Left: + seek(e, -1); + return true; + case Key.Right: + seek(e, 1); + return true; } - LoadComponentAsync(currentScreen, screenContainer.Add); + return base.OnKeyDown(e); } + private double scrollAccumulation; + protected override bool OnScroll(ScrollEvent e) { - if (e.ScrollDelta.X + e.ScrollDelta.Y > 0) - clock.SeekBackward(!clock.IsRunning); - else - clock.SeekForward(!clock.IsRunning); + scrollAccumulation += (e.ScrollDelta.X + e.ScrollDelta.Y) * (e.IsPrecise ? 0.1 : 1); + + const int precision = 1; + + while (Math.Abs(scrollAccumulation) > precision) + { + if (scrollAccumulation > 0) + seek(e, -1); + else + seek(e, 1); + + scrollAccumulation = scrollAccumulation < 0 ? Math.Min(0, scrollAccumulation + precision) : Math.Max(0, scrollAccumulation - precision); + } + return true; } @@ -212,7 +215,40 @@ namespace osu.Game.Screens.Edit Beatmap.Value.Track.Tempo.Value = 1; Beatmap.Value.Track.Start(); } + return base.OnExiting(next); } + + private void exportBeatmap() => host.OpenFileExternally(Beatmap.Value.Save()); + + private void onModeChanged(EditorScreenMode mode) + { + currentScreen?.Exit(); + + switch (mode) + { + case EditorScreenMode.Compose: + currentScreen = new ComposeScreen(); + break; + case EditorScreenMode.Design: + currentScreen = new DesignScreen(); + break; + default: + currentScreen = new EditorScreen(); + break; + } + + LoadComponentAsync(currentScreen, screenContainer.Add); + } + + private void seek(UIEvent e, int direction) + { + double amount = e.ShiftPressed ? 2 : 1; + + if (direction < 1) + clock.SeekBackward(!clock.IsRunning, amount); + else + clock.SeekForward(!clock.IsRunning, amount); + } } } diff --git a/osu.Game/Screens/Edit/EditorClock.cs b/osu.Game/Screens/Edit/EditorClock.cs index 1c40181ec9..aa30b1a9f5 100644 --- a/osu.Game/Screens/Edit/EditorClock.cs +++ b/osu.Game/Screens/Edit/EditorClock.cs @@ -7,8 +7,7 @@ using osu.Framework.MathUtils; using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; -using osu.Game.Screens.Edit.Screens.Compose; -using OpenTK; +using osuTK; namespace osu.Game.Screens.Edit { @@ -69,16 +68,20 @@ namespace osu.Game.Screens.Edit /// Seeks backwards by one beat length. /// /// Whether to snap to the closest beat after seeking. - public void SeekBackward(bool snapped = false) => seek(-1, snapped); + /// The relative amount (magnitude) which should be seeked. + public void SeekBackward(bool snapped = false, double amount = 1) => seek(-1, snapped, amount); /// /// Seeks forwards by one beat length. /// /// Whether to snap to the closest beat after seeking. - public void SeekForward(bool snapped = false) => seek(1, snapped); + /// The relative amount (magnitude) which should be seeked. + public void SeekForward(bool snapped = false, double amount = 1) => seek(1, snapped, amount); - private void seek(int direction, bool snapped) + private void seek(int direction, bool snapped, double amount = 1) { + if (amount <= 0) throw new ArgumentException("Value should be greater than zero", nameof(amount)); + var timingPoint = ControlPointInfo.TimingPointAt(CurrentTime); if (direction < 0 && timingPoint.Time == CurrentTime) { @@ -88,7 +91,7 @@ namespace osu.Game.Screens.Edit timingPoint = ControlPointInfo.TimingPoints[--activeIndex]; } - double seekAmount = timingPoint.BeatLength / beatDivisor; + double seekAmount = timingPoint.BeatLength / beatDivisor * amount; double seekTime = CurrentTime + seekAmount * direction; if (!snapped || ControlPointInfo.TimingPoints.Count == 0) diff --git a/osu.Game/Screens/Edit/Screens/EditorScreen.cs b/osu.Game/Screens/Edit/EditorScreen.cs similarity index 97% rename from osu.Game/Screens/Edit/Screens/EditorScreen.cs rename to osu.Game/Screens/Edit/EditorScreen.cs index f8402b9a9f..3a8fc3ef80 100644 --- a/osu.Game/Screens/Edit/Screens/EditorScreen.cs +++ b/osu.Game/Screens/Edit/EditorScreen.cs @@ -7,7 +7,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Beatmaps; -namespace osu.Game.Screens.Edit.Screens +namespace osu.Game.Screens.Edit { /// /// TODO: eventually make this inherit Screen and add a local scren stack inside the Editor. diff --git a/osu.Game/Screens/Edit/Screens/EditorScreenMode.cs b/osu.Game/Screens/Edit/EditorScreenMode.cs similarity index 91% rename from osu.Game/Screens/Edit/Screens/EditorScreenMode.cs rename to osu.Game/Screens/Edit/EditorScreenMode.cs index be8363680d..17de6c4125 100644 --- a/osu.Game/Screens/Edit/Screens/EditorScreenMode.cs +++ b/osu.Game/Screens/Edit/EditorScreenMode.cs @@ -3,7 +3,7 @@ using System.ComponentModel; -namespace osu.Game.Screens.Edit.Screens +namespace osu.Game.Screens.Edit { public enum EditorScreenMode { diff --git a/osu.Game/Screens/Edit/Screens/Compose/Layers/BorderLayer.cs b/osu.Game/Screens/Edit/Screens/Compose/Layers/BorderLayer.cs deleted file mode 100644 index c46f9a1b7f..0000000000 --- a/osu.Game/Screens/Edit/Screens/Compose/Layers/BorderLayer.cs +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using OpenTK.Graphics; - -namespace osu.Game.Screens.Edit.Screens.Compose.Layers -{ - public class BorderLayer : Container - { - protected override Container Content => content; - private readonly Container content; - - public BorderLayer() - { - InternalChildren = new Drawable[] - { - new Container - { - Name = "Border", - RelativeSizeAxes = Axes.Both, - Masking = true, - BorderColour = Color4.White, - BorderThickness = 2, - Child = new Box - { - RelativeSizeAxes = Axes.Both, - Alpha = 0, - AlwaysPresent = true - } - }, - content = new Container { RelativeSizeAxes = Axes.Both } - }; - } - } -} diff --git a/osu.Game/Screens/Edit/Screens/Compose/Layers/HitObjectMaskLayer.cs b/osu.Game/Screens/Edit/Screens/Compose/Layers/HitObjectMaskLayer.cs deleted file mode 100644 index 65f31dd56d..0000000000 --- a/osu.Game/Screens/Edit/Screens/Compose/Layers/HitObjectMaskLayer.cs +++ /dev/null @@ -1,73 +0,0 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// 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.Input.Events; -using osu.Game.Rulesets.Edit; -using osu.Game.Rulesets.Objects.Drawables; - -namespace osu.Game.Screens.Edit.Screens.Compose.Layers -{ - public class HitObjectMaskLayer : CompositeDrawable - { - private MaskContainer maskContainer; - private HitObjectComposer composer; - - public HitObjectMaskLayer() - { - RelativeSizeAxes = Axes.Both; - } - - [BackgroundDependencyLoader] - private void load(HitObjectComposer composer) - { - this.composer = composer; - - maskContainer = new MaskContainer(); - - var maskSelection = composer.CreateMaskSelection(); - - maskContainer.MaskSelected += maskSelection.HandleSelected; - maskContainer.MaskDeselected += maskSelection.HandleDeselected; - maskContainer.MaskSelectionRequested += maskSelection.HandleSelectionRequested; - maskContainer.MaskDragRequested += maskSelection.HandleDrag; - - maskSelection.DeselectAll = maskContainer.DeselectAll; - - var dragLayer = new DragLayer(maskContainer.Select); - dragLayer.DragEnd += () => maskSelection.UpdateVisibility(); - - InternalChildren = new[] - { - dragLayer, - maskSelection, - maskContainer, - dragLayer.CreateProxy() - }; - - foreach (var obj in composer.HitObjects) - addMask(obj); - } - - protected override bool OnMouseDown(MouseDownEvent e) - { - maskContainer.DeselectAll(); - return true; - } - - /// - /// Adds a mask for a which adds movement support. - /// - /// The to create a mask for. - private void addMask(DrawableHitObject hitObject) - { - var mask = composer.CreateMaskFor(hitObject); - if (mask == null) - return; - - maskContainer.Add(mask); - } - } -} diff --git a/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskContainer.cs b/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskContainer.cs deleted file mode 100644 index 19258d669e..0000000000 --- a/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskContainer.cs +++ /dev/null @@ -1,128 +0,0 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.Collections.Generic; -using System.Linq; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Input.States; -using osu.Game.Rulesets.Edit; -using OpenTK; -using RectangleF = osu.Framework.Graphics.Primitives.RectangleF; - -namespace osu.Game.Screens.Edit.Screens.Compose.Layers -{ - public class MaskContainer : Container - { - /// - /// Invoked when any is selected. - /// - public event Action MaskSelected; - - /// - /// Invoked when any is deselected. - /// - public event Action MaskDeselected; - - /// - /// Invoked when any requests selection. - /// - public event Action MaskSelectionRequested; - - /// - /// Invoked when any requests drag. - /// - public event Action MaskDragRequested; - - private IEnumerable aliveMasks => AliveInternalChildren.Cast(); - - public MaskContainer() - { - RelativeSizeAxes = Axes.Both; - } - - public override void Add(HitObjectMask drawable) - { - if (drawable == null) throw new ArgumentNullException(nameof(drawable)); - - base.Add(drawable); - - drawable.Selected += onMaskSelected; - drawable.Deselected += onMaskDeselected; - drawable.SelectionRequested += onSelectionRequested; - drawable.DragRequested += onDragRequested; - } - - public override bool Remove(HitObjectMask drawable) - { - if (drawable == null) throw new ArgumentNullException(nameof(drawable)); - - var result = base.Remove(drawable); - - if (result) - { - drawable.Selected -= onMaskSelected; - drawable.Deselected -= onMaskDeselected; - drawable.SelectionRequested -= onSelectionRequested; - drawable.DragRequested -= onDragRequested; - } - - return result; - } - - /// - /// Select all masks in a given rectangle selection area. - /// - /// The rectangle to perform a selection on in screen-space coordinates. - public void Select(RectangleF rect) - { - foreach (var mask in aliveMasks.ToList()) - { - if (mask.IsPresent && rect.Contains(mask.SelectionPoint)) - mask.Select(); - else - mask.Deselect(); - } - } - - /// - /// Deselects all selected s. - /// - public void DeselectAll() => aliveMasks.ToList().ForEach(m => m.Deselect()); - - private void onMaskSelected(HitObjectMask mask) - { - MaskSelected?.Invoke(mask); - ChangeChildDepth(mask, 1); - } - - private void onMaskDeselected(HitObjectMask mask) - { - MaskDeselected?.Invoke(mask); - ChangeChildDepth(mask, 0); - } - - private void onSelectionRequested(HitObjectMask mask, InputState state) => MaskSelectionRequested?.Invoke(mask, state); - private void onDragRequested(HitObjectMask mask, Vector2 delta, InputState state) => MaskDragRequested?.Invoke(mask, delta, state); - - protected override int Compare(Drawable x, Drawable y) - { - if (!(x is HitObjectMask xMask) || !(y is HitObjectMask yMask)) - return base.Compare(x, y); - return Compare(xMask, yMask); - } - - public int Compare(HitObjectMask x, HitObjectMask y) - { - // dpeth is used to denote selected status (we always want selected masks to handle input first). - int d = x.Depth.CompareTo(y.Depth); - if (d != 0) - return d; - - // Put earlier hitobjects towards the end of the list, so they handle input first - int i = y.HitObject.HitObject.StartTime.CompareTo(x.HitObject.HitObject.StartTime); - return i == 0 ? CompareReverseChildID(x, y) : i; - } - } -} diff --git a/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskSelection.cs b/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskSelection.cs deleted file mode 100644 index 635edf82da..0000000000 --- a/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskSelection.cs +++ /dev/null @@ -1,164 +0,0 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.Collections.Generic; -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Input.States; -using osu.Game.Graphics; -using osu.Game.Rulesets.Edit; -using osu.Game.Rulesets.Edit.Types; -using OpenTK; - -namespace osu.Game.Screens.Edit.Screens.Compose.Layers -{ - /// - /// A box which surrounds s and provides interactive handles, context menus etc. - /// - public class MaskSelection : CompositeDrawable - { - public const float BORDER_RADIUS = 2; - - private readonly List selectedMasks; - - private Drawable outline; - - public MaskSelection() - { - selectedMasks = new List(); - - RelativeSizeAxes = Axes.Both; - AlwaysPresent = true; - Alpha = 0; - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - InternalChild = outline = new Container - { - Masking = true, - BorderThickness = BORDER_RADIUS, - BorderColour = colours.Yellow, - Child = new Box - { - RelativeSizeAxes = Axes.Both, - AlwaysPresent = true, - Alpha = 0 - } - }; - } - - #region User Input Handling - - public void HandleDrag(HitObjectMask m, Vector2 delta, InputState state) - { - // Todo: Various forms of snapping - - foreach (var mask in selectedMasks) - { - switch (mask.HitObject.HitObject) - { - case IHasEditablePosition editablePosition: - editablePosition.OffsetPosition(delta); - break; - } - } - } - - #endregion - - #region Selection Handling - - /// - /// Bind an action to deselect all selected masks. - /// - public Action DeselectAll { private get; set; } - - /// - /// Handle a mask becoming selected. - /// - /// The mask. - public void HandleSelected(HitObjectMask mask) => selectedMasks.Add(mask); - - /// - /// Handle a mask becoming deselected. - /// - /// The mask. - public void HandleDeselected(HitObjectMask mask) - { - selectedMasks.Remove(mask); - - // We don't want to update visibility if > 0, since we may be deselecting masks during drag-selection - if (selectedMasks.Count == 0) - UpdateVisibility(); - } - - /// - /// Handle a mask requesting selection. - /// - /// The mask. - public void HandleSelectionRequested(HitObjectMask mask, InputState state) - { - if (state.Keyboard.ControlPressed) - { - if (mask.IsSelected) - mask.Deselect(); - else - mask.Select(); - } - else - { - if (mask.IsSelected) - return; - - DeselectAll?.Invoke(); - mask.Select(); - } - - UpdateVisibility(); - } - - #endregion - - /// - /// Updates whether this is visible. - /// - internal void UpdateVisibility() - { - if (selectedMasks.Count > 0) - Show(); - else - Hide(); - } - - protected override void Update() - { - base.Update(); - - if (selectedMasks.Count == 0) - return; - - // Move the rectangle to cover the hitobjects - var topLeft = new Vector2(float.MaxValue, float.MaxValue); - var bottomRight = new Vector2(float.MinValue, float.MinValue); - - bool hasSelection = false; - - foreach (var mask in selectedMasks) - { - topLeft = Vector2.ComponentMin(topLeft, ToLocalSpace(mask.SelectionQuad.TopLeft)); - bottomRight = Vector2.ComponentMax(bottomRight, ToLocalSpace(mask.SelectionQuad.BottomRight)); - } - - topLeft -= new Vector2(5); - bottomRight += new Vector2(5); - - outline.Size = bottomRight - topLeft; - outline.Position = topLeft; - } - } -} diff --git a/osu.Game/Screens/Edit/Screens/Setup/Components/LabelledComponents/LabelledTextBox.cs b/osu.Game/Screens/Edit/Setup/Components/LabelledComponents/LabelledTextBox.cs similarity index 97% rename from osu.Game/Screens/Edit/Screens/Setup/Components/LabelledComponents/LabelledTextBox.cs rename to osu.Game/Screens/Edit/Setup/Components/LabelledComponents/LabelledTextBox.cs index 94200b7f4e..eae080bc81 100644 --- a/osu.Game/Screens/Edit/Screens/Setup/Components/LabelledComponents/LabelledTextBox.cs +++ b/osu.Game/Screens/Edit/Setup/Components/LabelledComponents/LabelledTextBox.cs @@ -2,7 +2,6 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using osu.Framework.Allocation; -using OpenTK.Graphics; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -10,8 +9,9 @@ using osu.Framework.Graphics.UserInterface; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; +using osuTK.Graphics; -namespace osu.Game.Screens.Edit.Screens.Setup.Components.LabelledComponents +namespace osu.Game.Screens.Edit.Setup.Components.LabelledComponents { public class LabelledTextBox : CompositeDrawable { diff --git a/osu.Game/Screens/Loader.cs b/osu.Game/Screens/Loader.cs index 3cef20e510..a59a8a77ef 100644 --- a/osu.Game/Screens/Loader.cs +++ b/osu.Game/Screens/Loader.cs @@ -7,7 +7,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Shaders; using osu.Game.Screens.Menu; -using OpenTK; +using osuTK; using osu.Framework.Screens; using osu.Game.Overlays; diff --git a/osu.Game/Screens/Menu/Button.cs b/osu.Game/Screens/Menu/Button.cs index 2b85ee6158..fee5453165 100644 --- a/osu.Game/Screens/Menu/Button.cs +++ b/osu.Game/Screens/Menu/Button.cs @@ -11,9 +11,9 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; -using OpenTK; -using OpenTK.Graphics; -using OpenTK.Input; +using osuTK; +using osuTK.Graphics; +using osuTK.Input; using osu.Framework.Extensions.Color4Extensions; using osu.Game.Graphics.Containers; using osu.Framework.Audio.Track; diff --git a/osu.Game/Screens/Menu/ButtonArea.cs b/osu.Game/Screens/Menu/ButtonArea.cs index 06004405b6..88fdb0a05e 100644 --- a/osu.Game/Screens/Menu/ButtonArea.cs +++ b/osu.Game/Screens/Menu/ButtonArea.cs @@ -7,7 +7,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; -using OpenTK; +using osuTK; namespace osu.Game.Screens.Menu { diff --git a/osu.Game/Screens/Menu/ButtonSystem.cs b/osu.Game/Screens/Menu/ButtonSystem.cs index 5c17317fc1..ae1f27610b 100644 --- a/osu.Game/Screens/Menu/ButtonSystem.cs +++ b/osu.Game/Screens/Menu/ButtonSystem.cs @@ -8,17 +8,19 @@ using osu.Framework; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; +using osu.Framework.Configuration; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input.Bindings; using osu.Framework.Logging; using osu.Framework.Threading; using osu.Game.Graphics; +using osu.Game.Input; using osu.Game.Input.Bindings; using osu.Game.Overlays; -using OpenTK; -using OpenTK.Graphics; -using OpenTK.Input; +using osuTK; +using osuTK.Graphics; +using osuTK.Input; namespace osu.Game.Screens.Menu { @@ -26,6 +28,8 @@ namespace osu.Game.Screens.Menu { public event Action StateChanged; + private readonly IBindable isIdle = new BindableBool(); + public Action OnEdit; public Action OnExit; public Action OnDirect; @@ -102,12 +106,22 @@ namespace osu.Game.Screens.Menu private OsuGame game; [BackgroundDependencyLoader(true)] - private void load(AudioManager audio, OsuGame game) + private void load(AudioManager audio, OsuGame game, IdleTracker idleTracker) { this.game = game; + + isIdle.ValueChanged += updateIdleState; + if (idleTracker != null) isIdle.BindTo(idleTracker.IsIdle); + sampleBack = audio.Sample.Get(@"Menu/button-back-select"); } + private void updateIdleState(bool isIdle) + { + if (isIdle && State != ButtonSystemState.Exit) + State = ButtonSystemState.Initial; + } + public bool OnPressed(GlobalAction action) { switch (action) @@ -266,9 +280,6 @@ namespace osu.Game.Screens.Menu protected override void Update() { - //if (OsuGame.IdleTime > 6000 && State != MenuState.Exit) - // State = MenuState.Initial; - base.Update(); if (logo != null) diff --git a/osu.Game/Screens/Menu/Disclaimer.cs b/osu.Game/Screens/Menu/Disclaimer.cs index 694f911773..c9ad519897 100644 --- a/osu.Game/Screens/Menu/Disclaimer.cs +++ b/osu.Game/Screens/Menu/Disclaimer.cs @@ -6,8 +6,8 @@ using osu.Framework.Graphics; using osu.Framework.Screens; using osu.Game.Graphics; using osu.Game.Graphics.Containers; -using OpenTK; -using OpenTK.Graphics; +using osuTK; +using osuTK.Graphics; using osu.Game.Overlays; namespace osu.Game.Screens.Menu diff --git a/osu.Game/Screens/Menu/FlowContainerWithOrigin.cs b/osu.Game/Screens/Menu/FlowContainerWithOrigin.cs index 30c420c05d..1c6fe37b1b 100644 --- a/osu.Game/Screens/Menu/FlowContainerWithOrigin.cs +++ b/osu.Game/Screens/Menu/FlowContainerWithOrigin.cs @@ -3,7 +3,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using OpenTK; +using osuTK; namespace osu.Game.Screens.Menu { diff --git a/osu.Game/Screens/Menu/Intro.cs b/osu.Game/Screens/Menu/Intro.cs index c1032011f7..fa01411a0f 100644 --- a/osu.Game/Screens/Menu/Intro.cs +++ b/osu.Game/Screens/Menu/Intro.cs @@ -13,8 +13,8 @@ using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.IO.Archives; using osu.Game.Screens.Backgrounds; -using OpenTK; -using OpenTK.Graphics; +using osuTK; +using osuTK.Graphics; using osu.Game.Overlays; namespace osu.Game.Screens.Menu diff --git a/osu.Game/Screens/Menu/IntroSequence.cs b/osu.Game/Screens/Menu/IntroSequence.cs index b5591ec78a..91dc7937f8 100644 --- a/osu.Game/Screens/Menu/IntroSequence.cs +++ b/osu.Game/Screens/Menu/IntroSequence.cs @@ -2,8 +2,8 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System.Linq; -using OpenTK; -using OpenTK.Graphics; +using osuTK; +using osuTK.Graphics; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; diff --git a/osu.Game/Screens/Menu/LogoVisualisation.cs b/osu.Game/Screens/Menu/LogoVisualisation.cs index 34fb0b196b..70a01a5c9f 100644 --- a/osu.Game/Screens/Menu/LogoVisualisation.cs +++ b/osu.Game/Screens/Menu/LogoVisualisation.cs @@ -1,9 +1,9 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using OpenTK; -using OpenTK.Graphics; -using OpenTK.Graphics.ES30; +using osuTK; +using osuTK.Graphics; +using osuTK.Graphics.ES30; using osu.Framework.Configuration; using osu.Framework.Graphics; using osu.Framework.Graphics.Batches; diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index 2374d6a2fe..974e42dda0 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -1,9 +1,9 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using OpenTK; -using OpenTK.Graphics; -using OpenTK.Input; +using osuTK; +using osuTK.Graphics; +using osuTK.Input; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Input.Events; @@ -29,6 +29,8 @@ namespace osu.Game.Screens.Menu protected override bool AllowBackButton => buttons.State != ButtonSystemState.Initial; + public override bool AllowExternalScreenChange => true; + private readonly BackgroundScreenDefault background; private Screen songSelect; diff --git a/osu.Game/Screens/Menu/MenuSideFlashes.cs b/osu.Game/Screens/Menu/MenuSideFlashes.cs index 3de68fe914..ec5528b13f 100644 --- a/osu.Game/Screens/Menu/MenuSideFlashes.cs +++ b/osu.Game/Screens/Menu/MenuSideFlashes.cs @@ -1,7 +1,7 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using OpenTK.Graphics; +using osuTK.Graphics; using osu.Framework.Allocation; using osu.Framework.Audio.Track; using osu.Framework.Configuration; diff --git a/osu.Game/Screens/Menu/OsuLogo.cs b/osu.Game/Screens/Menu/OsuLogo.cs index 4e6a107f91..f858fded92 100644 --- a/osu.Game/Screens/Menu/OsuLogo.cs +++ b/osu.Game/Screens/Menu/OsuLogo.cs @@ -17,9 +17,9 @@ using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics; using osu.Game.Graphics.Backgrounds; using osu.Game.Graphics.Containers; -using OpenTK; -using OpenTK.Graphics; -using OpenTK.Input; +using osuTK; +using osuTK.Graphics; +using osuTK.Input; namespace osu.Game.Screens.Menu { diff --git a/osu.Game/Screens/Multi/Components/BeatmapTypeInfo.cs b/osu.Game/Screens/Multi/Components/BeatmapTypeInfo.cs index 78ffe01ef0..a22e171275 100644 --- a/osu.Game/Screens/Multi/Components/BeatmapTypeInfo.cs +++ b/osu.Game/Screens/Multi/Components/BeatmapTypeInfo.cs @@ -8,7 +8,7 @@ using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Online.Multiplayer; -using OpenTK; +using osuTK; namespace osu.Game.Screens.Multi.Components { diff --git a/osu.Game/Screens/Multi/Components/DrawableRoom.cs b/osu.Game/Screens/Multi/Components/DrawableRoom.cs index 67db23263b..05ec2f0fac 100644 --- a/osu.Game/Screens/Multi/Components/DrawableRoom.cs +++ b/osu.Game/Screens/Multi/Components/DrawableRoom.cs @@ -19,8 +19,8 @@ using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Online.Multiplayer; using osu.Game.Users; -using OpenTK; -using OpenTK.Graphics; +using osuTK; +using osuTK.Graphics; namespace osu.Game.Screens.Multi.Components { diff --git a/osu.Game/Screens/Multi/Components/ModeTypeInfo.cs b/osu.Game/Screens/Multi/Components/ModeTypeInfo.cs index e2d0268090..95f0c2153d 100644 --- a/osu.Game/Screens/Multi/Components/ModeTypeInfo.cs +++ b/osu.Game/Screens/Multi/Components/ModeTypeInfo.cs @@ -6,7 +6,7 @@ using osu.Framework.Graphics.Containers; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables; using osu.Game.Online.Multiplayer; -using OpenTK; +using osuTK; namespace osu.Game.Screens.Multi.Components { diff --git a/osu.Game/Screens/Multi/Components/ParticipantInfo.cs b/osu.Game/Screens/Multi/Components/ParticipantInfo.cs index ab404488f1..b1c77a04af 100644 --- a/osu.Game/Screens/Multi/Components/ParticipantInfo.cs +++ b/osu.Game/Screens/Multi/Components/ParticipantInfo.cs @@ -10,7 +10,7 @@ using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Users; -using OpenTK; +using osuTK; namespace osu.Game.Screens.Multi.Components { diff --git a/osu.Game/Screens/Multi/Components/RoomInspector.cs b/osu.Game/Screens/Multi/Components/RoomInspector.cs index 22bca1efc7..1ec509d5a8 100644 --- a/osu.Game/Screens/Multi/Components/RoomInspector.cs +++ b/osu.Game/Screens/Multi/Components/RoomInspector.cs @@ -18,8 +18,8 @@ using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Online.Multiplayer; using osu.Game.Users; -using OpenTK; -using OpenTK.Graphics; +using osuTK; +using osuTK.Graphics; namespace osu.Game.Screens.Multi.Components { diff --git a/osu.Game/Screens/Multi/Header.cs b/osu.Game/Screens/Multi/Header.cs index 809fc25083..6bd300eadc 100644 --- a/osu.Game/Screens/Multi/Header.cs +++ b/osu.Game/Screens/Multi/Header.cs @@ -11,8 +11,8 @@ using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Overlays.SearchableList; using osu.Game.Screens.Multi.Screens; -using OpenTK; -using OpenTK.Graphics; +using osuTK; +using osuTK.Graphics; namespace osu.Game.Screens.Multi { diff --git a/osu.Game/Screens/Multi/Screens/Lounge/FilterControl.cs b/osu.Game/Screens/Multi/Screens/Lounge/FilterControl.cs index 421c89479a..69cb6ad349 100644 --- a/osu.Game/Screens/Multi/Screens/Lounge/FilterControl.cs +++ b/osu.Game/Screens/Multi/Screens/Lounge/FilterControl.cs @@ -4,7 +4,7 @@ using osu.Game.Graphics; using osu.Game.Online.Multiplayer; using osu.Game.Overlays.SearchableList; -using OpenTK.Graphics; +using osuTK.Graphics; namespace osu.Game.Screens.Multi.Screens.Lounge { diff --git a/osu.Game/Screens/Multi/Screens/Lounge/Lounge.cs b/osu.Game/Screens/Multi/Screens/Lounge/Lounge.cs index 3b3f789628..0af941a668 100644 --- a/osu.Game/Screens/Multi/Screens/Lounge/Lounge.cs +++ b/osu.Game/Screens/Multi/Screens/Lounge/Lounge.cs @@ -12,7 +12,7 @@ using osu.Game.Graphics.UserInterface; using osu.Game.Online.Multiplayer; using osu.Game.Overlays.SearchableList; using osu.Game.Screens.Multi.Components; -using OpenTK; +using osuTK; namespace osu.Game.Screens.Multi.Screens.Lounge { diff --git a/osu.Game/Screens/Multi/Screens/Match/Header.cs b/osu.Game/Screens/Multi/Screens/Match/Header.cs index d469815d59..c2de50fa65 100644 --- a/osu.Game/Screens/Multi/Screens/Match/Header.cs +++ b/osu.Game/Screens/Multi/Screens/Match/Header.cs @@ -16,7 +16,7 @@ using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Overlays.SearchableList; -using OpenTK.Graphics; +using osuTK.Graphics; namespace osu.Game.Screens.Multi.Screens.Match { diff --git a/osu.Game/Screens/Multi/Screens/Match/Info.cs b/osu.Game/Screens/Multi/Screens/Match/Info.cs index ec93eb90b1..a2c056c8bd 100644 --- a/osu.Game/Screens/Multi/Screens/Match/Info.cs +++ b/osu.Game/Screens/Multi/Screens/Match/Info.cs @@ -15,7 +15,7 @@ using osu.Game.Graphics.UserInterface; using osu.Game.Online.Multiplayer; using osu.Game.Overlays.SearchableList; using osu.Game.Screens.Multi.Components; -using OpenTK; +using osuTK; namespace osu.Game.Screens.Multi.Screens.Match { diff --git a/osu.Game/Screens/Multi/Screens/Match/Match.cs b/osu.Game/Screens/Multi/Screens/Match/Match.cs index ce3f7825a4..f7d98df60e 100644 --- a/osu.Game/Screens/Multi/Screens/Match/Match.cs +++ b/osu.Game/Screens/Multi/Screens/Match/Match.cs @@ -7,6 +7,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Beatmaps; using osu.Game.Online.Multiplayer; +using osu.Game.Screens.Multi.Screens.Match.Settings; using osu.Game.Screens.Select; using osu.Game.Users; @@ -34,11 +35,15 @@ namespace osu.Game.Screens.Multi.Screens.Match { this.room = room; Header header; + RoomSettingsOverlay settings; Info info; Children = new Drawable[] { - header = new Header(), + header = new Header + { + Depth = -1, + }, info = new Info { Margin = new MarginPadding { Top = Header.HEIGHT }, @@ -48,6 +53,16 @@ namespace osu.Game.Screens.Multi.Screens.Match RelativeSizeAxes = Axes.Both, Padding = new MarginPadding { Top = Header.HEIGHT + Info.HEIGHT }, }, + new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Top = Header.HEIGHT }, + Child = settings = new RoomSettingsOverlay(room) + { + RelativeSizeAxes = Axes.Both, + Height = 0.9f, + }, + }, }; header.OnRequestSelectBeatmap = () => Push(new MatchSongSelect()); @@ -59,6 +74,20 @@ namespace osu.Game.Screens.Multi.Screens.Match info.Beatmap = b; }, true); + header.Tabs.Current.ValueChanged += t => + { + if (t == MatchHeaderPage.Settings) + settings.Show(); + else + settings.Hide(); + }; + + settings.StateChanged += s => + { + if (s == Visibility.Hidden) + header.Tabs.Current.Value = MatchHeaderPage.Room; + }; + nameBind.BindTo(room.Name); nameBind.BindValueChanged(n => info.Name = n, true); diff --git a/osu.Game/Screens/Multi/Screens/Match/Participants.cs b/osu.Game/Screens/Multi/Screens/Match/Participants.cs index 9fa90f8752..55541a1acd 100644 --- a/osu.Game/Screens/Multi/Screens/Match/Participants.cs +++ b/osu.Game/Screens/Multi/Screens/Match/Participants.cs @@ -8,7 +8,7 @@ using osu.Framework.Graphics.Containers; using osu.Game.Overlays.SearchableList; using osu.Game.Screens.Multi.Components; using osu.Game.Users; -using OpenTK; +using osuTK; namespace osu.Game.Screens.Multi.Screens.Match { diff --git a/osu.Game/Screens/Multi/Screens/Match/Settings/GameTypePicker.cs b/osu.Game/Screens/Multi/Screens/Match/Settings/GameTypePicker.cs new file mode 100644 index 0000000000..cd8b081b4e --- /dev/null +++ b/osu.Game/Screens/Multi/Screens/Match/Settings/GameTypePicker.cs @@ -0,0 +1,110 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// 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.UserInterface; +using osu.Framework.Input.Events; +using osu.Game.Graphics; +using osu.Game.Online.Multiplayer; +using osu.Game.Screens.Multi.Components; +using osuTK; + +namespace osu.Game.Screens.Multi.Screens.Match.Settings +{ + public class GameTypePicker : TabControl + { + private const float height = 40; + private const float selection_width = 3; + + protected override TabItem CreateTabItem(GameType value) => new GameTypePickerItem(value); + protected override Dropdown CreateDropdown() => null; + + public GameTypePicker() + { + Height = height + selection_width * 2; + TabContainer.Spacing = new Vector2(10 - selection_width * 2); + + AddItem(new GameTypeTag()); + AddItem(new GameTypeVersus()); + AddItem(new GameTypeTagTeam()); + AddItem(new GameTypeTeamVersus()); + } + + private class GameTypePickerItem : TabItem + { + private const float transition_duration = 200; + + private readonly CircularContainer hover, selection; + + public GameTypePickerItem(GameType value) : base(value) + { + AutoSizeAxes = Axes.Both; + + Children = new Drawable[] + { + selection = new CircularContainer + { + RelativeSizeAxes = Axes.Both, + Masking = true, + Alpha = 0, + Child = new Box + { + RelativeSizeAxes = Axes.Both, + }, + }, + new DrawableGameType(Value) + { + Size = new Vector2(height), + Margin = new MarginPadding(selection_width), + }, + new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding(selection_width), + Child = hover = new CircularContainer + { + RelativeSizeAxes = Axes.Both, + Masking = true, + Alpha = 0, + Child = new Box + { + RelativeSizeAxes = Axes.Both, + }, + }, + }, + }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + selection.Colour = colours.Yellow; + } + + protected override bool OnHover(HoverEvent e) + { + hover.FadeTo(0.05f, transition_duration, Easing.OutQuint); + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + hover.FadeOut(transition_duration, Easing.OutQuint); + base.OnHoverLost(e); + } + + protected override void OnActivated() + { + selection.FadeIn(transition_duration, Easing.OutQuint); + } + + protected override void OnDeactivated() + { + selection.FadeOut(transition_duration, Easing.OutQuint); + } + } + } +} diff --git a/osu.Game/Screens/Multi/Screens/Match/Settings/RoomAvailabilityPicker.cs b/osu.Game/Screens/Multi/Screens/Match/Settings/RoomAvailabilityPicker.cs new file mode 100644 index 0000000000..251bd062ec --- /dev/null +++ b/osu.Game/Screens/Multi/Screens/Match/Settings/RoomAvailabilityPicker.cs @@ -0,0 +1,105 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Allocation; +using osu.Framework.Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.UserInterface; +using osu.Framework.Input.Events; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Online.Multiplayer; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Screens.Multi.Screens.Match.Settings +{ + public class RoomAvailabilityPicker : TabControl + { + protected override TabItem CreateTabItem(RoomAvailability value) => new RoomAvailabilityPickerItem(value); + protected override Dropdown CreateDropdown() => null; + + public RoomAvailabilityPicker() + { + RelativeSizeAxes = Axes.X; + Height = 35; + + TabContainer.Spacing = new Vector2(10); + + AddItem(RoomAvailability.Public); + AddItem(RoomAvailability.FriendsOnly); + AddItem(RoomAvailability.InviteOnly); + } + + private class RoomAvailabilityPickerItem : TabItem + { + private const float transition_duration = 200; + + private readonly Box hover, selection; + + public RoomAvailabilityPickerItem(RoomAvailability value) : base(value) + { + RelativeSizeAxes = Axes.Y; + Width = 120; + Masking = true; + CornerRadius = 5; + + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = OsuColour.FromHex(@"3d3943"), + }, + selection = new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0, + }, + hover = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.White, + Alpha = 0, + }, + new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Font = @"Exo2.0-Bold", + Text = value.GetDescription(), + }, + }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + selection.Colour = colours.GreenLight; + } + + protected override bool OnHover(HoverEvent e) + { + hover.FadeTo(0.05f, transition_duration, Easing.OutQuint); + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + hover.FadeOut(transition_duration, Easing.OutQuint); + base.OnHoverLost(e); + } + + protected override void OnActivated() + { + selection.FadeIn(transition_duration, Easing.OutQuint); + } + + protected override void OnDeactivated() + { + selection.FadeOut(transition_duration, Easing.OutQuint); + } + } + } +} diff --git a/osu.Game/Screens/Multi/Screens/Match/Settings/RoomSettingsOverlay.cs b/osu.Game/Screens/Multi/Screens/Match/Settings/RoomSettingsOverlay.cs new file mode 100644 index 0000000000..d45ba48f0e --- /dev/null +++ b/osu.Game/Screens/Multi/Screens/Match/Settings/RoomSettingsOverlay.cs @@ -0,0 +1,270 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Allocation; +using osu.Framework.Configuration; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osu.Game.Online.Multiplayer; +using osu.Game.Overlays.SearchableList; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Screens.Multi.Screens.Match.Settings +{ + public class RoomSettingsOverlay : FocusedOverlayContainer + { + private const float transition_duration = 350; + private const float field_padding = 45; + + private readonly Bindable nameBind = new Bindable(); + private readonly Bindable availabilityBind = new Bindable(); + private readonly Bindable typeBind = new Bindable(); + private readonly Bindable maxParticipantsBind = new Bindable(); + + private readonly Container content; + private readonly OsuSpriteText typeLabel; + + protected readonly OsuTextBox NameField, MaxParticipantsField; + protected readonly RoomAvailabilityPicker AvailabilityPicker; + protected readonly GameTypePicker TypePicker; + protected readonly TriangleButton ApplyButton; + + public RoomSettingsOverlay(Room room) + { + Masking = true; + + Child = content = new Container + { + RelativeSizeAxes = Axes.Both, + RelativePositionAxes = Axes.Y, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = OsuColour.FromHex(@"28242d"), + }, + new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Top = 35, Bottom = 75, Horizontal = SearchableListOverlay.WIDTH_PADDING }, + Children = new[] + { + new SectionContainer + { + Padding = new MarginPadding { Right = field_padding / 2 }, + Children = new[] + { + new Section("ROOM NAME") + { + Child = NameField = new SettingsTextBox + { + RelativeSizeAxes = Axes.X, + TabbableContentContainer = this, + OnCommit = (sender, text) => apply(), + }, + }, + new Section("ROOM VISIBILITY") + { + Child = AvailabilityPicker = new RoomAvailabilityPicker(), + }, + new Section("GAME TYPE") + { + Child = new FillFlowContainer + { + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, + Direction = FillDirection.Vertical, + Spacing = new Vector2(7), + Children = new Drawable[] + { + TypePicker = new GameTypePicker + { + RelativeSizeAxes = Axes.X, + }, + typeLabel = new OsuSpriteText + { + TextSize = 14, + }, + }, + }, + }, + }, + }, + new SectionContainer + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + Padding = new MarginPadding { Left = field_padding / 2 }, + Children = new[] + { + new Section("MAX PARTICIPANTS") + { + Child = MaxParticipantsField = new SettingsNumberTextBox + { + RelativeSizeAxes = Axes.X, + TabbableContentContainer = this, + OnCommit = (sender, text) => apply(), + }, + }, + new Section("PASSWORD (OPTIONAL)") + { + Child = new SettingsPasswordTextBox + { + RelativeSizeAxes = Axes.X, + TabbableContentContainer = this, + OnCommit = (sender, text) => apply(), + }, + }, + }, + }, + }, + }, + ApplyButton = new ApplySettingsButton + { + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + Size = new Vector2(230, 35), + Margin = new MarginPadding { Bottom = 20 }, + Action = apply, + }, + }, + }; + + TypePicker.Current.ValueChanged += t => typeLabel.Text = t.Name; + + nameBind.ValueChanged += n => NameField.Text = n; + availabilityBind.ValueChanged += a => AvailabilityPicker.Current.Value = a; + typeBind.ValueChanged += t => TypePicker.Current.Value = t; + maxParticipantsBind.ValueChanged += m => MaxParticipantsField.Text = m?.ToString(); + + nameBind.BindTo(room.Name); + availabilityBind.BindTo(room.Availability); + typeBind.BindTo(room.Type); + maxParticipantsBind.BindTo(room.MaxParticipants); + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + typeLabel.Colour = colours.Yellow; + } + + protected override void PopIn() + { + // reapply the rooms values if the overlay was completely closed + if (content.Y == -1) + { + nameBind.TriggerChange(); + availabilityBind.TriggerChange(); + typeBind.TriggerChange(); + maxParticipantsBind.TriggerChange(); + } + + content.MoveToY(0, transition_duration, Easing.OutQuint); + } + + protected override void PopOut() + { + content.MoveToY(-1, transition_duration, Easing.InSine); + } + + private void apply() + { + nameBind.Value = NameField.Text; + availabilityBind.Value = AvailabilityPicker.Current.Value; + typeBind.Value = TypePicker.Current.Value; + + if (int.TryParse(MaxParticipantsField.Text, out int max)) + maxParticipantsBind.Value = max; + else + maxParticipantsBind.Value = null; + + Hide(); + } + + private class SettingsTextBox : OsuTextBox + { + protected override Color4 BackgroundUnfocused => Color4.Black; + protected override Color4 BackgroundFocused => Color4.Black; + } + + private class SettingsNumberTextBox : SettingsTextBox + { + protected override bool CanAddCharacter(char character) => char.IsNumber(character); + } + + private class SettingsPasswordTextBox : OsuPasswordTextBox + { + protected override Color4 BackgroundUnfocused => Color4.Black; + protected override Color4 BackgroundFocused => Color4.Black; + } + + private class SectionContainer : FillFlowContainer
+ { + public SectionContainer() + { + RelativeSizeAxes = Axes.Both; + Width = 0.5f; + Direction = FillDirection.Vertical; + Spacing = new Vector2(field_padding); + } + } + + private class Section : Container + { + private readonly Container content; + + protected override Container Content => content; + + public Section(string title) + { + AutoSizeAxes = Axes.Y; + RelativeSizeAxes = Axes.X; + + InternalChild = new FillFlowContainer + { + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, + Direction = FillDirection.Vertical, + Spacing = new Vector2(5), + Children = new Drawable[] + { + new OsuSpriteText + { + TextSize = 12, + Font = @"Exo2.0-Bold", + Text = title.ToUpper(), + }, + content = new Container + { + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, + }, + }, + }; + } + } + + private class ApplySettingsButton : TriangleButton + { + public ApplySettingsButton() + { + Text = "Apply"; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + BackgroundColour = colours.Yellow; + Triangles.ColourLight = colours.YellowLight; + Triangles.ColourDark = colours.YellowDark; + } + } + } +} diff --git a/osu.Game/Screens/OsuScreen.cs b/osu.Game/Screens/OsuScreen.cs index 4b893f0118..69f2b6ef9d 100644 --- a/osu.Game/Screens/OsuScreen.cs +++ b/osu.Game/Screens/OsuScreen.cs @@ -15,7 +15,7 @@ using osu.Game.Graphics.Containers; using osu.Game.Input.Bindings; using osu.Game.Rulesets; using osu.Game.Screens.Menu; -using OpenTK; +using osuTK; using osu.Game.Overlays; using osu.Framework.Graphics.Containers; @@ -34,6 +34,8 @@ namespace osu.Game.Screens protected virtual bool AllowBackButton => true; + public virtual bool AllowExternalScreenChange => false; + /// /// Override to create a BackgroundMode for the current screen. /// Note that the instance created may not be the used instance if it matches the BackgroundMode equality clause. diff --git a/osu.Game/Screens/Play/Break/BlurredIcon.cs b/osu.Game/Screens/Play/Break/BlurredIcon.cs index 7db578e846..6d14c94f13 100644 --- a/osu.Game/Screens/Play/Break/BlurredIcon.cs +++ b/osu.Game/Screens/Play/Break/BlurredIcon.cs @@ -5,7 +5,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Graphics; -using OpenTK; +using osuTK; namespace osu.Game.Screens.Play.Break { diff --git a/osu.Game/Screens/Play/Break/BreakArrows.cs b/osu.Game/Screens/Play/Break/BreakArrows.cs index 1382aa9d9d..d5b710a29e 100644 --- a/osu.Game/Screens/Play/Break/BreakArrows.cs +++ b/osu.Game/Screens/Play/Break/BreakArrows.cs @@ -4,7 +4,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Graphics.Containers; -using OpenTK; +using osuTK; namespace osu.Game.Screens.Play.Break { diff --git a/osu.Game/Screens/Play/Break/BreakInfo.cs b/osu.Game/Screens/Play/Break/BreakInfo.cs index 51d39762d2..22e19ac8e2 100644 --- a/osu.Game/Screens/Play/Break/BreakInfo.cs +++ b/osu.Game/Screens/Play/Break/BreakInfo.cs @@ -4,8 +4,8 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Graphics.Sprites; -using osu.Game.Rulesets.Scoring; -using OpenTK; +using osu.Game.Scoring; +using osuTK; namespace osu.Game.Screens.Play.Break { diff --git a/osu.Game/Screens/Play/Break/GlowIcon.cs b/osu.Game/Screens/Play/Break/GlowIcon.cs index 281a9dd27d..d412f3e103 100644 --- a/osu.Game/Screens/Play/Break/GlowIcon.cs +++ b/osu.Game/Screens/Play/Break/GlowIcon.cs @@ -5,7 +5,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Graphics; -using OpenTK; +using osuTK; namespace osu.Game.Screens.Play.Break { diff --git a/osu.Game/Screens/Play/Break/LetterboxOverlay.cs b/osu.Game/Screens/Play/Break/LetterboxOverlay.cs index 001f470078..8bfa0e632a 100644 --- a/osu.Game/Screens/Play/Break/LetterboxOverlay.cs +++ b/osu.Game/Screens/Play/Break/LetterboxOverlay.cs @@ -5,7 +5,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using OpenTK.Graphics; +using osuTK.Graphics; namespace osu.Game.Screens.Play.Break { diff --git a/osu.Game/Screens/Play/FailOverlay.cs b/osu.Game/Screens/Play/FailOverlay.cs index bbed0c8843..947fa1734a 100644 --- a/osu.Game/Screens/Play/FailOverlay.cs +++ b/osu.Game/Screens/Play/FailOverlay.cs @@ -2,7 +2,7 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using osu.Game.Graphics; -using OpenTK.Graphics; +using osuTK.Graphics; using osu.Framework.Allocation; namespace osu.Game.Screens.Play diff --git a/osu.Game/Screens/Play/GameplayMenuOverlay.cs b/osu.Game/Screens/Play/GameplayMenuOverlay.cs index 2c984e6135..d934759474 100644 --- a/osu.Game/Screens/Play/GameplayMenuOverlay.cs +++ b/osu.Game/Screens/Play/GameplayMenuOverlay.cs @@ -6,13 +6,13 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Graphics.Sprites; -using OpenTK; -using OpenTK.Graphics; +using osuTK; +using osuTK.Graphics; using osu.Game.Graphics; using osu.Framework.Allocation; using osu.Game.Graphics.UserInterface; using osu.Framework.Graphics.Shapes; -using OpenTK.Input; +using osuTK.Input; using System.Collections.Generic; using System.Linq; using osu.Framework.Input.Bindings; diff --git a/osu.Game/Screens/Play/HUD/QuitButton.cs b/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs similarity index 88% rename from osu.Game/Screens/Play/HUD/QuitButton.cs rename to osu.Game/Screens/Play/HUD/HoldForMenuButton.cs index 88547e0169..9625c4338b 100644 --- a/osu.Game/Screens/Play/HUD/QuitButton.cs +++ b/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs @@ -8,16 +8,18 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.UserInterface; +using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Framework.MathUtils; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; -using OpenTK; +using osu.Game.Input.Bindings; +using osuTK; namespace osu.Game.Screens.Play.HUD { - public class QuitButton : FillFlowContainer + public class HoldForMenuButton : FillFlowContainer { public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; @@ -30,7 +32,7 @@ namespace osu.Game.Screens.Play.HUD private readonly OsuSpriteText text; - public QuitButton() + public HoldForMenuButton() { Direction = FillDirection.Horizontal; Spacing = new Vector2(20, 0); @@ -75,11 +77,11 @@ namespace osu.Game.Screens.Play.HUD Alpha = 1; else Alpha = Interpolation.ValueAt( - MathHelper.Clamp(Clock.ElapsedFrameTime, 0, 1000), + MathHelper.Clamp(Clock.ElapsedFrameTime, 0, 200), Alpha, MathHelper.Clamp(1 - positionalAdjust, 0.04f, 1), 0, 200, Easing.OutQuint); } - private class Button : HoldToConfirmContainer + private class Button : HoldToConfirmContainer, IKeyBindingHandler { private SpriteIcon icon; private CircularProgress circularProgress; @@ -90,6 +92,30 @@ namespace osu.Game.Screens.Play.HUD public Action HoverGained; public Action HoverLost; + public bool OnPressed(GlobalAction action) + { + switch (action) + { + case GlobalAction.Back: + BeginConfirm(); + return true; + } + + return false; + } + + public bool OnReleased(GlobalAction action) + { + switch (action) + { + case GlobalAction.Back: + AbortConfirm(); + return true; + } + + return false; + } + [BackgroundDependencyLoader] private void load(OsuColour colours) { diff --git a/osu.Game/Screens/Play/HUD/ModDisplay.cs b/osu.Game/Screens/Play/HUD/ModDisplay.cs index 04f086282e..9509c7acae 100644 --- a/osu.Game/Screens/Play/HUD/ModDisplay.cs +++ b/osu.Game/Screens/Play/HUD/ModDisplay.cs @@ -9,7 +9,7 @@ using osu.Framework.Graphics.UserInterface; using osu.Game.Graphics.Sprites; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.UI; -using OpenTK; +using osuTK; using osu.Game.Graphics.Containers; using System.Linq; using osu.Framework.Input.Events; diff --git a/osu.Game/Screens/Play/HUD/PlayerSettingsOverlay.cs b/osu.Game/Screens/Play/HUD/PlayerSettingsOverlay.cs index debce8c680..2249e743e6 100644 --- a/osu.Game/Screens/Play/HUD/PlayerSettingsOverlay.cs +++ b/osu.Game/Screens/Play/HUD/PlayerSettingsOverlay.cs @@ -4,9 +4,9 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input.Events; -using OpenTK; +using osuTK; using osu.Game.Screens.Play.PlayerSettings; -using OpenTK.Input; +using osuTK.Input; namespace osu.Game.Screens.Play.HUD { diff --git a/osu.Game/Screens/Play/HUD/StandardComboCounter.cs b/osu.Game/Screens/Play/HUD/StandardComboCounter.cs index 911d201061..3ca4476969 100644 --- a/osu.Game/Screens/Play/HUD/StandardComboCounter.cs +++ b/osu.Game/Screens/Play/HUD/StandardComboCounter.cs @@ -1,7 +1,7 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using OpenTK; +using osuTK; using osu.Framework.Graphics; namespace osu.Game.Screens.Play.HUD diff --git a/osu.Game/Screens/Play/HUD/StandardHealthDisplay.cs b/osu.Game/Screens/Play/HUD/StandardHealthDisplay.cs index b551b1f7a6..9eedde5269 100644 --- a/osu.Game/Screens/Play/HUD/StandardHealthDisplay.cs +++ b/osu.Game/Screens/Play/HUD/StandardHealthDisplay.cs @@ -2,13 +2,14 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System; +using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Graphics; using osu.Game.Rulesets.Judgements; -using OpenTK; -using OpenTK.Graphics; +using osuTK; +using osuTK.Graphics; using osu.Framework.Graphics.Shapes; using osu.Game.Rulesets.Scoring; @@ -92,6 +93,13 @@ namespace osu.Game.Screens.Play.HUD }; } + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + AccentColour = colours.BlueLighter; + GlowColour = colours.BlueDarker; + } + public void Flash(JudgementResult result) { if (result.Type == HitResult.Miss) diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index db0d7b6ccc..11cee98bdf 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -9,15 +9,14 @@ using osu.Framework.Input.Events; using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Configuration; -using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; using osu.Game.Overlays; using osu.Game.Overlays.Notifications; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; using osu.Game.Screens.Play.HUD; -using OpenTK; -using OpenTK.Input; +using osuTK; +using osuTK.Input; namespace osu.Game.Screens.Play { @@ -34,7 +33,7 @@ namespace osu.Game.Screens.Play public readonly HealthDisplay HealthDisplay; public readonly SongProgress Progress; public readonly ModDisplay ModDisplay; - public readonly QuitButton HoldToQuit; + public readonly HoldForMenuButton HoldToQuit; public readonly PlayerSettingsOverlay PlayerSettingsOverlay; private Bindable showHud; @@ -49,7 +48,7 @@ namespace osu.Game.Screens.Play Add(content = new Container { RelativeSizeAxes = Axes.Both, - + AlwaysPresent = true, // The hud may be hidden but certain elements may need to still be updated Children = new Drawable[] { ComboCounter = CreateComboCounter(), @@ -69,7 +68,7 @@ namespace osu.Game.Screens.Play Children = new Drawable[] { KeyCounter = CreateKeyCounter(adjustableClock as IFrameBasedClock), - HoldToQuit = CreateQuitButton(), + HoldToQuit = CreateHoldForMenuButton(), } } } @@ -89,7 +88,7 @@ namespace osu.Game.Screens.Play } [BackgroundDependencyLoader(true)] - private void load(OsuConfigManager config, NotificationOverlay notificationOverlay, OsuColour colours) + private void load(OsuConfigManager config, NotificationOverlay notificationOverlay) { showHud = config.GetBindable(OsuSetting.ShowInterface); showHud.ValueChanged += hudVisibility => content.FadeTo(hudVisibility ? 1 : 0, duration); @@ -104,18 +103,6 @@ namespace osu.Game.Screens.Play Text = @"The score overlay is currently disabled. You can toggle this by pressing Shift+Tab." }); } - - // todo: the stuff below should probably not be in this base implementation, but in each individual class. - ComboCounter.AccentColour = colours.BlueLighter; - AccuracyCounter.AccentColour = colours.BlueLighter; - ScoreCounter.AccentColour = colours.BlueLighter; - - var shd = HealthDisplay as StandardHealthDisplay; - if (shd != null) - { - shd.AccentColour = colours.BlueLighter; - shd.GlowColour = colours.BlueDarker; - } } protected override void LoadComplete() @@ -134,11 +121,13 @@ namespace osu.Game.Screens.Play { PlayerSettingsOverlay.Show(); ModDisplay.FadeIn(200); + KeyCounter.Margin = new MarginPadding(10) { Bottom = 30 }; } else { PlayerSettingsOverlay.Hide(); ModDisplay.Delay(2000).FadeOut(200); + KeyCounter.Margin = new MarginPadding(10); } } @@ -217,7 +206,7 @@ namespace osu.Game.Screens.Play RelativeSizeAxes = Axes.X, }; - protected virtual QuitButton CreateQuitButton() => new QuitButton + protected virtual HoldForMenuButton CreateHoldForMenuButton() => new HoldForMenuButton { Anchor = Anchor.BottomRight, Origin = Anchor.BottomRight, diff --git a/osu.Game/Screens/Play/KeyCounter.cs b/osu.Game/Screens/Play/KeyCounter.cs index d1efe4cab7..b494b84ee4 100644 --- a/osu.Game/Screens/Play/KeyCounter.cs +++ b/osu.Game/Screens/Play/KeyCounter.cs @@ -9,8 +9,8 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; using osu.Game.Graphics.Sprites; -using OpenTK; -using OpenTK.Graphics; +using osuTK; +using osuTK.Graphics; namespace osu.Game.Screens.Play { diff --git a/osu.Game/Screens/Play/KeyCounterCollection.cs b/osu.Game/Screens/Play/KeyCounterCollection.cs index 925f96f33b..ce0e98d032 100644 --- a/osu.Game/Screens/Play/KeyCounterCollection.cs +++ b/osu.Game/Screens/Play/KeyCounterCollection.cs @@ -10,8 +10,8 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Input.Events; using osu.Framework.Timing; using osu.Game.Configuration; -using OpenTK; -using OpenTK.Graphics; +using osuTK; +using osuTK.Graphics; namespace osu.Game.Screens.Play { diff --git a/osu.Game/Screens/Play/KeyCounterKeyboard.cs b/osu.Game/Screens/Play/KeyCounterKeyboard.cs index 725eae0367..521f7d4e84 100644 --- a/osu.Game/Screens/Play/KeyCounterKeyboard.cs +++ b/osu.Game/Screens/Play/KeyCounterKeyboard.cs @@ -2,7 +2,7 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using osu.Framework.Input.Events; -using OpenTK.Input; +using osuTK.Input; namespace osu.Game.Screens.Play { diff --git a/osu.Game/Screens/Play/KeyCounterMouse.cs b/osu.Game/Screens/Play/KeyCounterMouse.cs index c1ed5f84f4..3f7aaa6076 100644 --- a/osu.Game/Screens/Play/KeyCounterMouse.cs +++ b/osu.Game/Screens/Play/KeyCounterMouse.cs @@ -2,8 +2,8 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using osu.Framework.Input.Events; -using OpenTK.Input; -using OpenTK; +using osuTK.Input; +using osuTK; namespace osu.Game.Screens.Play { diff --git a/osu.Game/Screens/Play/PauseContainer.cs b/osu.Game/Screens/Play/PauseContainer.cs index 25e701294a..19a05e8eed 100644 --- a/osu.Game/Screens/Play/PauseContainer.cs +++ b/osu.Game/Screens/Play/PauseContainer.cs @@ -9,7 +9,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Timing; using osu.Game.Graphics; -using OpenTK.Graphics; +using osuTK.Graphics; namespace osu.Game.Screens.Play { diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 566cd80c0c..611341eae0 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -27,6 +27,7 @@ using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; +using osu.Game.Scoring; using osu.Game.Screens.Ranking; using osu.Game.Skinning; using osu.Game.Storyboards.Drawables; @@ -35,6 +36,8 @@ namespace osu.Game.Screens.Play { public class Player : ScreenWithBeatmapBackground, IProvideCursor { + protected override bool AllowBackButton => false; // handled by HoldForMenuButton + protected override float BackgroundParallaxAmount => 0.1f; protected override bool HideOverlaysOnEnter => true; @@ -64,6 +67,9 @@ namespace osu.Game.Screens.Play /// private DecoupleableInterpolatingFramedClock adjustableClock; + [Resolved] + private ScoreManager scoreManager { get; set; } + private PauseContainer pauseContainer; private RulesetInfo ruleset; @@ -271,13 +277,10 @@ namespace osu.Game.Screens.Play { if (!IsCurrentScreen) return; - var score = new Score - { - Beatmap = Beatmap.Value.BeatmapInfo, - Ruleset = ruleset - }; - ScoreProcessor.PopulateScore(score); - score.User = RulesetContainer.Replay?.User ?? api.LocalUser.Value; + var score = CreateScore(); + if (RulesetContainer.Replay == null) + scoreManager.Import(score, true); + Push(new Results(score)); onCompletionEvent = null; @@ -285,6 +288,20 @@ namespace osu.Game.Screens.Play } } + protected virtual ScoreInfo CreateScore() + { + var score = new ScoreInfo + { + Beatmap = Beatmap.Value.BeatmapInfo, + Ruleset = ruleset, + User = api.LocalUser.Value + }; + + ScoreProcessor.PopulateScore(score); + + return score; + } + private bool onFail() { if (Beatmap.Value.Mods.Value.OfType().Any(m => !m.AllowFail)) diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index d87fb1db86..bf44a60473 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -17,8 +17,8 @@ using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Screens.Menu; using osu.Game.Screens.Play.PlayerSettings; -using OpenTK; -using OpenTK.Graphics; +using osuTK; +using osuTK.Graphics; namespace osu.Game.Screens.Play { diff --git a/osu.Game/Screens/Play/PlayerSettings/CollectionSettings.cs b/osu.Game/Screens/Play/PlayerSettings/CollectionSettings.cs index 9a654d3bfd..da47de8a9b 100644 --- a/osu.Game/Screens/Play/PlayerSettings/CollectionSettings.cs +++ b/osu.Game/Screens/Play/PlayerSettings/CollectionSettings.cs @@ -1,7 +1,6 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Game.Graphics.Sprites; @@ -25,7 +24,7 @@ namespace osu.Game.Screens.Play.PlayerSettings new CollectionsDropdown { RelativeSizeAxes = Axes.X, - Items = new[] { new KeyValuePair(@"All", PlaylistCollection.All) }, + Items = new[] { PlaylistCollection.All }, }, }; } diff --git a/osu.Game/Screens/Play/PlayerSettings/PlayerSettingsGroup.cs b/osu.Game/Screens/Play/PlayerSettings/PlayerSettingsGroup.cs index 6e317ccfc9..833a22556e 100644 --- a/osu.Game/Screens/Play/PlayerSettings/PlayerSettingsGroup.cs +++ b/osu.Game/Screens/Play/PlayerSettings/PlayerSettingsGroup.cs @@ -9,8 +9,8 @@ using osu.Framework.Input.Events; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; -using OpenTK; -using OpenTK.Graphics; +using osuTK; +using osuTK.Graphics; namespace osu.Game.Screens.Play.PlayerSettings { diff --git a/osu.Game/Screens/Play/ReplayPlayer.cs b/osu.Game/Screens/Play/ReplayPlayer.cs index 6e2a2e4c9c..fe77fd57f2 100644 --- a/osu.Game/Screens/Play/ReplayPlayer.cs +++ b/osu.Game/Screens/Play/ReplayPlayer.cs @@ -1,23 +1,25 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using osu.Game.Rulesets.Replays; +using osu.Game.Scoring; namespace osu.Game.Screens.Play { public class ReplayPlayer : Player { - public Replay Replay; + private readonly Score score; - public ReplayPlayer(Replay replay) + public ReplayPlayer(Score score) { - Replay = replay; + this.score = score; } protected override void LoadComplete() { base.LoadComplete(); - RulesetContainer.SetReplay(Replay); + RulesetContainer.SetReplay(score.Replay); } + + protected override ScoreInfo CreateScore() => score.ScoreInfo; } } diff --git a/osu.Game/Screens/Play/ScreenWithBeatmapBackground.cs b/osu.Game/Screens/Play/ScreenWithBeatmapBackground.cs index 26d3218fbf..3846b45d2f 100644 --- a/osu.Game/Screens/Play/ScreenWithBeatmapBackground.cs +++ b/osu.Game/Screens/Play/ScreenWithBeatmapBackground.cs @@ -7,7 +7,7 @@ using osu.Framework.Graphics; using osu.Framework.Screens; using osu.Game.Configuration; using osu.Game.Screens.Backgrounds; -using OpenTK; +using osuTK; namespace osu.Game.Screens.Play { diff --git a/osu.Game/Screens/Play/SkipOverlay.cs b/osu.Game/Screens/Play/SkipOverlay.cs index cd34623951..e5a6dd2db1 100644 --- a/osu.Game/Screens/Play/SkipOverlay.cs +++ b/osu.Game/Screens/Play/SkipOverlay.cs @@ -13,8 +13,8 @@ using osu.Framework.Timing; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Screens.Ranking; -using OpenTK; -using OpenTK.Graphics; +using osuTK; +using osuTK.Graphics; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics.Containers; using osu.Framework.Input.Bindings; diff --git a/osu.Game/Screens/Play/SongProgress.cs b/osu.Game/Screens/Play/SongProgress.cs index 2e2c77c1c8..336f9cc430 100644 --- a/osu.Game/Screens/Play/SongProgress.cs +++ b/osu.Game/Screens/Play/SongProgress.cs @@ -1,7 +1,7 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using OpenTK; +using osuTK; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using System; @@ -21,7 +21,7 @@ namespace osu.Game.Screens.Play { private const int bottom_bar_height = 5; - private static readonly Vector2 handle_size = new Vector2(14, 25); + private static readonly Vector2 handle_size = new Vector2(10, 18); private const float transition_duration = 200; @@ -135,6 +135,8 @@ namespace osu.Game.Screens.Play { bar.FadeTo(allowSeeking ? 1 : 0, transition_duration, Easing.In); this.MoveTo(new Vector2(0, allowSeeking ? 0 : bottom_bar_height), transition_duration, Easing.In); + + info.Margin = new MarginPadding { Bottom = Height - (allowSeeking ? 0 : handle_size.Y) }; } protected override void PopIn() diff --git a/osu.Game/Screens/Play/SongProgressBar.cs b/osu.Game/Screens/Play/SongProgressBar.cs index 2367049b6e..1f0c4936a5 100644 --- a/osu.Game/Screens/Play/SongProgressBar.cs +++ b/osu.Game/Screens/Play/SongProgressBar.cs @@ -2,8 +2,8 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System; -using OpenTK; -using OpenTK.Graphics; +using osuTK; +using osuTK.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics; using osu.Framework.Graphics.Shapes; diff --git a/osu.Game/Screens/Play/SquareGraph.cs b/osu.Game/Screens/Play/SquareGraph.cs index bc4c87e191..c0ee0079b9 100644 --- a/osu.Game/Screens/Play/SquareGraph.cs +++ b/osu.Game/Screens/Play/SquareGraph.cs @@ -9,8 +9,8 @@ using osu.Framework.Caching; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using OpenTK; -using OpenTK.Graphics; +using osuTK; +using osuTK.Graphics; using osu.Framework.Graphics.Shapes; namespace osu.Game.Screens.Play diff --git a/osu.Game/Screens/Ranking/ResultModeButton.cs b/osu.Game/Screens/Ranking/ResultModeButton.cs index 70a177ac4b..5df3ff3afb 100644 --- a/osu.Game/Screens/Ranking/ResultModeButton.cs +++ b/osu.Game/Screens/Ranking/ResultModeButton.cs @@ -7,8 +7,8 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.UserInterface; using osu.Game.Graphics; -using OpenTK; -using OpenTK.Graphics; +using osuTK; +using osuTK.Graphics; using osu.Framework.Graphics.Shapes; namespace osu.Game.Screens.Ranking diff --git a/osu.Game/Screens/Ranking/ResultModeTabControl.cs b/osu.Game/Screens/Ranking/ResultModeTabControl.cs index 184e6cddee..e5c6115085 100644 --- a/osu.Game/Screens/Ranking/ResultModeTabControl.cs +++ b/osu.Game/Screens/Ranking/ResultModeTabControl.cs @@ -3,7 +3,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.UserInterface; -using OpenTK; +using osuTK; namespace osu.Game.Screens.Ranking { diff --git a/osu.Game/Screens/Ranking/Results.cs b/osu.Game/Screens/Ranking/Results.cs index 56a270f559..1ff5fc7bfd 100644 --- a/osu.Game/Screens/Ranking/Results.cs +++ b/osu.Game/Screens/Ranking/Results.cs @@ -7,23 +7,23 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Game.Rulesets.Scoring; using osu.Framework.Graphics.Sprites; using osu.Framework.Screens; using osu.Game.Graphics.Containers; using osu.Game.Screens.Backgrounds; -using OpenTK; -using OpenTK.Graphics; +using osuTK; +using osuTK.Graphics; using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics.Sprites; +using osu.Game.Scoring; namespace osu.Game.Screens.Ranking { public class Results : OsuScreen { - private readonly Score score; + private readonly ScoreInfo score; private Container circleOuterBackground; private Container circleOuter; private Container circleInner; @@ -44,7 +44,7 @@ namespace osu.Game.Screens.Ranking private const float circle_outer_scale = 0.96f; - public Results(Score score) + public Results(ScoreInfo score) { this.score = score; } diff --git a/osu.Game/Screens/Ranking/ResultsPage.cs b/osu.Game/Screens/Ranking/ResultsPage.cs index d3b2b5c8a0..5f68623e54 100644 --- a/osu.Game/Screens/Ranking/ResultsPage.cs +++ b/osu.Game/Screens/Ranking/ResultsPage.cs @@ -8,22 +8,22 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Game.Beatmaps; using osu.Game.Graphics; -using osu.Game.Rulesets.Scoring; -using OpenTK; -using OpenTK.Graphics; +using osu.Game.Scoring; +using osuTK; +using osuTK.Graphics; namespace osu.Game.Screens.Ranking { public class ResultsPage : Container { - protected readonly Score Score; + protected readonly ScoreInfo Score; protected readonly WorkingBeatmap Beatmap; private CircularContainer content; private Box fill; protected override Container Content => content; - public ResultsPage(Score score, WorkingBeatmap beatmap) + public ResultsPage(ScoreInfo score, WorkingBeatmap beatmap) { Score = score; Beatmap = beatmap; diff --git a/osu.Game/Screens/Ranking/ResultsPageRanking.cs b/osu.Game/Screens/Ranking/ResultsPageRanking.cs index 73ad5ba30c..c5a5cc6ad9 100644 --- a/osu.Game/Screens/Ranking/ResultsPageRanking.cs +++ b/osu.Game/Screens/Ranking/ResultsPageRanking.cs @@ -5,16 +5,16 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Game.Beatmaps; using osu.Game.Graphics; -using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Select.Leaderboards; -using OpenTK; +using osuTK; using osu.Framework.Graphics.Shapes; +using osu.Game.Scoring; namespace osu.Game.Screens.Ranking { public class ResultsPageRanking : ResultsPage { - public ResultsPageRanking(Score score, WorkingBeatmap beatmap = null) : base(score, beatmap) + public ResultsPageRanking(ScoreInfo score, WorkingBeatmap beatmap = null) : base(score, beatmap) { } diff --git a/osu.Game/Screens/Ranking/ResultsPageScore.cs b/osu.Game/Screens/Ranking/ResultsPageScore.cs index 58c9cde9c7..62103314e1 100644 --- a/osu.Game/Screens/Ranking/ResultsPageScore.cs +++ b/osu.Game/Screens/Ranking/ResultsPageScore.cs @@ -4,8 +4,8 @@ using System; using System.Collections.Generic; using System.Linq; -using OpenTK; -using OpenTK.Graphics; +using osuTK; +using osuTK.Graphics; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; @@ -24,6 +24,7 @@ using osu.Game.Users; using osu.Framework.Graphics.Shapes; using osu.Framework.Extensions; using osu.Framework.Localisation; +using osu.Game.Scoring; namespace osu.Game.Screens.Ranking { @@ -32,7 +33,7 @@ namespace osu.Game.Screens.Ranking private Container scoreContainer; private ScoreCounter scoreCounter; - public ResultsPageScore(Score score, WorkingBeatmap beatmap) : base(score, beatmap) { } + public ResultsPageScore(ScoreInfo score, WorkingBeatmap beatmap) : base(score, beatmap) { } private FillFlowContainer statisticsContainer; @@ -195,9 +196,9 @@ namespace osu.Game.Screens.Ranking private class DrawableScoreStatistic : Container { - private readonly KeyValuePair statistic; + private readonly KeyValuePair statistic; - public DrawableScoreStatistic(KeyValuePair statistic) + public DrawableScoreStatistic(KeyValuePair statistic) { this.statistic = statistic; diff --git a/osu.Game/Screens/ScreenWhiteBox.cs b/osu.Game/Screens/ScreenWhiteBox.cs index 077caebc58..1131152d5f 100644 --- a/osu.Game/Screens/ScreenWhiteBox.cs +++ b/osu.Game/Screens/ScreenWhiteBox.cs @@ -9,8 +9,8 @@ using osu.Framework.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Screens.Backgrounds; using osu.Game.Graphics.UserInterface; -using OpenTK; -using OpenTK.Graphics; +using osuTK; +using osuTK.Graphics; using osu.Game.Graphics; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics.Shapes; diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 7c9053d2f0..348394c1f5 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -1,14 +1,14 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using OpenTK; +using osuTK; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using System; using System.Collections.Generic; using System.Linq; using osu.Game.Configuration; -using OpenTK.Input; +using osuTK.Input; using osu.Framework.MathUtils; using System.Diagnostics; using System.Threading.Tasks; diff --git a/osu.Game/Screens/Select/BeatmapDetailAreaTabControl.cs b/osu.Game/Screens/Select/BeatmapDetailAreaTabControl.cs index 485ca026cf..e18b70a0e0 100644 --- a/osu.Game/Screens/Select/BeatmapDetailAreaTabControl.cs +++ b/osu.Game/Screens/Select/BeatmapDetailAreaTabControl.cs @@ -2,7 +2,7 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System; -using OpenTK.Graphics; +using osuTK.Graphics; using osu.Framework.Allocation; using osu.Framework.Configuration; using osu.Framework.Extensions.Color4Extensions; diff --git a/osu.Game/Screens/Select/BeatmapDetails.cs b/osu.Game/Screens/Select/BeatmapDetails.cs index 3999adfede..9b87bbaa7b 100644 --- a/osu.Game/Screens/Select/BeatmapDetails.cs +++ b/osu.Game/Screens/Select/BeatmapDetails.cs @@ -1,8 +1,8 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using OpenTK; -using OpenTK.Graphics; +using osuTK; +using osuTK.Graphics; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; diff --git a/osu.Game/Screens/Select/BeatmapInfoWedge.cs b/osu.Game/Screens/Select/BeatmapInfoWedge.cs index 81455833e9..c72701d3cb 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedge.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedge.cs @@ -5,8 +5,8 @@ using System; using System.Collections.Generic; using System.Linq; using JetBrains.Annotations; -using OpenTK; -using OpenTK.Graphics; +using osuTK; +using osuTK.Graphics; using osu.Framework.Allocation; using osu.Framework.Configuration; using osu.Framework.Extensions.Color4Extensions; diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs index 109ac25cd1..8216fc0e33 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs @@ -18,8 +18,8 @@ using osu.Game.Graphics.Backgrounds; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Overlays; -using OpenTK; -using OpenTK.Graphics; +using osuTK; +using osuTK.Graphics; namespace osu.Game.Screens.Select.Carousel { diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index ec2622285f..2e5f01fe29 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -18,8 +18,8 @@ using osu.Game.Beatmaps.Drawables; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Overlays; -using OpenTK; -using OpenTK.Graphics; +using osuTK; +using osuTK.Graphics; namespace osu.Game.Screens.Select.Carousel { diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs index e11b342efe..3ec980602d 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs @@ -11,8 +11,8 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; using osu.Framework.MathUtils; using osu.Game.Graphics; -using OpenTK; -using OpenTK.Graphics; +using osuTK; +using osuTK.Graphics; namespace osu.Game.Screens.Select.Carousel { diff --git a/osu.Game/Screens/Select/Details/AdvancedStats.cs b/osu.Game/Screens/Select/Details/AdvancedStats.cs index 852e4f190f..88fd9ca032 100644 --- a/osu.Game/Screens/Select/Details/AdvancedStats.cs +++ b/osu.Game/Screens/Select/Details/AdvancedStats.cs @@ -1,8 +1,8 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using OpenTK; -using OpenTK.Graphics; +using osuTK; +using osuTK.Graphics; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; diff --git a/osu.Game/Screens/Select/Details/FailRetryGraph.cs b/osu.Game/Screens/Select/Details/FailRetryGraph.cs index bf4eb07108..b5a6a26807 100644 --- a/osu.Game/Screens/Select/Details/FailRetryGraph.cs +++ b/osu.Game/Screens/Select/Details/FailRetryGraph.cs @@ -1,7 +1,7 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using OpenTK; +using osuTK; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; diff --git a/osu.Game/Screens/Select/FilterControl.cs b/osu.Game/Screens/Select/FilterControl.cs index fce7af1400..faffdbf31a 100644 --- a/osu.Game/Screens/Select/FilterControl.cs +++ b/osu.Game/Screens/Select/FilterControl.cs @@ -2,8 +2,8 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System; -using OpenTK; -using OpenTK.Graphics; +using osuTK; +using osuTK.Graphics; using osu.Framework.Allocation; using osu.Framework.Configuration; using osu.Framework.Graphics; diff --git a/osu.Game/Screens/Select/Footer.cs b/osu.Game/Screens/Select/Footer.cs index 5fe1aa31ac..c4123884e0 100644 --- a/osu.Game/Screens/Select/Footer.cs +++ b/osu.Game/Screens/Select/Footer.cs @@ -4,9 +4,9 @@ using System; using System.Collections.Generic; using System.Linq; -using OpenTK; -using OpenTK.Graphics; -using OpenTK.Input; +using osuTK; +using osuTK.Graphics; +using osuTK.Input; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; diff --git a/osu.Game/Screens/Select/FooterButton.cs b/osu.Game/Screens/Select/FooterButton.cs index 4f12082e08..3c32c18f05 100644 --- a/osu.Game/Screens/Select/FooterButton.cs +++ b/osu.Game/Screens/Select/FooterButton.cs @@ -2,9 +2,9 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System; -using OpenTK; -using OpenTK.Graphics; -using OpenTK.Input; +using osuTK; +using osuTK.Graphics; +using osuTK.Input; using osu.Framework.Graphics; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; diff --git a/osu.Game/Screens/Select/Leaderboards/DrawableRank.cs b/osu.Game/Screens/Select/Leaderboards/DrawableRank.cs index 0cf1e60304..3258a62adf 100644 --- a/osu.Game/Screens/Select/Leaderboards/DrawableRank.cs +++ b/osu.Game/Screens/Select/Leaderboards/DrawableRank.cs @@ -7,7 +7,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; using osu.Framework.Extensions; -using osu.Game.Rulesets.Scoring; +using osu.Game.Scoring; namespace osu.Game.Screens.Select.Leaderboards { diff --git a/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs b/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs index c171e791d2..a65cc6f096 100644 --- a/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs @@ -3,8 +3,8 @@ using System; using System.Collections.Generic; -using OpenTK; -using OpenTK.Graphics; +using osuTK; +using osuTK.Graphics; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; @@ -14,12 +14,12 @@ using osu.Framework.Threading; using osu.Game.Beatmaps; using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; -using osu.Game.Rulesets.Scoring; using osu.Game.Online.API; using osu.Game.Online.API.Requests; using System.Linq; using osu.Framework.Configuration; using osu.Game.Rulesets; +using osu.Game.Scoring; namespace osu.Game.Screens.Select.Leaderboards { @@ -34,7 +34,7 @@ namespace osu.Game.Screens.Select.Leaderboards private readonly IBindable ruleset = new Bindable(); - public Action ScoreSelected; + public Action ScoreSelected; private readonly LoadingAnimation loading; @@ -42,9 +42,9 @@ namespace osu.Game.Screens.Select.Leaderboards private bool scoresLoadedOnce; - private IEnumerable scores; + private IEnumerable scores; - public IEnumerable Scores + public IEnumerable Scores { get { return scores; } set @@ -179,6 +179,9 @@ namespace osu.Game.Screens.Select.Leaderboards private APIAccess api; private BeatmapInfo beatmap; + [Resolved] + private ScoreManager scoreManager { get; set; } + private ScheduledDelegate pendingUpdateScores; public BeatmapInfo Beatmap @@ -216,6 +219,8 @@ namespace osu.Game.Screens.Select.Leaderboards api.OnStateChange -= handleApiStateChange; } + public void RefreshScores() => updateScores(); + private GetScoresRequest getScoresRequest; private void handleApiStateChange(APIState oldState, APIState newState) @@ -242,8 +247,8 @@ namespace osu.Game.Screens.Select.Leaderboards { if (Scope == LeaderboardScope.Local) { - // TODO: get local scores from wherever here. - PlaceholderState = PlaceholderState.NoScores; + Scores = scoreManager.QueryScores(s => s.Beatmap.ID == Beatmap.ID).ToArray(); + PlaceholderState = Scores.Any() ? PlaceholderState.Successful : PlaceholderState.NoScores; return; } @@ -310,9 +315,9 @@ namespace osu.Game.Screens.Select.Leaderboards currentPlaceholder = placeholder; } - protected override void Update() + protected override void UpdateAfterChildren() { - base.Update(); + base.UpdateAfterChildren(); var fadeStart = scrollContainer.Current + scrollContainer.DrawHeight; diff --git a/osu.Game/Screens/Select/Leaderboards/LeaderboardScore.cs b/osu.Game/Screens/Select/Leaderboards/LeaderboardScore.cs index 241f6da3d1..1ba529c0bf 100644 --- a/osu.Game/Screens/Select/Leaderboards/LeaderboardScore.cs +++ b/osu.Game/Screens/Select/Leaderboards/LeaderboardScore.cs @@ -2,8 +2,8 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System.Linq; -using OpenTK; -using OpenTK.Graphics; +using osuTK; +using osuTK.Graphics; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; @@ -14,8 +14,8 @@ using osu.Framework.Input.Events; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; -using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; +using osu.Game.Scoring; using osu.Game.Users; namespace osu.Game.Screens.Select.Leaderboards @@ -25,7 +25,7 @@ namespace osu.Game.Screens.Select.Leaderboards public static readonly float HEIGHT = 60; public readonly int RankPosition; - public readonly Score Score; + public readonly ScoreInfo Score; private const float corner_radius = 5; private const float edge_margin = 5; @@ -43,7 +43,7 @@ namespace osu.Game.Screens.Select.Leaderboards private Container flagBadgeContainer; private FillFlowContainer modsContainer; - public LeaderboardScore(Score score, int rank) + public LeaderboardScore(ScoreInfo score, int rank) { Score = score; RankPosition = rank; diff --git a/osu.Game/Screens/Select/Leaderboards/RetrievalFailurePlaceholder.cs b/osu.Game/Screens/Select/Leaderboards/RetrievalFailurePlaceholder.cs index 6601c9df8b..66a7793f7c 100644 --- a/osu.Game/Screens/Select/Leaderboards/RetrievalFailurePlaceholder.cs +++ b/osu.Game/Screens/Select/Leaderboards/RetrievalFailurePlaceholder.cs @@ -6,7 +6,7 @@ using osu.Framework.Graphics; using osu.Framework.Input.Events; using osu.Game.Graphics; using osu.Game.Graphics.Containers; -using OpenTK; +using osuTK; namespace osu.Game.Screens.Select.Leaderboards { diff --git a/osu.Game/Screens/Select/Options/BeatmapOptionsButton.cs b/osu.Game/Screens/Select/Options/BeatmapOptionsButton.cs index cd775419be..9659d537f1 100644 --- a/osu.Game/Screens/Select/Options/BeatmapOptionsButton.cs +++ b/osu.Game/Screens/Select/Options/BeatmapOptionsButton.cs @@ -8,9 +8,9 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; -using OpenTK; -using OpenTK.Graphics; -using OpenTK.Input; +using osuTK; +using osuTK.Graphics; +using osuTK.Input; using osu.Game.Graphics.Containers; namespace osu.Game.Screens.Select.Options diff --git a/osu.Game/Screens/Select/Options/BeatmapOptionsOverlay.cs b/osu.Game/Screens/Select/Options/BeatmapOptionsOverlay.cs index 845c9f0a6b..dcadefebae 100644 --- a/osu.Game/Screens/Select/Options/BeatmapOptionsOverlay.cs +++ b/osu.Game/Screens/Select/Options/BeatmapOptionsOverlay.cs @@ -7,9 +7,9 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; -using OpenTK; -using OpenTK.Graphics; -using OpenTK.Input; +using osuTK; +using osuTK.Graphics; +using osuTK.Input; using osu.Game.Graphics.Containers; namespace osu.Game.Screens.Select.Options diff --git a/osu.Game/Screens/Select/PlaySongSelect.cs b/osu.Game/Screens/Select/PlaySongSelect.cs index 285410ce20..0e3dfcf284 100644 --- a/osu.Game/Screens/Select/PlaySongSelect.cs +++ b/osu.Game/Screens/Select/PlaySongSelect.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; using System.Linq; -using OpenTK.Input; +using osuTK.Input; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; @@ -85,6 +85,17 @@ namespace osu.Game.Screens.Select } } + protected override void ExitFromBack() + { + if (modSelect.State == Visibility.Visible) + { + modSelect.Hide(); + return; + } + + base.ExitFromBack(); + } + protected override void UpdateBeatmap(WorkingBeatmap beatmap) { beatmap.Mods.BindTo(selectedMods); @@ -108,6 +119,8 @@ namespace osu.Game.Screens.Select removeAutoModOnResume = false; } + BeatmapDetails.Leaderboard.RefreshScores(); + Beatmap.Value.Track.Looping = true; base.OnResuming(last); @@ -122,12 +135,6 @@ namespace osu.Game.Screens.Select protected override bool OnExiting(Screen next) { - if (modSelect.State == Visibility.Visible) - { - modSelect.Hide(); - return true; - } - if (base.OnExiting(next)) return true; diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 4288483caf..66540c6900 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -3,8 +3,8 @@ using System; using System.Linq; -using OpenTK; -using OpenTK.Input; +using osuTK; +using osuTK.Input; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; @@ -40,6 +40,8 @@ namespace osu.Game.Screens.Select protected virtual bool ShowFooter => true; + public override bool AllowExternalScreenChange => true; + /// /// Can be null if is false. /// @@ -189,13 +191,15 @@ namespace osu.Game.Screens.Select }); Add(Footer = new Footer { - OnBack = Exit, + OnBack = ExitFromBack, }); FooterPanels.Add(BeatmapOptions = new BeatmapOptionsOverlay()); } } + protected virtual void ExitFromBack() => Exit(); + [BackgroundDependencyLoader(true)] private void load(BeatmapManager beatmaps, AudioManager audio, DialogOverlay dialog, OsuColour colours) { @@ -350,7 +354,7 @@ namespace osu.Game.Screens.Select } } - ensurePlayingSelected(preview); + if (IsCurrentScreen) ensurePlayingSelected(preview); UpdateBeatmap(Beatmap.Value); } @@ -503,7 +507,7 @@ namespace osu.Game.Screens.Select } } - private void onBeatmapSetAdded(BeatmapSetInfo s) => Carousel.UpdateBeatmapSet(s); + private void onBeatmapSetAdded(BeatmapSetInfo s, bool existing, bool silent) => Carousel.UpdateBeatmapSet(s); private void onBeatmapSetRemoved(BeatmapSetInfo s) => Carousel.RemoveBeatmapSet(s); private void onBeatmapRestored(BeatmapInfo b) => Carousel.UpdateBeatmapSet(beatmaps.QueryBeatmapSet(s => s.ID == b.BeatmapSetInfoID)); private void onBeatmapHidden(BeatmapInfo b) => Carousel.UpdateBeatmapSet(beatmaps.QueryBeatmapSet(s => s.ID == b.BeatmapSetInfoID)); diff --git a/osu.Game/Screens/Select/WedgeBackground.cs b/osu.Game/Screens/Select/WedgeBackground.cs index 949a3e00f6..6e9ee0704b 100644 --- a/osu.Game/Screens/Select/WedgeBackground.cs +++ b/osu.Game/Screens/Select/WedgeBackground.cs @@ -4,8 +4,8 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using OpenTK; -using OpenTK.Graphics; +using osuTK; +using osuTK.Graphics; using osu.Framework.Graphics.Shapes; namespace osu.Game.Screens.Select diff --git a/osu.Game/Screens/Tournament/Drawings.cs b/osu.Game/Screens/Tournament/Drawings.cs index 4e2109afd0..0a428ea289 100644 --- a/osu.Game/Screens/Tournament/Drawings.cs +++ b/osu.Game/Screens/Tournament/Drawings.cs @@ -18,8 +18,8 @@ using osu.Game.Graphics.UserInterface; using osu.Game.Screens.Backgrounds; using osu.Game.Screens.Tournament.Components; using osu.Game.Screens.Tournament.Teams; -using OpenTK; -using OpenTK.Graphics; +using osuTK; +using osuTK.Graphics; using osu.Framework.IO.Stores; using osu.Framework.Graphics.Shapes; diff --git a/osu.Game/Screens/Tournament/Group.cs b/osu.Game/Screens/Tournament/Group.cs index 6845d8fc48..e9825b8d31 100644 --- a/osu.Game/Screens/Tournament/Group.cs +++ b/osu.Game/Screens/Tournament/Group.cs @@ -10,8 +10,8 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; using osu.Game.Graphics.Sprites; -using OpenTK; -using OpenTK.Graphics; +using osuTK; +using osuTK.Graphics; using osu.Game.Screens.Tournament.Teams; using osu.Framework.Graphics.Shapes; diff --git a/osu.Game/Screens/Tournament/GroupContainer.cs b/osu.Game/Screens/Tournament/GroupContainer.cs index c717a7401f..73d4f13132 100644 --- a/osu.Game/Screens/Tournament/GroupContainer.cs +++ b/osu.Game/Screens/Tournament/GroupContainer.cs @@ -7,7 +7,7 @@ using System.Linq; using System.Text; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using OpenTK; +using osuTK; using osu.Game.Screens.Tournament.Teams; namespace osu.Game.Screens.Tournament diff --git a/osu.Game/Screens/Tournament/ScrollingTeamContainer.cs b/osu.Game/Screens/Tournament/ScrollingTeamContainer.cs index d1c7e0fced..d401332e06 100644 --- a/osu.Game/Screens/Tournament/ScrollingTeamContainer.cs +++ b/osu.Game/Screens/Tournament/ScrollingTeamContainer.cs @@ -14,8 +14,8 @@ using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; using osu.Framework.Threading; using osu.Game.Screens.Tournament.Teams; -using OpenTK; -using OpenTK.Graphics; +using osuTK; +using osuTK.Graphics; namespace osu.Game.Screens.Tournament { diff --git a/osu.Game/Skinning/DefaultSkin.cs b/osu.Game/Skinning/DefaultSkin.cs index 8d1a31d4db..292889d4a3 100644 --- a/osu.Game/Skinning/DefaultSkin.cs +++ b/osu.Game/Skinning/DefaultSkin.cs @@ -4,7 +4,7 @@ using osu.Framework.Audio.Sample; using osu.Framework.Graphics; using osu.Framework.Graphics.Textures; -using OpenTK.Graphics; +using osuTK.Graphics; namespace osu.Game.Skinning { diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index bd7ca22fa1..25a9813ad6 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -13,7 +13,7 @@ using osu.Framework.Graphics.Textures; using osu.Framework.IO.Stores; using osu.Game.Database; using osu.Game.Graphics.Sprites; -using OpenTK; +using osuTK; namespace osu.Game.Skinning { diff --git a/osu.Game/Skinning/SkinConfiguration.cs b/osu.Game/Skinning/SkinConfiguration.cs index 40f5c158cc..7d354d108c 100644 --- a/osu.Game/Skinning/SkinConfiguration.cs +++ b/osu.Game/Skinning/SkinConfiguration.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; using osu.Game.Beatmaps.Formats; -using OpenTK.Graphics; +using osuTK.Graphics; namespace osu.Game.Skinning { diff --git a/osu.Game/Skinning/SkinFileInfo.cs b/osu.Game/Skinning/SkinFileInfo.cs index c9a3b0e94c..8f0f2b536a 100644 --- a/osu.Game/Skinning/SkinFileInfo.cs +++ b/osu.Game/Skinning/SkinFileInfo.cs @@ -2,15 +2,13 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System.ComponentModel.DataAnnotations; -using System.ComponentModel.DataAnnotations.Schema; using osu.Game.Database; using osu.Game.IO; namespace osu.Game.Skinning { - public class SkinFileInfo : INamedFileInfo + public class SkinFileInfo : INamedFileInfo, IHasPrimaryKey { - [DatabaseGenerated(DatabaseGeneratedOption.Identity)] public int ID { get; set; } public int SkinInfoID { get; set; } diff --git a/osu.Game/Skinning/SkinInfo.cs b/osu.Game/Skinning/SkinInfo.cs index 47dad60d88..60162ed224 100644 --- a/osu.Game/Skinning/SkinInfo.cs +++ b/osu.Game/Skinning/SkinInfo.cs @@ -3,28 +3,30 @@ using System; using System.Collections.Generic; -using System.ComponentModel.DataAnnotations.Schema; using osu.Game.Database; namespace osu.Game.Skinning { public class SkinInfo : IHasFiles, IEquatable, IHasPrimaryKey, ISoftDelete { - [DatabaseGenerated(DatabaseGeneratedOption.Identity)] public int ID { get; set; } public string Name { get; set; } + public string Hash { get; set; } + public string Creator { get; set; } public List Files { get; set; } public bool DeletePending { get; set; } + public string FullName => $"\"{Name}\" by {Creator}"; + public static SkinInfo Default { get; } = new SkinInfo { Name = "osu!lazer", Creator = "team osu!" }; public bool Equals(SkinInfo other) => other != null && ID == other.ID; - public override string ToString() => $"\"{Name}\" by {Creator}"; + public override string ToString() => FullName; } } diff --git a/osu.Game/Skinning/SkinManager.cs b/osu.Game/Skinning/SkinManager.cs index bd694e443a..ce179d43ef 100644 --- a/osu.Game/Skinning/SkinManager.cs +++ b/osu.Game/Skinning/SkinManager.cs @@ -26,8 +26,32 @@ namespace osu.Game.Skinning public override string[] HandledExtensions => new[] { ".osk" }; + protected override string[] HashableFileTypes => new[] { ".ini" }; + protected override string ImportFromStablePath => "Skins"; + public SkinManager(Storage storage, DatabaseContextFactory contextFactory, IIpcHost importHost, AudioManager audio) + : base(storage, contextFactory, new SkinStore(contextFactory, storage), importHost) + { + this.audio = audio; + + ItemRemoved += removedInfo => + { + // check the removed skin is not the current user choice. if it is, switch back to default. + if (removedInfo.ID == CurrentSkinInfo.Value.ID) + CurrentSkinInfo.Value = SkinInfo.Default; + }; + + CurrentSkinInfo.ValueChanged += info => CurrentSkin.Value = getSkin(info); + CurrentSkin.ValueChanged += skin => + { + if (skin.SkinInfo != CurrentSkinInfo.Value) + throw new InvalidOperationException($"Setting {nameof(CurrentSkin)}'s value directly is not supported. Use {nameof(CurrentSkinInfo)} instead."); + + SourceChanged?.Invoke(); + }; + } + /// /// Returns a list of all usable s. Includes the special default skin plus all skins from . /// @@ -45,24 +69,13 @@ namespace osu.Game.Skinning /// A list of available . public List GetAllUserSkins() => ModelStore.ConsumableItems.Where(s => !s.DeletePending).ToList(); - protected override SkinInfo CreateModel(ArchiveReader archive) => new SkinInfo - { - Name = archive.Name - }; + protected override SkinInfo CreateModel(ArchiveReader archive) => new SkinInfo { Name = archive.Name }; protected override void Populate(SkinInfo model, ArchiveReader archive) { base.Populate(model, archive); - populate(model); - } - /// - /// Populate a from its (if possible). - /// - /// - private void populate(SkinInfo model) - { - Skin reference = GetSkin(model); + Skin reference = getSkin(model); if (!string.IsNullOrEmpty(reference.Configuration.SkinInfo.Name)) { model.Name = reference.Configuration.SkinInfo.Name; @@ -80,7 +93,7 @@ namespace osu.Game.Skinning ///
/// The skin to lookup. /// A instance correlating to the provided . - public Skin GetSkin(SkinInfo skinInfo) + private Skin getSkin(SkinInfo skinInfo) { if (skinInfo == SkinInfo.Default) return new DefaultSkin(); @@ -88,28 +101,6 @@ namespace osu.Game.Skinning return new LegacySkin(skinInfo, Files.Store, audio); } - public SkinManager(Storage storage, DatabaseContextFactory contextFactory, IIpcHost importHost, AudioManager audio) - : base(storage, contextFactory, new SkinStore(contextFactory, storage), importHost) - { - this.audio = audio; - - ItemRemoved += removedInfo => - { - // check the removed skin is not the current user choice. if it is, switch back to default. - if (removedInfo.ID == CurrentSkinInfo.Value.ID) - CurrentSkinInfo.Value = SkinInfo.Default; - }; - - CurrentSkinInfo.ValueChanged += info => CurrentSkin.Value = GetSkin(info); - CurrentSkin.ValueChanged += skin => - { - if (skin.SkinInfo != CurrentSkinInfo.Value) - throw new InvalidOperationException($"Setting {nameof(CurrentSkin)}'s value directly is not supported. Use {nameof(CurrentSkinInfo)} instead."); - - SourceChanged?.Invoke(); - }; - } - /// /// Perform a lookup query on available s. /// diff --git a/osu.Game/Skinning/SkinnableDrawable.cs b/osu.Game/Skinning/SkinnableDrawable.cs index 5195daee65..12bbde67fb 100644 --- a/osu.Game/Skinning/SkinnableDrawable.cs +++ b/osu.Game/Skinning/SkinnableDrawable.cs @@ -3,7 +3,7 @@ using System; using osu.Framework.Graphics; -using OpenTK; +using osuTK; namespace osu.Game.Skinning { diff --git a/osu.Game/Skinning/SkinnableSound.cs b/osu.Game/Skinning/SkinnableSound.cs index 2fc9cff463..4f881a917e 100644 --- a/osu.Game/Skinning/SkinnableSound.cs +++ b/osu.Game/Skinning/SkinnableSound.cs @@ -31,6 +31,8 @@ namespace osu.Game.Skinning public void Play() => channels?.ForEach(c => c.Play()); + public override bool IsPresent => false; // We don't need to receive updates. + protected override void SkinChanged(ISkinSource skin, bool allowFallback) { channels = samples.Select(s => diff --git a/osu.Game/Storyboards/CommandTimelineGroup.cs b/osu.Game/Storyboards/CommandTimelineGroup.cs index 8ec758a104..b81b954f37 100644 --- a/osu.Game/Storyboards/CommandTimelineGroup.cs +++ b/osu.Game/Storyboards/CommandTimelineGroup.cs @@ -1,8 +1,8 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using OpenTK; -using OpenTK.Graphics; +using osuTK; +using osuTK.Graphics; using osu.Framework.Graphics; using System.Collections.Generic; using System.Linq; diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs index ef03539998..bbb9491b0a 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs @@ -1,7 +1,7 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using OpenTK; +using osuTK; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs index d15f3053a3..8f860c5f53 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs @@ -1,7 +1,7 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using OpenTK; +using osuTK; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Animations; diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs index efbb3de253..485f9bf61c 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs @@ -1,7 +1,7 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using OpenTK; +using osuTK; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; diff --git a/osu.Game/Storyboards/StoryboardAnimation.cs b/osu.Game/Storyboards/StoryboardAnimation.cs index 910fecbf1d..b1c5720d99 100644 --- a/osu.Game/Storyboards/StoryboardAnimation.cs +++ b/osu.Game/Storyboards/StoryboardAnimation.cs @@ -1,7 +1,7 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using OpenTK; +using osuTK; using osu.Framework.Graphics; using osu.Game.Storyboards.Drawables; diff --git a/osu.Game/Storyboards/StoryboardSprite.cs b/osu.Game/Storyboards/StoryboardSprite.cs index 7e466e37e6..173f403cc2 100644 --- a/osu.Game/Storyboards/StoryboardSprite.cs +++ b/osu.Game/Storyboards/StoryboardSprite.cs @@ -1,7 +1,7 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using OpenTK; +using osuTK; using osu.Framework.Graphics; using osu.Game.Storyboards.Drawables; using System; diff --git a/osu.Game/Tests/Visual/EditorClockTestCase.cs b/osu.Game/Tests/Visual/EditorClockTestCase.cs index ebede74171..479ed385e2 100644 --- a/osu.Game/Tests/Visual/EditorClockTestCase.cs +++ b/osu.Game/Tests/Visual/EditorClockTestCase.cs @@ -7,7 +7,6 @@ using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Screens.Edit; -using osu.Game.Screens.Edit.Screens.Compose; namespace osu.Game.Tests.Visual { diff --git a/osu.Game/Tests/Visual/EditorTestCase.cs b/osu.Game/Tests/Visual/EditorTestCase.cs index 2ab121fcc9..2d02509b58 100644 --- a/osu.Game/Tests/Visual/EditorTestCase.cs +++ b/osu.Game/Tests/Visual/EditorTestCase.cs @@ -6,7 +6,6 @@ using System.Collections.Generic; using osu.Framework.Allocation; using osu.Game.Rulesets; using osu.Game.Screens.Edit; -using osu.Game.Screens.Edit.Screens; using osu.Game.Tests.Beatmaps; namespace osu.Game.Tests.Visual diff --git a/osu.Game/Tests/Visual/PlacementBlueprintTestCase.cs b/osu.Game/Tests/Visual/PlacementBlueprintTestCase.cs new file mode 100644 index 0000000000..ca762c6be2 --- /dev/null +++ b/osu.Game/Tests/Visual/PlacementBlueprintTestCase.cs @@ -0,0 +1,65 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// 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.Timing; +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Screens.Edit.Compose; + +namespace osu.Game.Tests.Visual +{ + [Cached(Type = typeof(IPlacementHandler))] + public abstract class PlacementBlueprintTestCase : OsuTestCase, IPlacementHandler + { + protected readonly Container HitObjectContainer; + private PlacementBlueprint currentBlueprint; + + protected PlacementBlueprintTestCase() + { + Beatmap.Value.BeatmapInfo.BaseDifficulty.CircleSize = 2; + + Add(HitObjectContainer = CreateHitObjectContainer()); + } + + [BackgroundDependencyLoader] + private void load() + { + Add(currentBlueprint = CreateBlueprint()); + } + + protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) + { + var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); + dependencies.CacheAs(new StopwatchClock()); + + return dependencies; + } + + public void BeginPlacement(HitObject hitObject) + { + } + + public void EndPlacement(HitObject hitObject) + { + AddHitObject(CreateHitObject(hitObject)); + + Remove(currentBlueprint); + Add(currentBlueprint = CreateBlueprint()); + } + + public void Delete(HitObject hitObject) + { + } + + protected virtual Container CreateHitObjectContainer() => new Container { RelativeSizeAxes = Axes.Both }; + + protected virtual void AddHitObject(DrawableHitObject hitObject) => HitObjectContainer.Add(hitObject); + + protected abstract DrawableHitObject CreateHitObject(HitObject hitObject); + protected abstract PlacementBlueprint CreateBlueprint(); + } +} diff --git a/osu.Game/Tests/Visual/ScrollingTestContainer.cs b/osu.Game/Tests/Visual/ScrollingTestContainer.cs new file mode 100644 index 0000000000..bc251fddbe --- /dev/null +++ b/osu.Game/Tests/Visual/ScrollingTestContainer.cs @@ -0,0 +1,97 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Allocation; +using osu.Framework.Configuration; +using osu.Framework.Graphics.Containers; +using osu.Framework.Lists; +using osu.Game.Configuration; +using osu.Game.Rulesets.Timing; +using osu.Game.Rulesets.UI.Scrolling; +using osu.Game.Rulesets.UI.Scrolling.Algorithms; + +namespace osu.Game.Tests.Visual +{ + /// + /// A container which provides a to children. + /// This should only be used when testing + /// + public class ScrollingTestContainer : Container + { + public SortedList ControlPoints => scrollingInfo.Algorithm.ControlPoints; + + public ScrollVisualisationMethod ScrollAlgorithm { set => scrollingInfo.Algorithm.Algorithm = value; } + + public double TimeRange { set => scrollingInfo.TimeRange.Value = value; } + + public IScrollingInfo ScrollingInfo => scrollingInfo; + + [Cached(Type = typeof(IScrollingInfo))] + private readonly TestScrollingInfo scrollingInfo = new TestScrollingInfo(); + + public ScrollingTestContainer(ScrollingDirection direction) + { + scrollingInfo.Direction.Value = direction; + } + + public void Flip() => scrollingInfo.Direction.Value = scrollingInfo.Direction.Value == ScrollingDirection.Up ? ScrollingDirection.Down : ScrollingDirection.Up; + + private class TestScrollingInfo : IScrollingInfo + { + public readonly Bindable Direction = new Bindable(); + IBindable IScrollingInfo.Direction => Direction; + + public readonly Bindable TimeRange = new Bindable(1000) { Value = 1000 }; + IBindable IScrollingInfo.TimeRange => TimeRange; + + public readonly TestScrollAlgorithm Algorithm = new TestScrollAlgorithm(); + IScrollAlgorithm IScrollingInfo.Algorithm => Algorithm; + } + + private class TestScrollAlgorithm : IScrollAlgorithm + { + public readonly SortedList ControlPoints = new SortedList(); + + private IScrollAlgorithm implementation; + + public TestScrollAlgorithm() + { + Algorithm = ScrollVisualisationMethod.Constant; + } + + public ScrollVisualisationMethod Algorithm + { + set + { + switch (value) + { + case ScrollVisualisationMethod.Constant: + implementation = new ConstantScrollAlgorithm(); + break; + case ScrollVisualisationMethod.Overlapping: + implementation = new OverlappingScrollAlgorithm(ControlPoints); + break; + case ScrollVisualisationMethod.Sequential: + implementation = new SequentialScrollAlgorithm(ControlPoints); + break; + } + } + } + + public double GetDisplayStartTime(double time, double timeRange) + => implementation.GetDisplayStartTime(time, timeRange); + + public float GetLength(double startTime, double endTime, double timeRange, float scrollLength) + => implementation.GetLength(startTime, endTime, timeRange, scrollLength); + + public float PositionAt(double time, double currentTime, double timeRange, float scrollLength) + => implementation.PositionAt(time, currentTime, timeRange, scrollLength); + + public double TimeAt(float position, double currentTime, double timeRange, float scrollLength) + => implementation.TimeAt(position, currentTime, timeRange, scrollLength); + + public void Reset() + => implementation.Reset(); + } + } +} diff --git a/osu.Game/Tests/Visual/SelectionBlueprintTestCase.cs b/osu.Game/Tests/Visual/SelectionBlueprintTestCase.cs new file mode 100644 index 0000000000..183bef7602 --- /dev/null +++ b/osu.Game/Tests/Visual/SelectionBlueprintTestCase.cs @@ -0,0 +1,50 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// 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.Input.Events; +using osu.Framework.Timing; +using osu.Game.Rulesets.Edit; + +namespace osu.Game.Tests.Visual +{ + public abstract class SelectionBlueprintTestCase : OsuTestCase + { + private SelectionBlueprint blueprint; + + protected override Container Content => content ?? base.Content; + private readonly Container content; + + protected SelectionBlueprintTestCase() + { + base.Content.Add(content = new Container + { + Clock = new FramedClock(new StopwatchClock()), + RelativeSizeAxes = Axes.Both + }); + } + + [BackgroundDependencyLoader] + private void load() + { + blueprint = CreateBlueprint(); + blueprint.Depth = float.MinValue; + blueprint.SelectionRequested += (_, __) => blueprint.Select(); + + Add(blueprint); + + AddStep("Select", () => blueprint.Select()); + AddStep("Deselect", () => blueprint.Deselect()); + } + + protected override bool OnClick(ClickEvent e) + { + blueprint.Deselect(); + return true; + } + + protected abstract SelectionBlueprint CreateBlueprint(); + } +} diff --git a/osu.Game/Tests/Visual/TestCasePlayer.cs b/osu.Game/Tests/Visual/TestCasePlayer.cs index 9e499cb961..47bf787bb5 100644 --- a/osu.Game/Tests/Visual/TestCasePlayer.cs +++ b/osu.Game/Tests/Visual/TestCasePlayer.cs @@ -11,7 +11,7 @@ using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osu.Game.Screens.Play; using osu.Game.Tests.Beatmaps; -using OpenTK.Graphics; +using osuTK.Graphics; namespace osu.Game.Tests.Visual { diff --git a/osu.Game/Users/UserPanel.cs b/osu.Game/Users/UserPanel.cs index bcb91c1955..d86f608bd1 100644 --- a/osu.Game/Users/UserPanel.cs +++ b/osu.Game/Users/UserPanel.cs @@ -2,8 +2,8 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System; -using OpenTK; -using OpenTK.Graphics; +using osuTK; +using osuTK.Graphics; using osu.Framework.Allocation; using osu.Framework.Configuration; using osu.Framework.Extensions.Color4Extensions; diff --git a/osu.Game/Users/UserStatus.cs b/osu.Game/Users/UserStatus.cs index 1584605166..6b27ff18a7 100644 --- a/osu.Game/Users/UserStatus.cs +++ b/osu.Game/Users/UserStatus.cs @@ -1,7 +1,7 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using OpenTK.Graphics; +using osuTK.Graphics; using osu.Game.Graphics; namespace osu.Game.Users diff --git a/osu.Game/Utils/RavenLogger.cs b/osu.Game/Utils/RavenLogger.cs index b28dd1fb73..c6e6d1e9d7 100644 --- a/osu.Game/Utils/RavenLogger.cs +++ b/osu.Game/Utils/RavenLogger.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.IO; using System.Threading.Tasks; using osu.Framework.Logging; using SharpRaven; @@ -35,6 +36,16 @@ namespace osu.Game.Utils if (exception != null) { + if (exception is IOException ioe) + { + // disk full exceptions, see https://stackoverflow.com/a/9294382 + const int hr_error_handle_disk_full = unchecked((int)0x80070027); + const int hr_error_disk_full = unchecked((int)0x80070070); + + if (ioe.HResult == hr_error_handle_disk_full || ioe.HResult == hr_error_disk_full) + return; + } + // since we let unhandled exceptions go ignored at times, we want to ensure they don't get submitted on subsequent reports. if (lastException != null && lastException.Message == exception.Message && exception.StackTrace.StartsWith(lastException.StackTrace)) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 26004b513f..85eabb0350 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -18,10 +18,10 @@ - + - \ No newline at end of file + diff --git a/osu.sln.DotSettings b/osu.sln.DotSettings index 345400305c..d6882282e6 100644 --- a/osu.sln.DotSettings +++ b/osu.sln.DotSettings @@ -43,7 +43,7 @@ WARNING DO_NOT_SHOW WARNING - WARNING + HINT HINT HINT ERROR diff --git a/tools/cakebuild.csproj b/tools/cakebuild.csproj new file mode 100644 index 0000000000..8ccce35e26 --- /dev/null +++ b/tools/cakebuild.csproj @@ -0,0 +1,11 @@ + + + Exe + true + netcoreapp2.0 + + + + + + \ No newline at end of file