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..8f011deabe 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
@@ -257,3 +262,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/appveyor_deploy.yml b/appveyor_deploy.yml
deleted file mode 100644
index 22a4859885..0000000000
--- a/appveyor_deploy.yml
+++ /dev/null
@@ -1,30 +0,0 @@
-clone_depth: 1
-version: '{build}'
-skip_non_tags: true
-image: Visual Studio 2017
-install:
- - git clone https://github.com/ppy/osu-deploy
-before_build:
- - ps: if($env:appveyor_repo_tag -eq 'True') { Update-AppveyorBuild -Version $env:appveyor_repo_tag_name }
- - cmd: git submodule update --init --recursive --depth=5
- - cmd: nuget restore -verbosity quiet
-build_script:
- - ps: iex ((New-Object Net.WebClient).DownloadString('https://raw.githubusercontent.com/appveyor/secure-file/master/install.ps1'))
- - appveyor DownloadFile https://puu.sh/BCrS8/7faccf7876.enc # signing certificate
- - cmd: appveyor-tools\secure-file -decrypt 7faccf7876.enc -secret %decode_secret% -out %HOMEPATH%\deanherbert.pfx
- - appveyor DownloadFile https://puu.sh/A6g75/fdc6f19b04.enc # deploy configuration
- - cd osu-deploy
- - nuget restore -verbosity quiet
- - msbuild osu.Desktop.Deploy.csproj
- - cmd: ..\appveyor-tools\secure-file -decrypt ..\fdc6f19b04.enc -secret %decode_secret% -out bin\Debug\netcoreapp2.1\osu.Desktop.Deploy.dll.config
- - dotnet bin/Debug/netcoreapp2.1/osu.Desktop.Deploy.dll %code_signing_password% %APPVEYOR_REPO_TAG_NAME%
-environment:
- decode_secret:
- secure: i67IC2xj6DjjxmA6Oj2jing3+MwzLkq6CbGsjfZ7rdY=
- code_signing_password:
- secure: 34tLNqvjmmZEi97MLKfrnQ==
-artifacts:
- - path: 'osu-deploy/releases/*'
-deploy:
- - provider: Environment
- name: github
\ No newline at end of file
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 100644
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..9ee64e369f 160000
--- a/osu-resources
+++ b/osu-resources
@@ -1 +1 @@
-Subproject commit c3848d8b1c84966abe851d915bcca878415614b4
+Subproject commit 9ee64e369fe6fdafc6aed40f5a35b5f01eb82c53
diff --git a/osu.Desktop/osu.Desktop.csproj b/osu.Desktop/osu.Desktop.csproj
index 1e8bf05e01..e1e59804e5 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 34e07170bd..cac1356c81 100644
--- a/osu.Game.Rulesets.Catch.Tests/TestCaseAutoJuiceStream.cs
+++ b/osu.Game.Rulesets.Catch.Tests/TestCaseAutoJuiceStream.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 System.Linq;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Catch.Objects;
@@ -38,12 +37,12 @@ namespace osu.Game.Rulesets.Catch.Tests
beatmap.HitObjects.Add(new JuiceStream
{
X = 0.5f - width / 2,
- ControlPoints = new List
+ ControlPoints = new[]
{
Vector2.Zero,
new Vector2(width * CatchPlayfield.BASE_WIDTH, 0)
},
- CurveType = CurveType.Linear,
+ PathType = PathType.Linear,
Distance = width * CatchPlayfield.BASE_WIDTH,
StartTime = i * 2000,
NewCombo = i % 8 == 0
diff --git a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs
index 88686ac243..fed65c42af 100644
--- a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs
+++ b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs
@@ -35,7 +35,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
StartTime = obj.StartTime,
Samples = obj.Samples,
ControlPoints = curveData.ControlPoints,
- CurveType = curveData.CurveType,
+ PathType = curveData.PathType,
Distance = curveData.Distance,
NodeSamples = curveData.NodeSamples,
RepeatCount = curveData.RepeatCount,
diff --git a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs
index d7eed72563..f1e131932b 100644
--- a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs
+++ b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs
@@ -50,7 +50,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 +95,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 +110,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,12 +127,12 @@ 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;
@@ -140,24 +140,24 @@ namespace osu.Game.Rulesets.Catch.Objects
public double Distance
{
- get { return Curve.Distance; }
- set { Curve.Distance = value; }
+ get { return Path.Distance; }
+ set { Path.Distance = value; }
}
- public SliderCurve Curve { get; } = new SliderCurve();
+ public SliderPath Path { get; } = new SliderPath();
- public List ControlPoints
+ public Vector2[] ControlPoints
{
- get { return Curve.ControlPoints; }
- set { Curve.ControlPoints = value; }
+ get { return Path.ControlPoints; }
+ set { Path.ControlPoints = value; }
}
public List> NodeSamples { get; set; } = new List>();
- public CurveType CurveType
+ public PathType PathType
{
- get { return Curve.CurveType; }
- set { Curve.CurveType = value; }
+ get { return Path.PathType; }
+ set { Path.PathType = value; }
}
public double? LegacyLastTickOffset { get; set; }
diff --git a/osu.Game.Rulesets.Catch/UI/CatchRulesetContainer.cs b/osu.Game.Rulesets.Catch/UI/CatchRulesetContainer.cs
index bd0fec43a1..94233bc9f0 100644
--- a/osu.Game.Rulesets.Catch/UI/CatchRulesetContainer.cs
+++ b/osu.Game.Rulesets.Catch/UI/CatchRulesetContainer.cs
@@ -31,7 +31,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.Mania/Beatmaps/ManiaBeatmapConverter.cs b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs
index 835c4474d7..d86ebc9a09 100644
--- a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs
+++ b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs
@@ -48,7 +48,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
TargetColumns = (int)Math.Max(1, roundedCircleSize);
else
{
- float percentSliderOrSpinner = (float)beatmap.HitObjects.Count(h => h is IHasEndTime) / beatmap.HitObjects.Count();
+ float percentSliderOrSpinner = (float)beatmap.HitObjects.Count(h => h is IHasEndTime) / beatmap.HitObjects.Count;
if (percentSliderOrSpinner < 0.2)
TargetColumns = 7;
else if (percentSliderOrSpinner < 0.3 || roundedCircleSize >= 5)
diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs
index 05ca1d4365..7bd39adb45 100644
--- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs
+++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs
@@ -112,7 +112,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
drainTime = 10000;
BeatmapDifficulty difficulty = OriginalBeatmap.BeatmapInfo.BaseDifficulty;
- conversionDifficulty = ((difficulty.DrainRate + MathHelper.Clamp(difficulty.ApproachRate, 4, 7)) / 1.5 + (double)OriginalBeatmap.HitObjects.Count() / drainTime * 9f) / 38f * 5f / 1.15;
+ conversionDifficulty = ((difficulty.DrainRate + MathHelper.Clamp(difficulty.ApproachRate, 4, 7)) / 1.5 + (double)OriginalBeatmap.HitObjects.Count / drainTime * 9f) / 38f * 5f / 1.15;
conversionDifficulty = Math.Min(conversionDifficulty.Value, 12);
return conversionDifficulty.Value;
diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs
index f37d8134ce..fcacde769b 100644
--- a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs
+++ b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs
@@ -1,22 +1,23 @@
// 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.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.Masks;
using osu.Game.Rulesets.Mania.UI;
+using osu.Game.Rulesets.UI;
namespace osu.Game.Rulesets.Mania.Edit
{
- public class ManiaHitObjectComposer : HitObjectComposer
+ public class ManiaHitObjectComposer : HitObjectComposer
{
protected new ManiaConfigManager Config => (ManiaConfigManager)base.Config;
@@ -32,22 +33,19 @@ namespace osu.Game.Rulesets.Mania.Edit
return dependencies;
}
- protected override RulesetContainer CreateRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap) => new ManiaEditRulesetContainer(ruleset, beatmap);
+ protected override RulesetContainer CreateRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap)
+ => new ManiaEditRulesetContainer(ruleset, beatmap);
- protected override IReadOnlyList CompositionTools => new ICompositionTool[]
- {
- new HitObjectCompositionTool("Note"),
- new HitObjectCompositionTool("Hold"),
- };
+ protected override IReadOnlyList CompositionTools => Array.Empty();
- public override HitObjectMask CreateMaskFor(DrawableHitObject hitObject)
+ public override SelectionMask CreateMaskFor(DrawableHitObject hitObject)
{
switch (hitObject)
{
case DrawableNote note:
- return new NoteMask(note);
+ return new NoteSelectionMask(note);
case DrawableHoldNote holdNote:
- return new HoldNoteMask(holdNote);
+ return new HoldNoteSelectionMask(holdNote);
}
return base.CreateMaskFor(hitObject);
diff --git a/osu.Game.Rulesets.Mania/Edit/Layers/Selection/Overlays/HoldNoteMask.cs b/osu.Game.Rulesets.Mania/Edit/Masks/HoldNoteSelectionMask.cs
similarity index 86%
rename from osu.Game.Rulesets.Mania/Edit/Layers/Selection/Overlays/HoldNoteMask.cs
rename to osu.Game.Rulesets.Mania/Edit/Masks/HoldNoteSelectionMask.cs
index 03d2ba19cb..a2c01d7a0e 100644
--- a/osu.Game.Rulesets.Mania/Edit/Layers/Selection/Overlays/HoldNoteMask.cs
+++ b/osu.Game.Rulesets.Mania/Edit/Masks/HoldNoteSelectionMask.cs
@@ -13,9 +13,9 @@ using osu.Game.Rulesets.UI.Scrolling;
using OpenTK;
using OpenTK.Graphics;
-namespace osu.Game.Rulesets.Mania.Edit.Layers.Selection.Overlays
+namespace osu.Game.Rulesets.Mania.Edit.Masks
{
- public class HoldNoteMask : HitObjectMask
+ public class HoldNoteSelectionMask : SelectionMask
{
public new DrawableHoldNote HitObject => (DrawableHoldNote)base.HitObject;
@@ -23,13 +23,13 @@ namespace osu.Game.Rulesets.Mania.Edit.Layers.Selection.Overlays
private readonly BodyPiece body;
- public HoldNoteMask(DrawableHoldNote hold)
+ public HoldNoteSelectionMask(DrawableHoldNote hold)
: base(hold)
{
InternalChildren = new Drawable[]
{
- new HoldNoteNoteMask(hold.Head),
- new HoldNoteNoteMask(hold.Tail),
+ new HoldNoteNoteSelectionMask(hold.Head),
+ new HoldNoteNoteSelectionMask(hold.Tail),
body = new BodyPiece
{
AccentColour = Color4.Transparent
@@ -59,9 +59,9 @@ namespace osu.Game.Rulesets.Mania.Edit.Layers.Selection.Overlays
Y -= HitObject.Tail.DrawHeight;
}
- private class HoldNoteNoteMask : NoteMask
+ private class HoldNoteNoteSelectionMask : NoteSelectionMask
{
- public HoldNoteNoteMask(DrawableNote note)
+ public HoldNoteNoteSelectionMask(DrawableNote note)
: base(note)
{
Select();
diff --git a/osu.Game.Rulesets.Mania/Edit/Layers/Selection/Overlays/NoteMask.cs b/osu.Game.Rulesets.Mania/Edit/Masks/NoteSelectionMask.cs
similarity index 85%
rename from osu.Game.Rulesets.Mania/Edit/Layers/Selection/Overlays/NoteMask.cs
rename to osu.Game.Rulesets.Mania/Edit/Masks/NoteSelectionMask.cs
index 78f876cb14..18f042a483 100644
--- a/osu.Game.Rulesets.Mania/Edit/Layers/Selection/Overlays/NoteMask.cs
+++ b/osu.Game.Rulesets.Mania/Edit/Masks/NoteSelectionMask.cs
@@ -7,11 +7,11 @@ 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
+namespace osu.Game.Rulesets.Mania.Edit.Masks
{
- public class NoteMask : HitObjectMask
+ public class NoteSelectionMask : SelectionMask
{
- public NoteMask(DrawableNote note)
+ public NoteSelectionMask(DrawableNote note)
: base(note)
{
Scale = note.Scale;
diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModDualStages.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModDualStages.cs
index aecfb50fbe..4790a77cc0 100644
--- a/osu.Game.Rulesets.Mania/Mods/ManiaModDualStages.cs
+++ b/osu.Game.Rulesets.Mania/Mods/ManiaModDualStages.cs
@@ -5,13 +5,11 @@ 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";
@@ -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/UI/ManiaRulesetContainer.cs b/osu.Game.Rulesets.Mania/UI/ManiaRulesetContainer.cs
index 49874f6dc1..500fb5a631 100644
--- a/osu.Game.Rulesets.Mania/UI/ManiaRulesetContainer.cs
+++ b/osu.Game.Rulesets.Mania/UI/ManiaRulesetContainer.cs
@@ -96,7 +96,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.Osu.Tests/TestCaseHitCirclePlacementMask.cs b/osu.Game.Rulesets.Osu.Tests/TestCaseHitCirclePlacementMask.cs
new file mode 100644
index 0000000000..be0b94c4c8
--- /dev/null
+++ b/osu.Game.Rulesets.Osu.Tests/TestCaseHitCirclePlacementMask.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.Masks.HitCircleMasks;
+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 TestCaseHitCirclePlacementMask : HitObjectPlacementMaskTestCase
+ {
+ protected override DrawableHitObject CreateHitObject(HitObject hitObject) => new DrawableHitCircle((HitCircle)hitObject);
+ protected override PlacementMask CreateMask() => new HitCirclePlacementMask();
+ }
+}
diff --git a/osu.Game.Rulesets.Osu.Tests/TestCaseHitCircleSelectionMask.cs b/osu.Game.Rulesets.Osu.Tests/TestCaseHitCircleSelectionMask.cs
new file mode 100644
index 0000000000..e3d61623bf
--- /dev/null
+++ b/osu.Game.Rulesets.Osu.Tests/TestCaseHitCircleSelectionMask.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.Masks.HitCircleMasks;
+using osu.Game.Rulesets.Osu.Objects;
+using osu.Game.Rulesets.Osu.Objects.Drawables;
+using osu.Game.Tests.Visual;
+using OpenTK;
+
+namespace osu.Game.Rulesets.Osu.Tests
+{
+ public class TestCaseHitCircleSelectionMask : HitObjectSelectionMaskTestCase
+ {
+ private readonly DrawableHitCircle drawableObject;
+
+ public TestCaseHitCircleSelectionMask()
+ {
+ var hitCircle = new HitCircle { Position = new Vector2(256, 192) };
+ hitCircle.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty { CircleSize = 2 });
+
+ Add(drawableObject = new DrawableHitCircle(hitCircle));
+ }
+
+ protected override SelectionMask CreateMask() => new HitCircleSelectionMask(drawableObject);
+ }
+}
diff --git a/osu.Game.Rulesets.Osu.Tests/TestCaseSlider.cs b/osu.Game.Rulesets.Osu.Tests/TestCaseSlider.cs
index f3d3fb6e7f..0bd6bb5abc 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestCaseSlider.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestCaseSlider.cs
@@ -108,7 +108,7 @@ namespace osu.Game.Rulesets.Osu.Tests
{
StartTime = Time.Current + 1000,
Position = new Vector2(239, 176),
- ControlPoints = new List
+ ControlPoints = new[]
{
Vector2.Zero,
new Vector2(154, 28),
@@ -141,7 +141,7 @@ namespace osu.Game.Rulesets.Osu.Tests
{
StartTime = Time.Current + 1000,
Position = new Vector2(-(distance / 2), 0),
- ControlPoints = new List
+ ControlPoints = new[]
{
Vector2.Zero,
new Vector2(distance, 0),
@@ -161,7 +161,7 @@ namespace osu.Game.Rulesets.Osu.Tests
{
StartTime = Time.Current + 1000,
Position = new Vector2(-200, 0),
- ControlPoints = new List
+ ControlPoints = new[]
{
Vector2.Zero,
new Vector2(200, 200),
@@ -181,10 +181,10 @@ namespace osu.Game.Rulesets.Osu.Tests
{
var slider = new Slider
{
- CurveType = CurveType.Linear,
+ PathType = PathType.Linear,
StartTime = Time.Current + 1000,
Position = new Vector2(-200, 0),
- ControlPoints = new List
+ ControlPoints = new[]
{
Vector2.Zero,
new Vector2(150, 75),
@@ -207,10 +207,10 @@ namespace osu.Game.Rulesets.Osu.Tests
{
var slider = new Slider
{
- CurveType = CurveType.Bezier,
+ PathType = PathType.Bezier,
StartTime = Time.Current + 1000,
Position = new Vector2(-200, 0),
- ControlPoints = new List
+ ControlPoints = new[]
{
Vector2.Zero,
new Vector2(150, 75),
@@ -232,10 +232,10 @@ namespace osu.Game.Rulesets.Osu.Tests
{
var slider = new Slider
{
- CurveType = CurveType.Linear,
+ PathType = PathType.Linear,
StartTime = Time.Current + 1000,
Position = new Vector2(0, 0),
- ControlPoints = new List
+ ControlPoints = new[]
{
Vector2.Zero,
new Vector2(-200, 0),
@@ -264,8 +264,8 @@ namespace osu.Game.Rulesets.Osu.Tests
{
StartTime = Time.Current + 1000,
Position = new Vector2(-100, 0),
- CurveType = CurveType.Catmull,
- ControlPoints = new List
+ PathType = PathType.Catmull,
+ ControlPoints = new[]
{
Vector2.Zero,
new Vector2(50, -50),
diff --git a/osu.Game.Rulesets.Osu.Tests/TestCaseSliderPlacementMask.cs b/osu.Game.Rulesets.Osu.Tests/TestCaseSliderPlacementMask.cs
new file mode 100644
index 0000000000..889ea0c311
--- /dev/null
+++ b/osu.Game.Rulesets.Osu.Tests/TestCaseSliderPlacementMask.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.Masks.SliderMasks;
+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 TestCaseSliderPlacementMask : HitObjectPlacementMaskTestCase
+ {
+ protected override DrawableHitObject CreateHitObject(HitObject hitObject) => new DrawableSlider((Slider)hitObject);
+ protected override PlacementMask CreateMask() => new SliderPlacementMask();
+ }
+}
diff --git a/osu.Game.Rulesets.Osu.Tests/TestCaseSliderSelectionMask.cs b/osu.Game.Rulesets.Osu.Tests/TestCaseSliderSelectionMask.cs
new file mode 100644
index 0000000000..87e0e1a7ec
--- /dev/null
+++ b/osu.Game.Rulesets.Osu.Tests/TestCaseSliderSelectionMask.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;
+using System.Collections.Generic;
+using osu.Game.Beatmaps;
+using osu.Game.Beatmaps.ControlPoints;
+using osu.Game.Rulesets.Edit;
+using osu.Game.Rulesets.Objects.Types;
+using osu.Game.Rulesets.Osu.Edit.Masks.SliderMasks;
+using osu.Game.Rulesets.Osu.Edit.Masks.SliderMasks.Components;
+using osu.Game.Rulesets.Osu.Objects;
+using osu.Game.Rulesets.Osu.Objects.Drawables;
+using osu.Game.Tests.Visual;
+using OpenTK;
+
+namespace osu.Game.Rulesets.Osu.Tests
+{
+ public class TestCaseSliderSelectionMask : HitObjectSelectionMaskTestCase
+ {
+ public override IReadOnlyList RequiredTypes => new[]
+ {
+ typeof(SliderSelectionMask),
+ typeof(SliderCircleSelectionMask),
+ typeof(SliderBodyPiece),
+ typeof(SliderCircle),
+ typeof(PathControlPointVisualiser),
+ typeof(PathControlPointPiece)
+ };
+
+ private readonly DrawableSlider drawableObject;
+
+ public TestCaseSliderSelectionMask()
+ {
+ var slider = new Slider
+ {
+ Position = new Vector2(256, 192),
+ ControlPoints = new[]
+ {
+ Vector2.Zero,
+ new Vector2(150, 150),
+ new Vector2(300, 0)
+ },
+ PathType = PathType.Bezier,
+ Distance = 350
+ };
+
+ slider.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty { CircleSize = 2 });
+
+ Add(drawableObject = new DrawableSlider(slider));
+ }
+
+ protected override SelectionMask CreateMask() => new SliderSelectionMask(drawableObject);
+ }
+}
diff --git a/osu.Game.Rulesets.Osu.Tests/TestCaseSpinnerPlacementMask.cs b/osu.Game.Rulesets.Osu.Tests/TestCaseSpinnerPlacementMask.cs
new file mode 100644
index 0000000000..c2c7942c57
--- /dev/null
+++ b/osu.Game.Rulesets.Osu.Tests/TestCaseSpinnerPlacementMask.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.Masks.SpinnerMasks;
+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 TestCaseSpinnerPlacementMask : HitObjectPlacementMaskTestCase
+ {
+ protected override DrawableHitObject CreateHitObject(HitObject hitObject) => new DrawableSpinner((Spinner)hitObject);
+
+ protected override PlacementMask CreateMask() => new SpinnerPlacementMask();
+ }
+}
diff --git a/osu.Game.Rulesets.Osu.Tests/TestCaseSpinnerSelectionMask.cs b/osu.Game.Rulesets.Osu.Tests/TestCaseSpinnerSelectionMask.cs
new file mode 100644
index 0000000000..b436ff0e9f
--- /dev/null
+++ b/osu.Game.Rulesets.Osu.Tests/TestCaseSpinnerSelectionMask.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.Masks.SpinnerMasks;
+using osu.Game.Rulesets.Osu.Edit.Masks.SpinnerMasks.Components;
+using osu.Game.Rulesets.Osu.Objects;
+using osu.Game.Rulesets.Osu.Objects.Drawables;
+using osu.Game.Tests.Visual;
+using OpenTK;
+
+namespace osu.Game.Rulesets.Osu.Tests
+{
+ public class TestCaseSpinnerSelectionMask : HitObjectSelectionMaskTestCase
+ {
+ public override IReadOnlyList RequiredTypes => new[]
+ {
+ typeof(SpinnerSelectionMask),
+ typeof(SpinnerPiece)
+ };
+
+ private readonly DrawableSpinner drawableSpinner;
+
+ public TestCaseSpinnerSelectionMask()
+ {
+ 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 SelectionMask CreateMask() => new SpinnerSelectionMask(drawableSpinner) { Size = new Vector2(0.5f) };
+ }
+}
diff --git a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs
index 8130d25890..87c81cdd3b 100644
--- a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs
+++ b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs
@@ -36,14 +36,17 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
StartTime = original.StartTime,
Samples = original.Samples,
ControlPoints = curveData.ControlPoints,
- CurveType = curveData.CurveType,
+ PathType = curveData.PathType,
Distance = curveData.Distance,
NodeSamples = curveData.NodeSamples,
RepeatCount = curveData.RepeatCount,
Position = positionData?.Position ?? Vector2.Zero,
NewCombo = comboData?.NewCombo ?? false,
ComboOffset = comboData?.ComboOffset ?? 0,
- LegacyLastTickOffset = legacyOffset?.LegacyLastTickOffset
+ LegacyLastTickOffset = legacyOffset?.LegacyLastTickOffset,
+ // prior to v8, speed multipliers don't adjust for how many ticks are generated over the same distance.
+ // this results in more (or less) ticks being generated in ().Sum(s => s.NestedHitObjects.Count - 1);
diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
index d4a60dd52f..b0887ac72b 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
@@ -34,7 +34,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
{
countHitCircles = Beatmap.HitObjects.Count(h => h is HitCircle);
- beatmapMaxCombo = Beatmap.HitObjects.Count();
+ beatmapMaxCombo = Beatmap.HitObjects.Count;
// Add the ticks + tail of the slider. 1 is subtracted because the "headcircle" would be counted twice (once for the slider itself in the line above)
beatmapMaxCombo += Beatmap.HitObjects.OfType().Sum(s => s.NestedHitObjects.Count - 1);
}
diff --git a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs
index ccfcc1ef25..39e3c009da 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs
@@ -75,15 +75,13 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
{
computeSliderCursorPosition(lastSlider);
lastCursorPosition = lastSlider.LazyEndPosition ?? lastCursorPosition;
+
+ TravelDistance = lastSlider.LazyTravelDistance * scalingFactor;
}
// Don't need to jump to reach spinners
if (!(BaseObject is Spinner))
JumpDistance = (BaseObject.StackedPosition * scalingFactor - lastCursorPosition * scalingFactor).Length;
-
- // Todo: BUG!!! Last slider's travel distance is considered ONLY IF we ourselves are also a slider!
- if (BaseObject is Slider)
- TravelDistance = (lastSlider?.LazyTravelDistance ?? 0) * scalingFactor;
}
private void setTimingValues()
@@ -110,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/HitCircleCompositionTool.cs b/osu.Game.Rulesets.Osu/Edit/HitCircleCompositionTool.cs
new file mode 100644
index 0000000000..767c7db5da
--- /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.Masks.HitCircleMasks;
+using osu.Game.Rulesets.Osu.Objects;
+
+namespace osu.Game.Rulesets.Osu.Edit
+{
+ public class HitCircleCompositionTool : HitObjectCompositionTool
+ {
+ public HitCircleCompositionTool()
+ : base(nameof(HitCircle))
+ {
+ }
+
+ public override PlacementMask CreatePlacementMask() => new HitCirclePlacementMask();
+ }
+}
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/Masks/HitCircleMasks/Components/HitCirclePiece.cs b/osu.Game.Rulesets.Osu/Edit/Masks/HitCircleMasks/Components/HitCirclePiece.cs
new file mode 100644
index 0000000000..c11ae096a7
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/Edit/Masks/HitCircleMasks/Components/HitCirclePiece.cs
@@ -0,0 +1,44 @@
+// 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.Graphics;
+using osu.Game.Rulesets.Osu.Objects;
+using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces;
+using OpenTK;
+
+namespace osu.Game.Rulesets.Osu.Edit.Masks.HitCircleMasks.Components
+{
+ public class HitCirclePiece : CompositeDrawable
+ {
+ private readonly HitCircle hitCircle;
+
+ public HitCirclePiece(HitCircle 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();
+
+ hitCircle.PositionChanged += _ => UpdatePosition();
+ hitCircle.StackHeightChanged += _ => UpdatePosition();
+ hitCircle.ScaleChanged += _ => Scale = new Vector2(hitCircle.Scale);
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(OsuColour colours)
+ {
+ Colour = colours.Yellow;
+
+ UpdatePosition();
+ }
+
+ protected virtual void UpdatePosition() => Position = hitCircle.StackedPosition;
+ }
+}
diff --git a/osu.Game.Rulesets.Osu/Edit/Masks/HitCircleMasks/HitCirclePlacementMask.cs b/osu.Game.Rulesets.Osu/Edit/Masks/HitCircleMasks/HitCirclePlacementMask.cs
new file mode 100644
index 0000000000..0d0acbed7d
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/Edit/Masks/HitCircleMasks/HitCirclePlacementMask.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.Masks.HitCircleMasks.Components;
+using osu.Game.Rulesets.Osu.Objects;
+
+namespace osu.Game.Rulesets.Osu.Edit.Masks.HitCircleMasks
+{
+ public class HitCirclePlacementMask : PlacementMask
+ {
+ public new HitCircle HitObject => (HitCircle)base.HitObject;
+
+ public HitCirclePlacementMask()
+ : 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/Masks/HitCircleMasks/HitCircleSelectionMask.cs b/osu.Game.Rulesets.Osu/Edit/Masks/HitCircleMasks/HitCircleSelectionMask.cs
new file mode 100644
index 0000000000..da46da92a5
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/Edit/Masks/HitCircleMasks/HitCircleSelectionMask.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.Osu.Edit.Masks.HitCircleMasks.Components;
+using osu.Game.Rulesets.Osu.Objects;
+using osu.Game.Rulesets.Osu.Objects.Drawables;
+
+namespace osu.Game.Rulesets.Osu.Edit.Masks.HitCircleMasks
+{
+ public class HitCircleSelectionMask : SelectionMask
+ {
+ public HitCircleSelectionMask(DrawableHitCircle hitCircle)
+ : base(hitCircle)
+ {
+ InternalChild = new HitCirclePiece((HitCircle)hitCircle.HitObject);
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Osu/Edit/Masks/SliderMasks/Components/PathControlPointPiece.cs b/osu.Game.Rulesets.Osu/Edit/Masks/SliderMasks/Components/PathControlPointPiece.cs
new file mode 100644
index 0000000000..70156578b4
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/Edit/Masks/SliderMasks/Components/PathControlPointPiece.cs
@@ -0,0 +1,113 @@
+// 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.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.Osu.Objects;
+using OpenTK;
+
+namespace osu.Game.Rulesets.Osu.Edit.Masks.SliderMasks.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.ControlPoints[index];
+
+ marker.Colour = isSegmentSeparator ? colours.Red : colours.Yellow;
+
+ path.ClearVertices();
+
+ if (index != slider.ControlPoints.Length - 1)
+ {
+ path.AddVertex(Vector2.Zero);
+ path.AddVertex(slider.ControlPoints[index + 1] - slider.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.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.ControlPoints = newControlPoints;
+ slider.Path.Calculate(true);
+
+ return true;
+ }
+
+ protected override bool OnDragEnd(DragEndEvent e) => true;
+
+ private bool isSegmentSeparator => isSegmentSeparatorWithNext || isSegmentSeparatorWithPrevious;
+
+ private bool isSegmentSeparatorWithNext => index < slider.ControlPoints.Length - 1 && slider.ControlPoints[index + 1] == slider.ControlPoints[index];
+
+ private bool isSegmentSeparatorWithPrevious => index > 0 && slider.ControlPoints[index - 1] == slider.ControlPoints[index];
+ }
+}
diff --git a/osu.Game.Rulesets.Osu/Edit/Masks/SliderMasks/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Masks/SliderMasks/Components/PathControlPointVisualiser.cs
new file mode 100644
index 0000000000..1d25f8cd39
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/Edit/Masks/SliderMasks/Components/PathControlPointVisualiser.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.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Game.Rulesets.Osu.Objects;
+
+namespace osu.Game.Rulesets.Osu.Edit.Masks.SliderMasks.Components
+{
+ public class PathControlPointVisualiser : CompositeDrawable
+ {
+ private readonly Slider slider;
+
+ private readonly Container pieces;
+
+ public PathControlPointVisualiser(Slider slider)
+ {
+ this.slider = slider;
+
+ InternalChild = pieces = new Container { RelativeSizeAxes = Axes.Both };
+
+ slider.ControlPointsChanged += _ => updatePathControlPoints();
+ updatePathControlPoints();
+ }
+
+ private void updatePathControlPoints()
+ {
+ while (slider.ControlPoints.Length > pieces.Count)
+ pieces.Add(new PathControlPointPiece(slider, pieces.Count));
+ while (slider.ControlPoints.Length < pieces.Count)
+ pieces.Remove(pieces[pieces.Count - 1]);
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Osu/Edit/Masks/SliderMasks/Components/SliderBodyPiece.cs b/osu.Game.Rulesets.Osu/Edit/Masks/SliderMasks/Components/SliderBodyPiece.cs
new file mode 100644
index 0000000000..006c256d53
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/Edit/Masks/SliderMasks/Components/SliderBodyPiece.cs
@@ -0,0 +1,59 @@
+// 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.Containers;
+using osu.Game.Graphics;
+using osu.Game.Rulesets.Osu.Objects;
+using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces;
+using OpenTK;
+using OpenTK.Graphics;
+
+namespace osu.Game.Rulesets.Osu.Edit.Masks.SliderMasks.Components
+{
+ public class SliderBodyPiece : CompositeDrawable
+ {
+ private readonly Slider slider;
+ private readonly ManualSliderBody body;
+
+ public SliderBodyPiece(Slider slider)
+ {
+ this.slider = slider;
+
+ InternalChild = body = new ManualSliderBody
+ {
+ AccentColour = Color4.Transparent,
+ PathWidth = slider.Scale * 64
+ };
+
+ slider.PositionChanged += _ => updatePosition();
+ slider.ScaleChanged += _ => body.PathWidth = slider.Scale * 64;
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(OsuColour colours)
+ {
+ body.BorderColour = colours.Yellow;
+
+ updatePosition();
+ }
+
+ private void updatePosition() => Position = slider.StackedPosition;
+
+ protected override void Update()
+ {
+ base.Update();
+
+ slider.Path.Calculate();
+
+ 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/Masks/SliderMasks/Components/SliderCirclePiece.cs b/osu.Game.Rulesets.Osu/Edit/Masks/SliderMasks/Components/SliderCirclePiece.cs
new file mode 100644
index 0000000000..7864429d93
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/Edit/Masks/SliderMasks/Components/SliderCirclePiece.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.Game.Rulesets.Osu.Edit.Masks.HitCircleMasks.Components;
+using osu.Game.Rulesets.Osu.Objects;
+
+namespace osu.Game.Rulesets.Osu.Edit.Masks.SliderMasks.Components
+{
+ public class SliderCirclePiece : HitCirclePiece
+ {
+ private readonly Slider slider;
+ private readonly SliderPosition position;
+
+ public SliderCirclePiece(Slider slider, SliderPosition position)
+ : base(slider.HeadCircle)
+ {
+ this.slider = slider;
+ this.position = position;
+
+ slider.ControlPointsChanged += _ => UpdatePosition();
+ }
+
+ 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/Masks/SliderMasks/SliderCircleSelectionMask.cs b/osu.Game.Rulesets.Osu/Edit/Masks/SliderMasks/SliderCircleSelectionMask.cs
new file mode 100644
index 0000000000..a1b3fd545c
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/Edit/Masks/SliderMasks/SliderCircleSelectionMask.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 osu.Game.Rulesets.Edit;
+using osu.Game.Rulesets.Osu.Edit.Masks.SliderMasks.Components;
+using osu.Game.Rulesets.Osu.Objects;
+using osu.Game.Rulesets.Osu.Objects.Drawables;
+
+namespace osu.Game.Rulesets.Osu.Edit.Masks.SliderMasks
+{
+ public class SliderCircleSelectionMask : SelectionMask
+ {
+ public SliderCircleSelectionMask(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/Masks/SliderMasks/SliderPlacementMask.cs b/osu.Game.Rulesets.Osu/Edit/Masks/SliderMasks/SliderPlacementMask.cs
new file mode 100644
index 0000000000..12e768d58e
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/Edit/Masks/SliderMasks/SliderPlacementMask.cs
@@ -0,0 +1,180 @@
+// 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.IEnumerableExtensions;
+using osu.Framework.Graphics;
+using osu.Framework.Input.Events;
+using osu.Framework.MathUtils;
+using osu.Game.Graphics;
+using osu.Game.Rulesets.Edit;
+using osu.Game.Rulesets.Objects.Types;
+using osu.Game.Rulesets.Osu.Edit.Masks.SliderMasks.Components;
+using OpenTK;
+using OpenTK.Input;
+
+namespace osu.Game.Rulesets.Osu.Edit.Masks.SliderMasks
+{
+ public class SliderPlacementMask : PlacementMask
+ {
+ public new Objects.Slider HitObject => (Objects.Slider)base.HitObject;
+
+ private readonly List segments = new List();
+ private Vector2 cursor;
+
+ private PlacementState state;
+
+ public SliderPlacementMask()
+ : 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()
+ {
+ for (int i = 0; i < segments.Count; i++)
+ segments[i].Calculate(i == segments.Count - 1 ? (Vector2?)cursor : null);
+
+ HitObject.ControlPoints = segments.SelectMany(s => s.ControlPoints).Concat(cursor.Yield()).ToArray();
+ HitObject.PathType = HitObject.ControlPoints.Length > 2 ? PathType.Bezier : PathType.Linear;
+ HitObject.Distance = segments.Sum(s => s.Distance);
+ }
+
+ private void setState(PlacementState newState)
+ {
+ state = newState;
+ }
+
+ private enum PlacementState
+ {
+ Initial,
+ Body,
+ }
+
+ private class Segment
+ {
+ public float Distance { get; private set; }
+
+ public readonly List ControlPoints = new List();
+
+ public Segment(Vector2 offset)
+ {
+ ControlPoints.Add(offset);
+ }
+
+ public void Calculate(Vector2? cursor = null)
+ {
+ Span allControlPoints = stackalloc Vector2[ControlPoints.Count + (cursor.HasValue ? 1 : 0)];
+
+ for (int i = 0; i < ControlPoints.Count; i++)
+ allControlPoints[i] = ControlPoints[i];
+ if (cursor.HasValue)
+ allControlPoints[allControlPoints.Length - 1] = cursor.Value;
+
+ List result;
+
+ switch (allControlPoints.Length)
+ {
+ case 1:
+ case 2:
+ result = PathApproximator.ApproximateLinear(allControlPoints);
+ break;
+ default:
+ result = PathApproximator.ApproximateBezier(allControlPoints);
+ break;
+ }
+
+ Distance = 0;
+ for (int i = 0; i < result.Count - 1; i++)
+ Distance += Vector2.Distance(result[i], result[i + 1]);
+ }
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Osu/Edit/Masks/SliderMasks/SliderPosition.cs b/osu.Game.Rulesets.Osu/Edit/Masks/SliderMasks/SliderPosition.cs
new file mode 100644
index 0000000000..01c1871131
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/Edit/Masks/SliderMasks/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.Masks.SliderMasks
+{
+ public enum SliderPosition
+ {
+ Start,
+ End
+ }
+}
diff --git a/osu.Game.Rulesets.Osu/Edit/Masks/SliderMasks/SliderSelectionMask.cs b/osu.Game.Rulesets.Osu/Edit/Masks/SliderMasks/SliderSelectionMask.cs
new file mode 100644
index 0000000000..b79b0ba1fb
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/Edit/Masks/SliderMasks/SliderSelectionMask.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 osu.Framework.Graphics;
+using osu.Game.Rulesets.Edit;
+using osu.Game.Rulesets.Osu.Edit.Masks.SliderMasks.Components;
+using osu.Game.Rulesets.Osu.Objects;
+using osu.Game.Rulesets.Osu.Objects.Drawables;
+using OpenTK;
+
+namespace osu.Game.Rulesets.Osu.Edit.Masks.SliderMasks
+{
+ public class SliderSelectionMask : SelectionMask
+ {
+ private readonly SliderCircleSelectionMask headMask;
+
+ public SliderSelectionMask(DrawableSlider slider)
+ : base(slider)
+ {
+ var sliderObject = (Slider)slider.HitObject;
+
+ InternalChildren = new Drawable[]
+ {
+ new SliderBodyPiece(sliderObject),
+ headMask = new SliderCircleSelectionMask(slider.HeadCircle, sliderObject, SliderPosition.Start),
+ new SliderCircleSelectionMask(slider.TailCircle, sliderObject, SliderPosition.End),
+ new PathControlPointVisualiser(sliderObject),
+ };
+ }
+
+ public override Vector2 SelectionPoint => headMask.SelectionPoint;
+ }
+}
diff --git a/osu.Game.Rulesets.Osu/Edit/Masks/SpinnerMasks/Components/SpinnerPiece.cs b/osu.Game.Rulesets.Osu/Edit/Masks/SpinnerMasks/Components/SpinnerPiece.cs
new file mode 100644
index 0000000000..0d9609facf
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/Edit/Masks/SpinnerMasks/Components/SpinnerPiece.cs
@@ -0,0 +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.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 OpenTK;
+
+namespace osu.Game.Rulesets.Osu.Edit.Masks.SpinnerMasks.Components
+{
+ public class SpinnerPiece : CompositeDrawable
+ {
+ private readonly Spinner spinner;
+ private readonly CircularContainer circle;
+
+ public SpinnerPiece(Spinner spinner)
+ {
+ this.spinner = spinner;
+
+ Origin = Anchor.Centre;
+
+ RelativeSizeAxes = Axes.Both;
+ FillMode = FillMode.Fit;
+ Size = new Vector2(1.3f);
+
+ RingPiece ring;
+ 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);
+
+ spinner.PositionChanged += _ => updatePosition();
+ spinner.StackHeightChanged += _ => updatePosition();
+ spinner.ScaleChanged += _ => ring.Scale = new Vector2(spinner.Scale);
+
+ updatePosition();
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(OsuColour colours)
+ {
+ Colour = colours.Yellow;
+ }
+
+ private void updatePosition() => Position = spinner.Position;
+
+ public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => circle.ReceivePositionalInputAt(screenSpacePos);
+ }
+}
diff --git a/osu.Game.Rulesets.Osu/Edit/Masks/SpinnerMasks/SpinnerPlacementMask.cs b/osu.Game.Rulesets.Osu/Edit/Masks/SpinnerMasks/SpinnerPlacementMask.cs
new file mode 100644
index 0000000000..cd5d6cff04
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/Edit/Masks/SpinnerMasks/SpinnerPlacementMask.cs
@@ -0,0 +1,45 @@
+// 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.Masks.SpinnerMasks.Components;
+using osu.Game.Rulesets.Osu.Objects;
+using osu.Game.Rulesets.Osu.UI;
+
+namespace osu.Game.Rulesets.Osu.Edit.Masks.SpinnerMasks
+{
+ public class SpinnerPlacementMask : PlacementMask
+ {
+ public new Spinner HitObject => (Spinner)base.HitObject;
+
+ private readonly SpinnerPiece piece;
+
+ private bool isPlacingEnd;
+
+ public SpinnerPlacementMask()
+ : 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);
+ }
+
+ return true;
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Osu/Edit/Masks/SpinnerMasks/SpinnerSelectionMask.cs b/osu.Game.Rulesets.Osu/Edit/Masks/SpinnerMasks/SpinnerSelectionMask.cs
new file mode 100644
index 0000000000..0e47bd2a8b
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/Edit/Masks/SpinnerMasks/SpinnerSelectionMask.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 osu.Game.Rulesets.Edit;
+using osu.Game.Rulesets.Osu.Edit.Masks.SpinnerMasks.Components;
+using osu.Game.Rulesets.Osu.Objects;
+using osu.Game.Rulesets.Osu.Objects.Drawables;
+using OpenTK;
+
+namespace osu.Game.Rulesets.Osu.Edit.Masks.SpinnerMasks
+{
+ public class SpinnerSelectionMask : SelectionMask
+ {
+ private readonly SpinnerPiece piece;
+
+ public SpinnerSelectionMask(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/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs
index d6972d55d2..005ccec151 100644
--- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs
+++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs
@@ -8,7 +8,9 @@ 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.Masks.HitCircleMasks;
+using osu.Game.Rulesets.Osu.Edit.Masks.SliderMasks;
+using osu.Game.Rulesets.Osu.Edit.Masks.SpinnerMasks;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables;
using osu.Game.Rulesets.Osu.UI;
@@ -16,32 +18,35 @@ using osu.Game.Rulesets.UI;
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()
};
protected override Container CreateLayerContainer() => new PlayfieldAdjustmentContainer { RelativeSizeAxes = Axes.Both };
- public override HitObjectMask CreateMaskFor(DrawableHitObject hitObject)
+ public override SelectionMask CreateMaskFor(DrawableHitObject hitObject)
{
switch (hitObject)
{
case DrawableHitCircle circle:
- return new HitCircleMask(circle);
+ return new HitCircleSelectionMask(circle);
case DrawableSlider slider:
- return new SliderMask(slider);
+ return new SliderSelectionMask(slider);
+ case DrawableSpinner spinner:
+ return new SpinnerSelectionMask(spinner);
}
return base.CreateMaskFor(hitObject);
diff --git a/osu.Game.Rulesets.Osu/Edit/SliderCompositionTool.cs b/osu.Game.Rulesets.Osu/Edit/SliderCompositionTool.cs
new file mode 100644
index 0000000000..fd0430ce4c
--- /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.Masks.SliderMasks;
+using osu.Game.Rulesets.Osu.Objects;
+
+namespace osu.Game.Rulesets.Osu.Edit
+{
+ public class SliderCompositionTool : HitObjectCompositionTool
+ {
+ public SliderCompositionTool()
+ : base(nameof(Slider))
+ {
+ }
+
+ public override PlacementMask CreatePlacementMask() => new SliderPlacementMask();
+ }
+}
diff --git a/osu.Game.Rulesets.Osu/Edit/SpinnerCompositionTool.cs b/osu.Game.Rulesets.Osu/Edit/SpinnerCompositionTool.cs
new file mode 100644
index 0000000000..a1419fe281
--- /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.Masks.SpinnerMasks;
+using osu.Game.Rulesets.Osu.Objects;
+
+namespace osu.Game.Rulesets.Osu.Edit
+{
+ public class SpinnerCompositionTool : HitObjectCompositionTool
+ {
+ public SpinnerCompositionTool()
+ : base(nameof(Spinner))
+ {
+ }
+
+ public override PlacementMask CreatePlacementMask() => new SpinnerPlacementMask();
+ }
+}
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModHardRock.cs b/osu.Game.Rulesets.Osu/Mods/OsuModHardRock.cs
index 7a30e6b134..e01d71e1f8 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModHardRock.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModHardRock.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 System.Linq;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Game.Rulesets.Mods;
@@ -33,11 +32,12 @@ 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 List();
- slider.ControlPoints.ForEach(c => newControlPoints.Add(new Vector2(c.X, -c.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);
slider.ControlPoints = newControlPoints;
- slider.Curve?.Calculate(); // Recalculate the slider curve
+ slider.Path?.Calculate(); // Recalculate the slider curve
}
}
}
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs
index 4bdddcef11..e663989eeb 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs
@@ -61,6 +61,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
Size = circle.DrawSize;
HitObject.PositionChanged += _ => Position = HitObject.StackedPosition;
+ HitObject.StackHeightChanged += _ => Position = HitObject.StackedPosition;
+ HitObject.ScaleChanged += s => Scale = new Vector2(s);
}
public override Color4 AccentColour
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs
index 89f380db4e..514ae09064 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs
@@ -23,7 +23,7 @@ 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;
public DrawableSlider(Slider s)
@@ -38,7 +38,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
InternalChildren = new Drawable[]
{
- Body = new SliderBody(s)
+ Body = new SnakingSliderBody(s)
{
PathWidth = s.Scale * 64,
},
@@ -85,6 +85,13 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
}
HitObject.PositionChanged += _ => Position = HitObject.StackedPosition;
+ HitObject.ScaleChanged += _ =>
+ {
+ Body.PathWidth = HitObject.Scale * 64;
+ Ball.Scale = new Vector2(HitObject.Scale);
+ };
+
+ slider.ControlPointsChanged += _ => Body.Refresh();
}
public override Color4 AccentColour
@@ -119,7 +126,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..6a836679a2 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs
@@ -16,7 +16,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
{
this.slider = slider;
- Position = HitObject.Position - slider.Position;
+ h.PositionChanged += _ => updatePosition();
+ slider.ControlPointsChanged += _ => updatePosition();
+
+ updatePosition();
}
protected override void Update()
@@ -33,5 +36,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..cc88a6718b 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs
@@ -8,6 +8,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
{
public class DrawableSliderTail : DrawableOsuHitObject, IRequireTracking
{
+ private readonly Slider slider;
+
///
/// The judgement text is provided by the .
///
@@ -18,6 +20,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
public DrawableSliderTail(Slider slider, SliderTailCircle hitCircle)
: base(hitCircle)
{
+ this.slider = slider;
+
Origin = Anchor.Centre;
RelativeSizeAxes = Axes.Both;
@@ -25,7 +29,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
AlwaysPresent = true;
- Position = HitObject.Position - slider.Position;
+ hitCircle.PositionChanged += _ => updatePosition();
+ slider.ControlPointsChanged += _ => updatePosition();
+
+ updatePosition();
}
protected override void CheckForResult(bool userTriggered, double timeOffset)
@@ -33,5 +40,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/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs
index 51b1990a21..f3846bd52f 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs
@@ -112,6 +112,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
Alpha = 0
}
};
+
+ s.PositionChanged += _ => Position = s.Position;
}
public float Progress => MathHelper.Clamp(Disc.RotationAbsolute / 360 / Spinner.SpinsRequired, 0, 1);
@@ -167,7 +169,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
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);
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..9d239c15f2
--- /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 OpenTK;
+
+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 30140484de..acb3ee92ff 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/NumberPiece.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/NumberPiece.cs
@@ -4,7 +4,6 @@
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
-using osu.Framework.Graphics.Sprites;
using osu.Game.Graphics.Sprites;
using OpenTK.Graphics;
using osu.Framework.Graphics.Shapes;
@@ -14,7 +13,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
{
public class NumberPiece : Container
{
- private readonly SpriteText number;
+ private readonly SkinnableSpriteText number;
public string Text
{
@@ -41,15 +40,14 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
},
Child = new Box()
}, s => s.GetTexture("Play/osu/hitcircle") == null),
- number = new OsuSpriteText
+ number = new SkinnableSpriteText("Play/osu/number-text", _ => new OsuSpriteText
{
- Text = @"1",
Font = @"Venera",
UseFullGlyphHeight = false,
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
TextSize = 40,
- Alpha = 1
+ }, restrictSize: false)
+ {
+ Text = @"1"
}
};
}
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBody.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBody.cs
index f4ccf673e9..ca2daa3adb 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 OpenTK.Graphics;
+using OpenTK.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..0e6f3ad16c
--- /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 OpenTK;
+
+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/OsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs
index fdf5aaffa8..67396c7ae4 100644
--- a/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs
+++ b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs
@@ -16,13 +16,15 @@ namespace osu.Game.Rulesets.Osu.Objects
public const double OBJECT_RADIUS = 64;
public event Action PositionChanged;
+ public event Action StackHeightChanged;
+ public event Action ScaleChanged;
public double TimePreempt = 600;
public double TimeFadeIn = 400;
private Vector2 position;
- public Vector2 Position
+ public virtual Vector2 Position
{
get => position;
set
@@ -44,13 +46,39 @@ namespace osu.Game.Rulesets.Osu.Objects
public Vector2 StackedEndPosition => EndPosition + StackOffset;
- public virtual int StackHeight { get; set; }
+ private int stackHeight;
+
+ public int StackHeight
+ {
+ get => stackHeight;
+ set
+ {
+ if (stackHeight == value)
+ return;
+ stackHeight = value;
+
+ StackHeightChanged?.Invoke(value);
+ }
+ }
public Vector2 StackOffset => new Vector2(StackHeight * Scale * -6.4f);
public double Radius => OBJECT_RADIUS * Scale;
- public float Scale { get; set; } = 1;
+ private float scale = 1;
+
+ public float Scale
+ {
+ get => scale;
+ set
+ {
+ if (scale == value)
+ return;
+ scale = value;
+
+ ScaleChanged?.Invoke(value);
+ }
+ }
public virtual bool NewCombo { get; set; }
diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs
index c33e089178..3680c38945 100644
--- a/osu.Game.Rulesets.Osu/Objects/Slider.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs
@@ -22,7 +22,9 @@ namespace osu.Game.Rulesets.Osu.Objects
///
private const float base_scoring_distance = 100;
- public double EndTime => StartTime + this.SpanCount() * Curve.Distance / Velocity;
+ public event Action ControlPointsChanged;
+
+ 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 +52,49 @@ namespace osu.Game.Rulesets.Osu.Objects
}
}
- public SliderCurve Curve { get; } = new SliderCurve();
+ public SliderPath Path { get; } = new SliderPath();
- public List ControlPoints
+ public Vector2[] ControlPoints
{
- get { return Curve.ControlPoints; }
- set { Curve.ControlPoints = value; }
+ get => Path.ControlPoints;
+ set
+ {
+ if (Path.ControlPoints == value)
+ return;
+ Path.ControlPoints = value;
+
+ ControlPointsChanged?.Invoke(value);
+
+ if (TailCircle != null)
+ TailCircle.Position = EndPosition;
+ }
}
- public CurveType CurveType
+ public PathType PathType
{
- get { return Curve.CurveType; }
- set { Curve.CurveType = value; }
+ get { return Path.PathType; }
+ set { Path.PathType = value; }
}
public double Distance
{
- get { return Curve.Distance; }
- set { Curve.Distance = value; }
+ get { return Path.Distance; }
+ set { Path.Distance = value; }
+ }
+
+ public override Vector2 Position
+ {
+ get => base.Position;
+ set
+ {
+ base.Position = value;
+
+ if (HeadCircle != null)
+ HeadCircle.Position = value;
+
+ if (TailCircle != null)
+ TailCircle.Position = EndPosition;
+ }
}
public double? LegacyLastTickOffset { get; set; }
@@ -93,8 +120,21 @@ namespace osu.Game.Rulesets.Osu.Objects
///
public double SpanDuration => Duration / this.SpanCount();
- public double Velocity;
- public double TickDistance;
+ ///
+ /// Velocity of this .
+ ///
+ public double Velocity { get; private set; }
+
+ ///
+ /// Spacing between s of this .
+ ///
+ public double TickDistance { get; private set; }
+
+ ///
+ /// An extra multiplier that affects the number of s generated by this .
+ /// An increase in this value increases , which reduces the number of ticks generated.
+ ///
+ public double TickDistanceMultiplier = 1;
public HitCircle HeadCircle;
public SliderTailCircle TailCircle;
@@ -109,7 +149,7 @@ namespace osu.Game.Rulesets.Osu.Objects
double scoringDistance = base_scoring_distance * difficulty.SliderMultiplier * difficultyPoint.SpeedMultiplier;
Velocity = scoringDistance / timingPoint.BeatLength;
- TickDistance = scoringDistance / difficulty.SliderTickRate;
+ TickDistance = scoringDistance / difficulty.SliderTickRate * TickDistanceMultiplier;
}
protected override void CreateNestedHitObjects()
@@ -150,7 +190,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;
@@ -189,7 +229,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
@@ -207,7 +247,7 @@ 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 = getNodeSamples(1 + repeatIndex)
diff --git a/osu.Game.Rulesets.Osu/Objects/Spinner.cs b/osu.Game.Rulesets.Osu/Objects/Spinner.cs
index 1c60fd4831..1270685ab5 100644
--- a/osu.Game.Rulesets.Osu/Objects/Spinner.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Spinner.cs
@@ -7,6 +7,7 @@ using osu.Game.Rulesets.Objects.Types;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Osu.Judgements;
+using OpenTK;
namespace osu.Game.Rulesets.Osu.Objects
{
@@ -31,5 +32,10 @@ namespace osu.Game.Rulesets.Osu.Objects
}
public override Judgement CreateJudgement() => new OsuJudgement();
+
+ public override void OffsetPosition(Vector2 offset)
+ {
+ // for now we don't want to allow spinners to be moved around.
+ }
}
}
diff --git a/osu.Game.Rulesets.Osu/UI/OsuRulesetContainer.cs b/osu.Game.Rulesets.Osu/UI/OsuRulesetContainer.cs
index 0ea8d3ede8..ea5718bed2 100644
--- a/osu.Game.Rulesets.Osu/UI/OsuRulesetContainer.cs
+++ b/osu.Game.Rulesets.Osu/UI/OsuRulesetContainer.cs
@@ -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.Taiko/UI/TaikoRulesetContainer.cs b/osu.Game.Rulesets.Taiko/UI/TaikoRulesetContainer.cs
index 3d08bffe0f..c94ced3390 100644
--- a/osu.Game.Rulesets.Taiko/UI/TaikoRulesetContainer.cs
+++ b/osu.Game.Rulesets.Taiko/UI/TaikoRulesetContainer.cs
@@ -78,7 +78,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 60bd87dda7..ad62bcd7aa 100644
--- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs
+++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs
@@ -8,12 +8,14 @@ using OpenTK.Graphics;
using osu.Game.Tests.Resources;
using System.Linq;
using osu.Game.Audio;
+using osu.Game.Beatmaps;
using osu.Game.Rulesets.Objects.Types;
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;
@@ -22,6 +24,25 @@ namespace osu.Game.Tests.Beatmaps.Formats
[TestFixture]
public class LegacyBeatmapDecoderTest
{
+ [Test]
+ public void TestDecodeBeatmapVersion()
+ {
+ using (var resStream = Resource.OpenResource("beatmap-version.osu"))
+ using (var stream = new StreamReader(resStream))
+ {
+ var decoder = Decoder.GetDecoder(stream);
+
+ stream.BaseStream.Position = 0;
+ stream.DiscardBufferedData();
+
+ var working = new TestWorkingBeatmap(decoder.Decode(stream));
+
+ Assert.AreEqual(6, working.BeatmapInfo.BeatmapVersion);
+ Assert.AreEqual(6, working.Beatmap.BeatmapInfo.BeatmapVersion);
+ Assert.AreEqual(6, working.GetPlayableBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo.BeatmapVersion);
+ }
+ }
+
[Test]
public void TestDecodeBeatmapGeneral()
{
diff --git a/osu.Game.Tests/Resources/beatmap-version.osu b/osu.Game.Tests/Resources/beatmap-version.osu
new file mode 100644
index 0000000000..5749054ac4
--- /dev/null
+++ b/osu.Game.Tests/Resources/beatmap-version.osu
@@ -0,0 +1 @@
+osu file format v6
\ No newline at end of file
diff --git a/osu.Game.Tests/Visual/TestCaseHitObjectComposer.cs b/osu.Game.Tests/Visual/TestCaseHitObjectComposer.cs
index 5df371dd09..61647ffdc5 100644
--- a/osu.Game.Tests/Visual/TestCaseHitObjectComposer.cs
+++ b/osu.Game.Tests/Visual/TestCaseHitObjectComposer.cs
@@ -13,14 +13,18 @@ using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Edit;
+using osu.Game.Rulesets.Osu.Edit.Masks.HitCircleMasks;
+using osu.Game.Rulesets.Osu.Edit.Masks.HitCircleMasks.Components;
using osu.Game.Rulesets.Osu.Objects;
+using osu.Game.Screens.Edit.Screens.Compose;
using osu.Game.Screens.Edit.Screens.Compose.Layers;
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[]
{
@@ -29,9 +33,14 @@ namespace osu.Game.Tests.Visual
typeof(HitObjectComposer),
typeof(OsuHitObjectComposer),
typeof(HitObjectMaskLayer),
- typeof(NotNullAttribute)
+ typeof(NotNullAttribute),
+ typeof(HitCirclePiece),
+ typeof(HitCircleSelectionMask),
+ typeof(HitCirclePlacementMask),
};
+ private HitObjectComposer composer;
+
[BackgroundDependencyLoader]
private void load()
{
@@ -44,14 +53,12 @@ namespace osu.Game.Tests.Visual
new Slider
{
Position = new Vector2(128, 256),
- ControlPoints = new List
+ ControlPoints = new[]
{
Vector2.Zero,
new Vector2(216, 0),
},
- Distance = 400,
- Velocity = 1,
- TickDistance = 100,
+ Distance = 216,
Scale = 0.5f,
}
},
@@ -61,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.props b/osu.Game.props
index ec859e64a5..4bcac479a0 100644
--- a/osu.Game.props
+++ b/osu.Game.props
@@ -1,7 +1,7 @@
- 7
+ 7.2
..\app.manifest
diff --git a/osu.Game/Beatmaps/Beatmap.cs b/osu.Game/Beatmaps/Beatmap.cs
index 9aabb434a3..4ef7b28d49 100644
--- a/osu.Game/Beatmaps/Beatmap.cs
+++ b/osu.Game/Beatmaps/Beatmap.cs
@@ -48,7 +48,7 @@ namespace osu.Game.Beatmaps
[JsonConverter(typeof(TypedListConverter))]
public List HitObjects = new List();
- IEnumerable IBeatmap.HitObjects => HitObjects;
+ IReadOnlyList IBeatmap.HitObjects => HitObjects;
public virtual IEnumerable GetStatistics() => Enumerable.Empty();
diff --git a/osu.Game/Beatmaps/BeatmapConverter.cs b/osu.Game/Beatmaps/BeatmapConverter.cs
index a1bb70135a..3cb7833a12 100644
--- a/osu.Game/Beatmaps/BeatmapConverter.cs
+++ b/osu.Game/Beatmaps/BeatmapConverter.cs
@@ -55,39 +55,40 @@ namespace osu.Game.Beatmaps
beatmap.BeatmapInfo = original.BeatmapInfo;
beatmap.ControlPointInfo = original.ControlPointInfo;
- beatmap.HitObjects = original.HitObjects.SelectMany(h => convert(h, original)).ToList();
+ beatmap.HitObjects = convertHitObjects(original.HitObjects, original);
beatmap.Breaks = original.Breaks;
return beatmap;
}
- ///
- /// Converts a hit object.
- ///
- /// The hit object to convert.
- /// The un-converted Beatmap.
- /// The converted hit object.
- private IEnumerable convert(HitObject original, IBeatmap beatmap)
+ private List convertHitObjects(IReadOnlyList hitObjects, IBeatmap beatmap)
{
- // Check if the hitobject is already the converted type
- T tObject = original as T;
- if (tObject != null)
- {
- yield return tObject;
- yield break;
- }
+ var result = new List(hitObjects.Count);
- var converted = ConvertHitObject(original, beatmap).ToList();
- ObjectConverted?.Invoke(original, converted);
-
- // Convert the hit object
- foreach (var obj in converted)
+ foreach (var obj in hitObjects)
{
- if (obj == null)
+ if (obj is T tObj)
+ {
+ result.Add(tObj);
continue;
+ }
- yield return obj;
+ var converted = ConvertHitObject(obj, beatmap);
+
+ if (ObjectConverted != null)
+ {
+ converted = converted.ToList();
+ ObjectConverted.Invoke(obj, converted);
+ }
+
+ foreach (var c in converted)
+ {
+ if (c != null)
+ result.Add(c);
+ }
}
+
+ return result;
}
///
diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs
index 1aa4818393..3e1f3bdf54 100644
--- a/osu.Game/Beatmaps/BeatmapInfo.cs
+++ b/osu.Game/Beatmaps/BeatmapInfo.cs
@@ -19,7 +19,6 @@ namespace osu.Game.Beatmaps
[JsonIgnore]
public int ID { get; set; }
- //TODO: should be in database
public int BeatmapVersion;
private int? onlineBeatmapID;
diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs
index aa653d88f9..24c68d392b 100644
--- a/osu.Game/Beatmaps/BeatmapManager.cs
+++ b/osu.Game/Beatmaps/BeatmapManager.cs
@@ -148,11 +148,12 @@ namespace osu.Game.Beatmaps
///
/// The to be downloaded.
/// Whether the beatmap should be downloaded without video. Defaults to false.
- public void Download(BeatmapSetInfo beatmapSetInfo, bool noVideo = false)
+ /// Downloading can happen
+ public bool Download(BeatmapSetInfo beatmapSetInfo, bool noVideo = false)
{
var existing = GetExistingDownload(beatmapSetInfo);
- if (existing != null || api == null) return;
+ if (existing != null || api == null) return false;
if (!api.LocalUser.Value.IsSupporter)
{
@@ -161,7 +162,7 @@ namespace osu.Game.Beatmaps
Icon = FontAwesome.fa_superpowers,
Text = "You gotta be an osu!supporter to download for now 'yo"
});
- return;
+ return false;
}
var downloadNotification = new DownloadNotification
@@ -227,6 +228,7 @@ namespace osu.Game.Beatmaps
// don't run in the main api queue as this is a long-running task.
Task.Factory.StartNew(() => request.Perform(api), TaskCreationOptions.LongRunning);
BeatmapDownloadBegan?.Invoke(request);
+ return true;
}
protected override void PresentCompletedImport(IEnumerable imported)
@@ -348,7 +350,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/ControlPoints/ControlPointInfo.cs b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs
index dad69321a5..f064d53358 100644
--- a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs
+++ b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs
@@ -55,14 +55,14 @@ namespace osu.Game.Beatmaps.ControlPoints
///
/// The time to find the sound control point at.
/// The sound control point.
- public SampleControlPoint SamplePointAt(double time) => binarySearch(SamplePoints, time, SamplePoints.FirstOrDefault());
+ public SampleControlPoint SamplePointAt(double time) => binarySearch(SamplePoints, time, SamplePoints.Count > 0 ? SamplePoints[0] : null);
///
/// Finds the timing control point that is active at .
///
/// The time to find the timing control point at.
/// The timing control point.
- public TimingControlPoint TimingPointAt(double time) => binarySearch(TimingPoints, time, TimingPoints.FirstOrDefault());
+ public TimingControlPoint TimingPointAt(double time) => binarySearch(TimingPoints, time, TimingPoints.Count > 0 ? TimingPoints[0] : null);
///
/// Finds the maximum BPM represented by any timing control point.
@@ -104,17 +104,26 @@ namespace osu.Game.Beatmaps.ControlPoints
if (time < list[0].Time)
return prePoint ?? new T();
- int index = list.BinarySearch(new T { Time = time });
+ if (time >= list[list.Count - 1].Time)
+ return list[list.Count - 1];
- // Check if we've found an exact match (t == time)
- if (index >= 0)
- return list[index];
+ int l = 0;
+ int r = list.Count - 2;
- index = ~index;
+ while (l <= r)
+ {
+ int pivot = l + ((r - l) >> 1);
- // BinarySearch will return the index of the first element _greater_ than the search
- // This is the inactive point - the active point is the one before it (index - 1)
- return list[index - 1];
+ if (list[pivot].Time < time)
+ l = pivot + 1;
+ else if (list[pivot].Time > time)
+ r = pivot - 1;
+ else
+ return list[pivot];
+ }
+
+ // l will be the first control point with Time > time, but we want the one before it
+ return list[l - 1];
}
}
}
diff --git a/osu.Game/Beatmaps/Drawables/BeatmapSetDownloader.cs b/osu.Game/Beatmaps/Drawables/BeatmapSetDownloader.cs
index 6f4d4c0d6f..5b5dbec9c8 100644
--- a/osu.Game/Beatmaps/Drawables/BeatmapSetDownloader.cs
+++ b/osu.Game/Beatmaps/Drawables/BeatmapSetDownloader.cs
@@ -71,9 +71,11 @@ namespace osu.Game.Beatmaps.Drawables
if (DownloadState.Value > DownloadStatus.NotDownloaded)
return;
- beatmaps.Download(set, noVideo);
-
- DownloadState.Value = DownloadStatus.Downloading;
+ if (beatmaps.Download(set, noVideo))
+ {
+ // Only change state if download can happen
+ DownloadState.Value = DownloadStatus.Downloading;
+ }
}
private void setAdded(BeatmapSetInfo s)
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/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs
index 5b5bc5d936..71b7a65ccb 100644
--- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs
+++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs
@@ -5,6 +5,7 @@ using System;
using System.Globalization;
using System.IO;
using System.Linq;
+using osu.Framework.IO.File;
using osu.Game.Beatmaps.Timing;
using osu.Game.Rulesets.Objects.Legacy;
using osu.Game.Beatmaps.ControlPoints;
@@ -58,7 +59,7 @@ namespace osu.Game.Beatmaps.Formats
hitObject.ApplyDefaults(this.beatmap.ControlPointInfo, this.beatmap.BeatmapInfo.BaseDifficulty);
}
- protected override bool ShouldSkipLine(string line) => base.ShouldSkipLine(line) || line.StartsWith(" ") || line.StartsWith("_");
+ protected override bool ShouldSkipLine(string line) => base.ShouldSkipLine(line) || line.StartsWith(" ", StringComparison.Ordinal) || line.StartsWith("_", StringComparison.Ordinal);
protected override void ParseLine(Beatmap beatmap, Section section, string line)
{
@@ -100,7 +101,7 @@ namespace osu.Game.Beatmaps.Formats
switch (pair.Key)
{
case @"AudioFilename":
- metadata.AudioFile = pair.Value;
+ metadata.AudioFile = FileSafety.PathStandardise(pair.Value);
break;
case @"AudioLeadIn":
beatmap.BeatmapInfo.AudioLeadIn = int.Parse(pair.Value);
@@ -256,7 +257,7 @@ namespace osu.Game.Beatmaps.Formats
{
case EventType.Background:
string filename = split[2].Trim('"');
- beatmap.BeatmapInfo.Metadata.BackgroundFile = filename;
+ beatmap.BeatmapInfo.Metadata.BackgroundFile = FileSafety.PathStandardise(filename);
break;
case EventType.Break:
var breakEvent = new BreakPeriod
diff --git a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs
index a9e1e4c55d..2ef7c5de30 100644
--- a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs
+++ b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs
@@ -69,7 +69,7 @@ namespace osu.Game.Beatmaps.Formats
protected string StripComments(string line)
{
- var index = line.IndexOf("//", StringComparison.Ordinal);
+ var index = line.AsSpan().IndexOf("//".AsSpan());
if (index > 0)
return line.Substring(0, index);
return line;
diff --git a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs
index a73a32325a..375c0b29c0 100644
--- a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs
+++ b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs
@@ -298,6 +298,6 @@ namespace osu.Game.Beatmaps.Formats
}
}
- private string cleanFilename(string path) => FileSafety.PathStandardise(FileSafety.PathSanitise(path.Trim('\"')));
+ private string cleanFilename(string path) => FileSafety.PathStandardise(path.Trim('"'));
}
}
diff --git a/osu.Game/Beatmaps/IBeatmap.cs b/osu.Game/Beatmaps/IBeatmap.cs
index fe20bce98a..3d8b94dc46 100644
--- a/osu.Game/Beatmaps/IBeatmap.cs
+++ b/osu.Game/Beatmaps/IBeatmap.cs
@@ -39,7 +39,7 @@ namespace osu.Game.Beatmaps
///
/// The hitobjects contained by this beatmap.
///
- IEnumerable HitObjects { get; }
+ IReadOnlyList HitObjects { get; }
///
/// Returns statistics for the contained in this beatmap.
diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs
index e0a22460ef..5b76122616 100644
--- a/osu.Game/Beatmaps/WorkingBeatmap.cs
+++ b/osu.Game/Beatmaps/WorkingBeatmap.cs
@@ -41,8 +41,13 @@ namespace osu.Game.Beatmaps
beatmap = new RecyclableLazy(() =>
{
var b = GetBeatmap() ?? new Beatmap();
- // use the database-backed info.
+
+ // The original beatmap version needs to be preserved as the database doesn't contain it
+ BeatmapInfo.BeatmapVersion = b.BeatmapInfo.BeatmapVersion;
+
+ // Use the database-backed info for more up-to-date values (beatmap id, ranked status, etc)
b.BeatmapInfo = BeatmapInfo;
+
return b;
});
diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs
index 723bb90e7e..3686b702c4 100644
--- a/osu.Game/Database/ArchiveModelManager.cs
+++ b/osu.Game/Database/ArchiveModelManager.cs
@@ -404,7 +404,7 @@ namespace osu.Game.Database
using (Stream s = reader.GetStream(file))
fileInfos.Add(new TFileModel
{
- Filename = FileSafety.PathSanitise(file),
+ Filename = FileSafety.PathStandardise(file),
FileInfo = files.Add(s)
});
diff --git a/osu.Game/Graphics/UserInterface/OsuDropdown.cs b/osu.Game/Graphics/UserInterface/OsuDropdown.cs
index 04a7cb64d3..30803d1545 100644
--- a/osu.Game/Graphics/UserInterface/OsuDropdown.cs
+++ b/osu.Game/Graphics/UserInterface/OsuDropdown.cs
@@ -30,7 +30,7 @@ namespace osu.Game.Graphics.UserInterface
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
- if (accentColour == default(Color4))
+ if (accentColour == default)
accentColour = colours.PinkDarker;
updateAccentColour();
}
diff --git a/osu.Game/Graphics/UserInterface/OsuTabControl.cs b/osu.Game/Graphics/UserInterface/OsuTabControl.cs
index 24183e07a0..e7d6a87349 100644
--- a/osu.Game/Graphics/UserInterface/OsuTabControl.cs
+++ b/osu.Game/Graphics/UserInterface/OsuTabControl.cs
@@ -53,7 +53,7 @@ namespace osu.Game.Graphics.UserInterface
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
- if (accentColour == default(Color4))
+ if (accentColour == default)
AccentColour = colours.Blue;
}
@@ -142,7 +142,7 @@ namespace osu.Game.Graphics.UserInterface
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
- if (accentColour == default(Color4))
+ if (accentColour == default)
AccentColour = colours.Blue;
}
diff --git a/osu.Game/Graphics/UserInterface/RollingCounter.cs b/osu.Game/Graphics/UserInterface/RollingCounter.cs
index f84404a911..c2162b8a42 100644
--- a/osu.Game/Graphics/UserInterface/RollingCounter.cs
+++ b/osu.Game/Graphics/UserInterface/RollingCounter.cs
@@ -134,7 +134,7 @@ namespace osu.Game.Graphics.UserInterface
///
public virtual void ResetCount()
{
- SetCountWithoutRolling(default(T));
+ SetCountWithoutRolling(default);
}
///
diff --git a/osu.Game/Migrations/20181007180454_StandardizePaths.Designer.cs b/osu.Game/Migrations/20181007180454_StandardizePaths.Designer.cs
new file mode 100644
index 0000000000..b387a45ecf
--- /dev/null
+++ b/osu.Game/Migrations/20181007180454_StandardizePaths.Designer.cs
@@ -0,0 +1,380 @@
+//
+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("20181007180454_StandardizePaths")]
+ partial class StandardizePaths
+ {
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasAnnotation("ProductVersion", "2.1.3-rtm-32065");
+
+ 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("Name");
+
+ b.HasKey("ID");
+
+ 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/20181007180454_StandardizePaths.cs b/osu.Game/Migrations/20181007180454_StandardizePaths.cs
new file mode 100644
index 0000000000..274b8030a9
--- /dev/null
+++ b/osu.Game/Migrations/20181007180454_StandardizePaths.cs
@@ -0,0 +1,26 @@
+using System;
+using Microsoft.EntityFrameworkCore.Migrations;
+using System.IO;
+
+namespace osu.Game.Migrations
+{
+ public partial class StandardizePaths : Migration
+ {
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ string windowsStyle = @"\";
+ string standardized = "/";
+
+ // Escaping \ does not seem to be needed.
+ migrationBuilder.Sql($"UPDATE `BeatmapInfo` SET `Path` = REPLACE(`Path`, '{windowsStyle}', '{standardized}')");
+ migrationBuilder.Sql($"UPDATE `BeatmapMetadata` SET `AudioFile` = REPLACE(`AudioFile`, '{windowsStyle}', '{standardized}')");
+ migrationBuilder.Sql($"UPDATE `BeatmapMetadata` SET `BackgroundFile` = REPLACE(`BackgroundFile`, '{windowsStyle}', '{standardized}')");
+ migrationBuilder.Sql($"UPDATE `BeatmapSetFileInfo` SET `Filename` = REPLACE(`Filename`, '{windowsStyle}', '{standardized}')");
+ migrationBuilder.Sql($"UPDATE `SkinFileInfo` SET `Filename` = REPLACE(`Filename`, '{windowsStyle}', '{standardized}')");
+ }
+
+ protected override void Down(MigrationBuilder migrationBuilder)
+ {
+ }
+ }
+}
diff --git a/osu.Game/Migrations/OsuDbContextModelSnapshot.cs b/osu.Game/Migrations/OsuDbContextModelSnapshot.cs
index fde5c9fd82..663676a6d3 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.2-rtm-30932");
+ .HasAnnotation("ProductVersion", "2.1.3-rtm-32065");
modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b =>
{
diff --git a/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs b/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs
index 3c808d1bee..ffea7b83e1 100644
--- a/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs
+++ b/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs
@@ -1,16 +1,14 @@
// 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.ComponentModel;
-using osu.Game.Online.API.Requests.Responses;
using osu.Game.Overlays;
using osu.Game.Overlays.Direct;
using osu.Game.Rulesets;
namespace osu.Game.Online.API.Requests
{
- public class SearchBeatmapSetsRequest : APIRequest>
+ public class SearchBeatmapSetsRequest : APIRequest
{
private readonly string query;
private readonly RulesetInfo ruleset;
@@ -35,6 +33,7 @@ namespace osu.Game.Online.API.Requests
public enum BeatmapSearchCategory
{
Any = 7,
+
[Description("Ranked & Approved")]
RankedApproved = 0,
Approved = 1,
@@ -43,6 +42,7 @@ namespace osu.Game.Online.API.Requests
Qualified = 3,
Pending = 4,
Graveyard = 5,
+
[Description("My Maps")]
MyMaps = 6,
}
diff --git a/osu.Game/Online/API/Requests/SearchBeatmapSetsResponse.cs b/osu.Game/Online/API/Requests/SearchBeatmapSetsResponse.cs
new file mode 100644
index 0000000000..cf8b40d068
--- /dev/null
+++ b/osu.Game/Online/API/Requests/SearchBeatmapSetsResponse.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 Newtonsoft.Json;
+using osu.Game.Online.API.Requests.Responses;
+
+namespace osu.Game.Online.API.Requests
+{
+ public class SearchBeatmapSetsResponse
+ {
+ public IEnumerable BeatmapSets;
+
+ ///
+ /// A collection of parameters which should be passed to the search endpoint to fetch the next page.
+ ///
+ [JsonProperty("cursor")]
+ public dynamic CursorJson;
+ }
+}
diff --git a/osu.Game/Overlays/DirectOverlay.cs b/osu.Game/Overlays/DirectOverlay.cs
index f63d314053..641f57d25f 100644
--- a/osu.Game/Overlays/DirectOverlay.cs
+++ b/osu.Game/Overlays/DirectOverlay.cs
@@ -288,7 +288,7 @@ namespace osu.Game.Overlays
{
Task.Run(() =>
{
- var sets = response.Select(r => r.ToBeatmapSet(rulesets)).ToList();
+ var sets = response.BeatmapSets.Select(r => r.ToBeatmapSet(rulesets)).ToList();
// may not need scheduling; loads async internally.
Schedule(() =>
diff --git a/osu.Game/Overlays/Profile/Header/RankGraph.cs b/osu.Game/Overlays/Profile/Header/RankGraph.cs
index fc80370cf9..bfb01ce1c8 100644
--- a/osu.Game/Overlays/Profile/Header/RankGraph.cs
+++ b/osu.Game/Overlays/Profile/Header/RankGraph.cs
@@ -74,7 +74,7 @@ namespace osu.Game.Overlays.Profile.Header
Anchor = Anchor.BottomCentre,
Origin = Anchor.BottomCentre,
RelativeSizeAxes = Axes.X,
- Height = 75,
+ Height = 60,
Y = -secondary_textsize,
Alpha = 0,
}
diff --git a/osu.Game/Rulesets/Edit/EditRulesetContainer.cs b/osu.Game/Rulesets/Edit/EditRulesetContainer.cs
new file mode 100644
index 0000000000..bc54c907ab
--- /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..13571bda84 100644
--- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs
+++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs
@@ -13,6 +13,7 @@ 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;
@@ -22,21 +23,24 @@ namespace osu.Game.Rulesets.Edit
{
public abstract class HitObjectComposer : CompositeDrawable
{
- private readonly Ruleset ruleset;
-
public IEnumerable HitObjects => rulesetContainer.Playfield.AllHitObjects;
- protected ICompositionTool CurrentTool { get; private set; }
+ protected readonly Ruleset Ruleset;
+
+ protected readonly IBindable Beatmap = new Bindable();
+
protected IRulesetConfigManager Config { get; private set; }
private readonly List layerContainers = new List();
- private readonly IBindable beatmap = new Bindable();
- private RulesetContainer rulesetContainer;
+ private EditRulesetContainer rulesetContainer;
- protected HitObjectComposer(Ruleset ruleset)
+ private HitObjectMaskLayer maskLayer;
+ private PlacementContainer placementContainer;
+
+ internal HitObjectComposer(Ruleset ruleset)
{
- this.ruleset = ruleset;
+ Ruleset = ruleset;
RelativeSizeAxes = Axes.Both;
}
@@ -44,11 +48,11 @@ 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 = CreateRulesetContainer();
rulesetContainer.Clock = framedClock;
}
catch (Exception e)
@@ -57,14 +61,15 @@ namespace osu.Game.Rulesets.Edit
return;
}
- var layerBelowRuleset = new BorderLayer
- {
- RelativeSizeAxes = Axes.Both,
- Child = CreateLayerContainer()
- };
+ var layerBelowRuleset = CreateLayerContainer();
+ layerBelowRuleset.Child = new BorderLayer { RelativeSizeAxes = Axes.Both };
var layerAboveRuleset = CreateLayerContainer();
- layerAboveRuleset.Child = new HitObjectMaskLayer();
+ layerAboveRuleset.Children = new Drawable[]
+ {
+ maskLayer = new HitObjectMaskLayer(),
+ placementContainer = new PlacementContainer(),
+ };
layerContainers.Add(layerBelowRuleset);
layerContainers.Add(layerAboveRuleset);
@@ -107,8 +112,8 @@ 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, () => placementContainer.CurrentTool = t))
+ .Prepend(new RadioButton("Select", () => placementContainer.CurrentTool = null))
.ToList();
toolboxCollection.Items[0].Select();
@@ -119,18 +124,11 @@ namespace osu.Game.Rulesets.Edit
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();
@@ -144,17 +142,27 @@ namespace osu.Game.Rulesets.Edit
});
}
- private void setCompositionTool(ICompositionTool tool) => CurrentTool = tool;
+ ///
+ /// Adds a to the and visualises it.
+ ///
+ /// The to add.
+ public void Add(HitObject hitObject)
+ {
+ maskLayer.AddMaskFor(rulesetContainer.Add(hitObject));
+ placementContainer.Refresh();
+ }
- protected virtual RulesetContainer CreateRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap) => ruleset.CreateRulesetContainerWith(beatmap);
+ public void Remove(HitObject hitObject) => maskLayer.RemoveMaskFor(rulesetContainer.Remove(hitObject));
- protected abstract IReadOnlyList CompositionTools { get; }
+ internal abstract EditRulesetContainer CreateRulesetContainer();
+
+ protected abstract IReadOnlyList CompositionTools { get; }
///
- /// Creates a for a specific .
+ /// Creates a for a specific .
///
/// The to create the overlay for.
- public virtual HitObjectMask CreateMaskFor(DrawableHitObject hitObject) => null;
+ public virtual SelectionMask CreateMaskFor(DrawableHitObject hitObject) => null;
///
/// Creates a which outlines s
@@ -167,4 +175,18 @@ namespace osu.Game.Rulesets.Edit
///
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/PlacementMask.cs b/osu.Game/Rulesets/Edit/PlacementMask.cs
new file mode 100644
index 0000000000..97c6a74c92
--- /dev/null
+++ b/osu.Game/Rulesets/Edit/PlacementMask.cs
@@ -0,0 +1,96 @@
+// 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.Input;
+using osu.Framework.Input.Events;
+using osu.Framework.Timing;
+using osu.Game.Beatmaps;
+using osu.Game.Rulesets.Objects;
+using osu.Game.Screens.Edit.Screens.Compose;
+using OpenTK;
+
+namespace osu.Game.Rulesets.Edit
+{
+ ///
+ /// A mask which governs the creation of a new to actualisation.
+ ///
+ public abstract class PlacementMask : CompositeDrawable, IRequireHighFrequencyMousePosition
+ {
+ ///
+ /// 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 PlacementMask(HitObject hitObject)
+ {
+ HitObject = hitObject;
+
+ RelativeSizeAxes = Axes.Both;
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(IBindableBeatmap beatmap, IAdjustableClock clock)
+ {
+ this.beatmap.BindTo(beatmap);
+
+ EditorClock = clock;
+
+ ApplyDefaultsToHitObject();
+ }
+
+ private bool placementBegun;
+
+ ///
+ /// 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;
+ }
+ }
+ }
+}
diff --git a/osu.Game/Rulesets/Edit/HitObjectMask.cs b/osu.Game/Rulesets/Edit/SelectionMask.cs
similarity index 74%
rename from osu.Game/Rulesets/Edit/HitObjectMask.cs
rename to osu.Game/Rulesets/Edit/SelectionMask.cs
index 636ea418f3..3b78d5aaf6 100644
--- a/osu.Game/Rulesets/Edit/HitObjectMask.cs
+++ b/osu.Game/Rulesets/Edit/SelectionMask.cs
@@ -3,6 +3,7 @@
using System;
using osu.Framework;
+using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Primitives;
using osu.Framework.Input.Events;
@@ -16,31 +17,31 @@ namespace osu.Game.Rulesets.Edit
///
/// A mask placed above a adding editing functionality.
///
- public class HitObjectMask : CompositeDrawable, IStateful
+ public class SelectionMask : 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)
+ public SelectionMask(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)
@@ -130,13 +135,13 @@ namespace osu.Game.Rulesets.Edit
}
///
- /// 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..c5d64e3d4d 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 PlacementMask CreatePlacementMask();
}
}
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/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/Objects/BezierApproximator.cs b/osu.Game/Rulesets/Objects/BezierApproximator.cs
deleted file mode 100644
index 6094934510..0000000000
--- a/osu.Game/Rulesets/Objects/BezierApproximator.cs
+++ /dev/null
@@ -1,150 +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 OpenTK;
-
-namespace osu.Game.Rulesets.Objects
-{
- public class BezierApproximator
- {
- private readonly int count;
- private readonly List controlPoints;
- private readonly Vector2[] subdivisionBuffer1;
- private readonly Vector2[] subdivisionBuffer2;
-
- private const float tolerance = 0.25f;
- private const float tolerance_sq = tolerance * tolerance;
-
- public BezierApproximator(List controlPoints)
- {
- this.controlPoints = controlPoints;
- count = controlPoints.Count;
-
- 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