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/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.Game.Rulesets.Catch.Tests/TestCaseAutoJuiceStream.cs b/osu.Game.Rulesets.Catch.Tests/TestCaseAutoJuiceStream.cs index 5e68acde94..cac1356c81 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestCaseAutoJuiceStream.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestCaseAutoJuiceStream.cs @@ -42,7 +42,7 @@ namespace osu.Game.Rulesets.Catch.Tests 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 15d4edc411..a178748bd5 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, RepeatSamples = curveData.RepeatSamples, RepeatCount = curveData.RepeatCount, diff --git a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs index 82e32d24d2..da581d9619 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 Vector2[] ControlPoints { - get { return Curve.ControlPoints; } - set { Curve.ControlPoints = value; } + get { return Path.ControlPoints; } + set { Path.ControlPoints = value; } } public List> RepeatSamples { 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.Mania/Edit/ManiaHitObjectComposer.cs b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs index fe97f9bc8e..fcacde769b 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs @@ -5,13 +5,13 @@ using System; using osu.Game.Beatmaps; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit.Tools; +using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Objects.Drawables; using osu.Game.Rulesets.Objects.Drawables; 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.Objects; using osu.Game.Rulesets.Mania.UI; using osu.Game.Rulesets.UI; 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.Osu.Tests/TestCaseSlider.cs b/osu.Game.Rulesets.Osu.Tests/TestCaseSlider.cs index 300ac16155..4c0385deda 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestCaseSlider.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestCaseSlider.cs @@ -181,7 +181,7 @@ 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[] @@ -207,7 +207,7 @@ 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[] @@ -232,7 +232,7 @@ 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[] @@ -264,7 +264,7 @@ namespace osu.Game.Rulesets.Osu.Tests { StartTime = Time.Current + 1000, Position = new Vector2(-100, 0), - CurveType = CurveType.Catmull, + PathType = PathType.Catmull, ControlPoints = new[] { Vector2.Zero, diff --git a/osu.Game.Rulesets.Osu.Tests/TestCaseSliderSelectionMask.cs b/osu.Game.Rulesets.Osu.Tests/TestCaseSliderSelectionMask.cs index 5e68d5cdc9..87e0e1a7ec 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestCaseSliderSelectionMask.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestCaseSliderSelectionMask.cs @@ -1,11 +1,14 @@ // 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; @@ -15,6 +18,16 @@ 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() @@ -28,7 +41,7 @@ namespace osu.Game.Rulesets.Osu.Tests new Vector2(150, 150), new Vector2(300, 0) }, - CurveType = CurveType.Bezier, + PathType = PathType.Bezier, Distance = 350 }; diff --git a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs index b2914d4b82..9f432fc31a 100644 --- a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs +++ b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs @@ -36,7 +36,7 @@ namespace osu.Game.Rulesets.Osu.Beatmaps StartTime = original.StartTime, Samples = original.Samples, ControlPoints = curveData.ControlPoints, - CurveType = curveData.CurveType, + PathType = curveData.PathType, Distance = curveData.Distance, RepeatSamples = curveData.RepeatSamples, RepeatCount = curveData.RepeatCount, diff --git a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs index d6684f55af..39e3c009da 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs @@ -108,7 +108,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing progress = progress % 1; // ReSharper disable once PossibleInvalidOperationException (bugged in current r# version) - var diff = slider.StackedPosition + slider.Curve.PositionAt(progress) - slider.LazyEndPosition.Value; + var diff = slider.StackedPosition + slider.Path.PositionAt(progress) - slider.LazyEndPosition.Value; float dist = diff.Length; if (dist > approxFollowCircleRadius) diff --git a/osu.Game.Rulesets.Osu/Edit/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 index 3123a4fcea..46d99e1740 100644 --- a/osu.Game.Rulesets.Osu/Edit/Masks/SliderMasks/Components/SliderBodyPiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Masks/SliderMasks/Components/SliderBodyPiece.cs @@ -46,6 +46,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Masks.SliderMasks.Components // Need to cause one update body.UpdateProgress(0); + body.Refresh(); } } } diff --git a/osu.Game.Rulesets.Osu/Edit/Masks/SliderMasks/Components/SliderCirclePiece.cs b/osu.Game.Rulesets.Osu/Edit/Masks/SliderMasks/Components/SliderCirclePiece.cs index c5ecde5c4c..7864429d93 100644 --- a/osu.Game.Rulesets.Osu/Edit/Masks/SliderMasks/Components/SliderCirclePiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Masks/SliderMasks/Components/SliderCirclePiece.cs @@ -16,6 +16,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Masks.SliderMasks.Components { this.slider = slider; this.position = position; + + slider.ControlPointsChanged += _ => UpdatePosition(); } protected override void UpdatePosition() @@ -23,10 +25,10 @@ namespace osu.Game.Rulesets.Osu.Edit.Masks.SliderMasks.Components switch (position) { case SliderPosition.Start: - Position = slider.StackedPosition + slider.Curve.PositionAt(0); + Position = slider.StackedPosition + slider.Path.PositionAt(0); break; case SliderPosition.End: - Position = slider.StackedPosition + slider.Curve.PositionAt(1); + Position = slider.StackedPosition + slider.Path.PositionAt(1); break; } } diff --git a/osu.Game.Rulesets.Osu/Edit/Masks/SliderMasks/SliderSelectionMask.cs b/osu.Game.Rulesets.Osu/Edit/Masks/SliderMasks/SliderSelectionMask.cs index a411064f68..b79b0ba1fb 100644 --- a/osu.Game.Rulesets.Osu/Edit/Masks/SliderMasks/SliderSelectionMask.cs +++ b/osu.Game.Rulesets.Osu/Edit/Masks/SliderMasks/SliderSelectionMask.cs @@ -24,6 +24,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Masks.SliderMasks new SliderBodyPiece(sliderObject), headMask = new SliderCircleSelectionMask(slider.HeadCircle, sliderObject, SliderPosition.Start), new SliderCircleSelectionMask(slider.TailCircle, sliderObject, SliderPosition.End), + new PathControlPointVisualiser(sliderObject), }; } diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModHardRock.cs b/osu.Game.Rulesets.Osu/Mods/OsuModHardRock.cs index 1b3725a15e..e01d71e1f8 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModHardRock.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModHardRock.cs @@ -37,7 +37,7 @@ namespace osu.Game.Rulesets.Osu.Mods 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/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index 16bd522c1d..a137343cfe 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -85,6 +85,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables } HitObject.PositionChanged += _ => Position = HitObject.StackedPosition; + + slider.ControlPointsChanged += _ => Body.Refresh(); } public override Color4 AccentColour @@ -119,7 +121,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/Pieces/SnakingSliderBody.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SnakingSliderBody.cs index 09d6f9459a..0e6f3ad16c 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SnakingSliderBody.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SnakingSliderBody.cs @@ -45,15 +45,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces [BackgroundDependencyLoader] private void load() { - // Generate the entire curve - slider.Curve.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]); + Refresh(); } public void UpdateProgress(double completionProgress) @@ -80,6 +72,27 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces 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) @@ -90,7 +103,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces SnakedStart = p0; SnakedEnd = p1; - slider.Curve.GetPathToProgress(CurrentCurve, p0, p1); + slider.Path.GetPathToProgress(CurrentCurve, p0, p1); SetVertices(CurrentCurve); diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index a6f5bdb24e..b7240991d4 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,34 @@ namespace osu.Game.Rulesets.Osu.Objects } } - public SliderCurve Curve { get; } = new SliderCurve(); + public SliderPath Path { get; } = new SliderPath(); 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 @@ -177,7 +189,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; @@ -216,7 +228,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 @@ -234,7 +246,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 = new List(RepeatSamples[repeatIndex]) diff --git a/osu.Game/Rulesets/Edit/PlacementMask.cs b/osu.Game/Rulesets/Edit/PlacementMask.cs index a588a9e181..97c6a74c92 100644 --- a/osu.Game/Rulesets/Edit/PlacementMask.cs +++ b/osu.Game/Rulesets/Edit/PlacementMask.cs @@ -15,6 +15,9 @@ using OpenTK; namespace osu.Game.Rulesets.Edit { + /// + /// A mask which governs the creation of a new to actualisation. + /// public abstract class PlacementMask : CompositeDrawable, IRequireHighFrequencyMousePosition { /// @@ -81,6 +84,8 @@ namespace osu.Game.Rulesets.Edit switch (e) { + case ScrollEvent _: + return false; case MouseEvent _: return true; default: 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/Legacy/Catch/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHitObjectParser.cs index 9c9fc2e742..85efdca07b 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHitObjectParser.cs @@ -37,7 +37,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Catch }; } - protected override HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, Vector2[] controlPoints, double length, CurveType curveType, int repeatCount, List> repeatSamples) + protected override HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, Vector2[] controlPoints, double length, PathType pathType, int repeatCount, List> repeatSamples) { newCombo |= forceNewCombo; comboOffset += extraComboOffset; @@ -52,7 +52,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Catch ComboOffset = comboOffset, ControlPoints = controlPoints, Distance = length, - CurveType = curveType, + PathType = pathType, RepeatSamples = repeatSamples, RepeatCount = repeatCount }; diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs index 965e76d27a..73f70d414f 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs @@ -70,7 +70,7 @@ namespace osu.Game.Rulesets.Objects.Legacy } else if (type.HasFlag(ConvertHitObjectType.Slider)) { - CurveType curveType = CurveType.Catmull; + PathType pathType = PathType.Catmull; double length = 0; string[] pointSplit = split[5].Split('|'); @@ -90,16 +90,16 @@ namespace osu.Game.Rulesets.Objects.Legacy switch (t) { case @"C": - curveType = CurveType.Catmull; + pathType = PathType.Catmull; break; case @"B": - curveType = CurveType.Bezier; + pathType = PathType.Bezier; break; case @"L": - curveType = CurveType.Linear; + pathType = PathType.Linear; break; case @"P": - curveType = CurveType.PerfectCurve; + pathType = PathType.PerfectCurve; break; } @@ -113,8 +113,8 @@ namespace osu.Game.Rulesets.Objects.Legacy // osu-stable special-cased colinear perfect curves to a CurveType.Linear bool isLinear(Vector2[] p) => Precision.AlmostEquals(0, (p[1].Y - p[0].Y) * (p[2].X - p[0].X) - (p[1].X - p[0].X) * (p[2].Y - p[0].Y)); - if (points.Length == 3 && curveType == CurveType.PerfectCurve && isLinear(points)) - curveType = CurveType.Linear; + if (points.Length == 3 && pathType == PathType.PerfectCurve && isLinear(points)) + pathType = PathType.Linear; int repeatCount = Convert.ToInt32(split[6], CultureInfo.InvariantCulture); @@ -178,7 +178,7 @@ namespace osu.Game.Rulesets.Objects.Legacy for (int i = 0; i < nodes; i++) nodeSamples.Add(convertSoundType(nodeSoundTypes[i], nodeBankInfos[i])); - result = CreateSlider(pos, combo, comboOffset, points, length, curveType, repeatCount, nodeSamples); + result = CreateSlider(pos, combo, comboOffset, points, length, pathType, repeatCount, nodeSamples); } else if (type.HasFlag(ConvertHitObjectType.Spinner)) { @@ -268,11 +268,11 @@ namespace osu.Game.Rulesets.Objects.Legacy /// When starting a new combo, the offset of the new combo relative to the current one. /// The slider control points. /// The slider length. - /// The slider curve type. + /// The slider curve type. /// The slider repeat count. /// The samples to be played when the repeat nodes are hit. This includes the head and tail of the slider. /// The hit object. - protected abstract HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, Vector2[] controlPoints, double length, CurveType curveType, int repeatCount, List> repeatSamples); + protected abstract HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, Vector2[] controlPoints, double length, PathType pathType, int repeatCount, List> repeatSamples); /// /// Creates a legacy Spinner-type hit object. diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs index 93c49ea3ce..6030bff427 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs @@ -20,9 +20,9 @@ namespace osu.Game.Rulesets.Objects.Legacy /// /// s don't need a curve since they're converted to ruleset-specific hitobjects. /// - public SliderCurve Curve { get; } = null; + public SliderPath Path { get; } = null; public Vector2[] ControlPoints { get; set; } - public CurveType CurveType { get; set; } + public PathType PathType { get; set; } public double Distance { get; set; } diff --git a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHitObjectParser.cs index 68e05f6223..6f10880aa2 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHitObjectParser.cs @@ -26,14 +26,14 @@ namespace osu.Game.Rulesets.Objects.Legacy.Mania }; } - protected override HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, Vector2[] controlPoints, double length, CurveType curveType, int repeatCount, List> repeatSamples) + protected override HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, Vector2[] controlPoints, double length, PathType pathType, int repeatCount, List> repeatSamples) { return new ConvertSlider { X = position.X, ControlPoints = controlPoints, Distance = length, - CurveType = curveType, + PathType = pathType, RepeatSamples = repeatSamples, RepeatCount = repeatCount }; diff --git a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHitObjectParser.cs index f3c815fc32..31c200eddc 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHitObjectParser.cs @@ -38,7 +38,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Osu }; } - protected override HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, Vector2[] controlPoints, double length, CurveType curveType, int repeatCount, List> repeatSamples) + protected override HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, Vector2[] controlPoints, double length, PathType pathType, int repeatCount, List> repeatSamples) { newCombo |= forceNewCombo; comboOffset += extraComboOffset; @@ -53,7 +53,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Osu ComboOffset = comboOffset, ControlPoints = controlPoints, Distance = Math.Max(0, length), - CurveType = curveType, + PathType = pathType, RepeatSamples = repeatSamples, RepeatCount = repeatCount }; diff --git a/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHitObjectParser.cs index 985a032640..0a244bb6c6 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHitObjectParser.cs @@ -23,13 +23,13 @@ namespace osu.Game.Rulesets.Objects.Legacy.Taiko return new ConvertHit(); } - protected override HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, Vector2[] controlPoints, double length, CurveType curveType, int repeatCount, List> repeatSamples) + protected override HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, Vector2[] controlPoints, double length, PathType pathType, int repeatCount, List> repeatSamples) { return new ConvertSlider { ControlPoints = controlPoints, Distance = length, - CurveType = curveType, + PathType = pathType, RepeatSamples = repeatSamples, RepeatCount = repeatCount }; diff --git a/osu.Game/Rulesets/Objects/SliderCurve.cs b/osu.Game/Rulesets/Objects/SliderPath.cs similarity index 84% rename from osu.Game/Rulesets/Objects/SliderCurve.cs rename to osu.Game/Rulesets/Objects/SliderPath.cs index dfccdf68f2..46f8cae8a0 100644 --- a/osu.Game/Rulesets/Objects/SliderCurve.cs +++ b/osu.Game/Rulesets/Objects/SliderPath.cs @@ -10,13 +10,13 @@ using OpenTK; namespace osu.Game.Rulesets.Objects { - public class SliderCurve + public class SliderPath { public double Distance; public Vector2[] ControlPoints; - public CurveType CurveType = CurveType.PerfectCurve; + public PathType PathType = PathType.PerfectCurve; public Vector2 Offset; @@ -25,15 +25,15 @@ namespace osu.Game.Rulesets.Objects private List calculateSubpath(ReadOnlySpan subControlPoints) { - switch (CurveType) + switch (PathType) { - case CurveType.Linear: + case PathType.Linear: var result = new List(subControlPoints.Length); foreach (var c in subControlPoints) result.Add(c); return result; - case CurveType.PerfectCurve: + case PathType.PerfectCurve: //we can only use CircularArc iff we have exactly three control points and no dissection. if (ControlPoints.Length != 3 || subControlPoints.Length != 3) break; @@ -46,7 +46,7 @@ namespace osu.Game.Rulesets.Objects break; return subpath; - case CurveType.Catmull: + case PathType.Catmull: return new CatmullApproximator(subControlPoints).CreateCatmull(); } @@ -93,7 +93,7 @@ namespace osu.Game.Rulesets.Objects Vector2 diff = calculatedPath[i + 1] - calculatedPath[i]; double d = diff.Length; - // Shorten slider curves that are too long compared to what's + // Shorten slider paths that are too long compared to what's // in the .osu file. if (Distance - l < d) { @@ -109,7 +109,7 @@ namespace osu.Game.Rulesets.Objects cumulativeLength.Add(l); } - // Lengthen slider curves that are too short compared to what's + // Lengthen slider paths that are too short compared to what's // in the .osu file. if (l < Distance && calculatedPath.Count > 1) { @@ -124,10 +124,33 @@ namespace osu.Game.Rulesets.Objects } } - public void Calculate() + private void calculateCumulativeLength() + { + double l = 0; + + cumulativeLength.Clear(); + cumulativeLength.Add(l); + + for (int i = 0; i < calculatedPath.Count - 1; ++i) + { + Vector2 diff = calculatedPath[i + 1] - calculatedPath[i]; + double d = diff.Length; + + l += d; + cumulativeLength.Add(l); + } + + Distance = l; + } + + public void Calculate(bool updateDistance = false) { calculatePath(); - calculateCumulativeLengthAndTrimPath(); + + if (!updateDistance) + calculateCumulativeLengthAndTrimPath(); + else + calculateCumulativeLength(); } private int indexOfDistance(double d) @@ -168,10 +191,10 @@ namespace osu.Game.Rulesets.Objects } /// - /// Computes the slider curve until a given progress that ranges from 0 (beginning of the slider) + /// Computes the slider path until a given progress that ranges from 0 (beginning of the slider) /// to 1 (end of the slider) and stores the generated path in the given list. /// - /// The list to be filled with the computed curve. + /// The list to be filled with the computed path. /// Start progress. Ranges from 0 (beginning of the slider) to 1 (end of the slider). /// End progress. Ranges from 0 (beginning of the slider) to 1 (end of the slider). public void GetPathToProgress(List path, double p0, double p1) @@ -196,10 +219,10 @@ namespace osu.Game.Rulesets.Objects } /// - /// Computes the position on the slider at a given progress that ranges from 0 (beginning of the curve) - /// to 1 (end of the curve). + /// Computes the position on the slider at a given progress that ranges from 0 (beginning of the path) + /// to 1 (end of the path). /// - /// Ranges from 0 (beginning of the curve) to 1 (end of the curve). + /// Ranges from 0 (beginning of the path) to 1 (end of the path). /// public Vector2 PositionAt(double progress) { diff --git a/osu.Game/Rulesets/Objects/Types/IHasCurve.cs b/osu.Game/Rulesets/Objects/Types/IHasCurve.cs index 69b2f722e7..2a0d495e94 100644 --- a/osu.Game/Rulesets/Objects/Types/IHasCurve.cs +++ b/osu.Game/Rulesets/Objects/Types/IHasCurve.cs @@ -13,7 +13,7 @@ namespace osu.Game.Rulesets.Objects.Types /// /// The curve. /// - SliderCurve Curve { get; } + SliderPath Path { get; } /// /// The control points that shape the curve. @@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Objects.Types /// /// The type of curve. /// - CurveType CurveType { get; } + PathType PathType { get; } } public static class HasCurveExtensions @@ -35,7 +35,7 @@ namespace osu.Game.Rulesets.Objects.Types /// [0, 1] where 0 is the start time of the and 1 is the end time of the . /// The position on the curve. public static Vector2 CurvePositionAt(this IHasCurve obj, double progress) - => obj.Curve.PositionAt(obj.ProgressAt(progress)); + => obj.Path.PositionAt(obj.ProgressAt(progress)); /// /// Computes the progress along the curve relative to how much of the has been completed. diff --git a/osu.Game/Rulesets/Objects/Types/CurveType.cs b/osu.Game/Rulesets/Objects/Types/PathType.cs similarity index 91% rename from osu.Game/Rulesets/Objects/Types/CurveType.cs rename to osu.Game/Rulesets/Objects/Types/PathType.cs index 1cee6202b6..5156302fe1 100644 --- a/osu.Game/Rulesets/Objects/Types/CurveType.cs +++ b/osu.Game/Rulesets/Objects/Types/PathType.cs @@ -3,7 +3,7 @@ namespace osu.Game.Rulesets.Objects.Types { - public enum CurveType + public enum PathType { Catmull, Bezier, diff --git a/osu.Game/Rulesets/UI/RulesetContainer.cs b/osu.Game/Rulesets/UI/RulesetContainer.cs index 3484018fc0..0abd358113 100644 --- a/osu.Game/Rulesets/UI/RulesetContainer.cs +++ b/osu.Game/Rulesets/UI/RulesetContainer.cs @@ -238,6 +238,8 @@ namespace osu.Game.Rulesets.UI KeyBindingInputManager = CreateInputManager(); KeyBindingInputManager.RelativeSizeAxes = Axes.Both; + + applyBeatmapMods(Mods); } [BackgroundDependencyLoader] @@ -255,16 +257,29 @@ namespace osu.Game.Rulesets.UI KeyBindingInputManager.Add(Cursor); // Apply mods - applyMods(Mods, config); + applyRulesetMods(Mods, config); loadObjects(); } + /// + /// Applies the active mods to the Beatmap. + /// + /// + private void applyBeatmapMods(IEnumerable mods) + { + if (mods == null) + return; + + foreach (var mod in mods.OfType>()) + mod.ApplyToBeatmap(Beatmap); + } + /// /// Applies the active mods to this RulesetContainer. /// /// - private void applyMods(IEnumerable mods, OsuConfigManager config) + private void applyRulesetMods(IEnumerable mods, OsuConfigManager config) { if (mods == null) return; diff --git a/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskSelection.cs b/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskSelection.cs index 4946b35abb..17b34bfb49 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskSelection.cs +++ b/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskSelection.cs @@ -87,6 +87,7 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers placementHandler.Delete(h.HitObject.HitObject); return true; } + return base.OnKeyDown(e); } diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index db0d7b6ccc..0f9f4fc20a 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -134,11 +134,13 @@ namespace osu.Game.Screens.Play { PlayerSettingsOverlay.Show(); ModDisplay.FadeIn(200); + KeyCounter.Margin = new MarginPadding(10) { Bottom = 30 }; } else { PlayerSettingsOverlay.Hide(); ModDisplay.Delay(2000).FadeOut(200); + KeyCounter.Margin = new MarginPadding(10); } } diff --git a/osu.Game/Screens/Play/SongProgress.cs b/osu.Game/Screens/Play/SongProgress.cs index 2e2c77c1c8..d4615b3016 100644 --- a/osu.Game/Screens/Play/SongProgress.cs +++ b/osu.Game/Screens/Play/SongProgress.cs @@ -21,7 +21,7 @@ namespace osu.Game.Screens.Play { private const int bottom_bar_height = 5; - private static readonly Vector2 handle_size = new Vector2(14, 25); + private static readonly Vector2 handle_size = new Vector2(10, 18); private const float transition_duration = 200; @@ -135,6 +135,8 @@ namespace osu.Game.Screens.Play { bar.FadeTo(allowSeeking ? 1 : 0, transition_duration, Easing.In); this.MoveTo(new Vector2(0, allowSeeking ? 0 : bottom_bar_height), transition_duration, Easing.In); + + info.Margin = new MarginPadding { Bottom = Height - (allowSeeking ? 0 : handle_size.Y) }; } protected override void PopIn() diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 4288483caf..acf699aa24 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -350,7 +350,7 @@ namespace osu.Game.Screens.Select } } - ensurePlayingSelected(preview); + if (IsCurrentScreen) ensurePlayingSelected(preview); UpdateBeatmap(Beatmap.Value); } diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index e2b466c69f..2f8c743bee 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -18,7 +18,7 @@ - + diff --git a/tools/cakebuild.csproj b/tools/cakebuild.csproj new file mode 100644 index 0000000000..8ccce35e26 --- /dev/null +++ b/tools/cakebuild.csproj @@ -0,0 +1,11 @@ + + + Exe + true + netcoreapp2.0 + + + + + + \ No newline at end of file