Merge remote-tracking branch 'origin/master' into fix-slider-samples

# Conflicts:
#	osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs
#	osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHitObjectParser.cs
#	osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs
#	osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHitObjectParser.cs
#	osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHitObjectParser.cs
#	osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHitObjectParser.cs
This commit is contained in:
smoogipoo 2018-11-05 16:32:27 +09:00
commit a4caaf56ef
143 changed files with 3066 additions and 1068 deletions

14
.github/ISSUE_TEMPLATE/bug-issues.md vendored Normal file
View File

@ -0,0 +1,14 @@
---
name: Bug Report
about: For issues regarding encountered game bugs
---
<!-- After you fill in all information, delete all comments in the issue -->
**Describe your problem:** <!-- Provide any information you believe could be useful -->
**Screenshots or videos showing encountered issue:**
**osu!lazer version:** <!-- Provide the version of your osu!lazer, you can find it at the bottom of the screen -->
**Logs:** <!-- Attach your osu!lazer logs, you can find them under %appdata%\osu\logs in Windows, or under ~/.local/share/osu/ in Linux and macOS -->

16
.github/ISSUE_TEMPLATE/crash-issues.md vendored Normal file
View File

@ -0,0 +1,16 @@
---
name: Crash Report
about: For issues regarding game crashes or permanent freezes
---
<!-- After you fill in all information, delete all comments in the issue -->
**Describe your problem:** <!-- Provide any information you believe could be useful -->
**Screenshots or videos showing encountered issue:**
**osu!lazer version:** <!-- Provide the version of your osu!lazer, you can find it at the bottom of the screen -->
**Logs:** <!-- Attach your osu!lazer logs, you can find them under %appdata%\osu\logs in Windows, or under ~/.local/share/osu/ in Linux and macOS -->
**Computer Specifications:** <!-- Attach your computer specifications, you can find them by using System Information in Windows, System Monitor in Linux, or About This Mac in macOS -->

View File

@ -0,0 +1,10 @@
---
name: Feature Request
about: Let us know what you would like to see in the game!
---
<!-- After you fill in all information, delete all comments in the issue -->
**Describe the feature:** <!-- Describe the feature you would like to see in the game -->
**Proposal designs of the feature:** <!-- Attach screenshots of how the feature should look like according to you -->

View File

@ -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
---
<!-- After you fill in all information, delete all comments in the issue -->
**Describe the feature:** <!-- Describe the missing game feature -->
**Designs:** <!-- Attach screenshots of how the feature is supposed to look like. For illustrative purpose only; final designs are usually re-imagined from scratch. -->

7
.gitignore vendored
View File

@ -10,6 +10,10 @@
# User-specific files (MonoDevelop/Xamarin Studio) # User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs *.userprefs
### Cake ###
tools/*
!tools/cakebuild.csproj
# Build results # Build results
bin/[Dd]ebug/ bin/[Dd]ebug/
[Dd]ebugPublic/ [Dd]ebugPublic/
@ -98,6 +102,7 @@ $tf/
_ReSharper*/ _ReSharper*/
*.[Rr]e[Ss]harper *.[Rr]e[Ss]harper
*.DotSettings.user *.DotSettings.user
inspectcode
# JustCode is a .NET coding add-in # JustCode is a .NET coding add-in
.JustCode .JustCode
@ -257,3 +262,5 @@ paket-files/
__pycache__/ __pycache__/
*.pyc *.pyc
Staging/ Staging/
inspectcodereport.xml

View File

@ -25,6 +25,7 @@ Build and run
- Using Visual Studio 2017, Rider or Visual Studio Code (configurations are included) - 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. - 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 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

View File

@ -1,22 +1,8 @@
clone_depth: 1 clone_depth: 1
version: '{branch}-{build}' version: '{branch}-{build}'
image: Visual Studio 2017 image: Visual Studio 2017
configuration: Debug test: off
cache:
- C:\ProgramData\chocolatey\bin -> appveyor.yml
- C:\ProgramData\chocolatey\lib -> appveyor.yml
install: install:
- cmd: git submodule update --init --recursive --depth=5 - cmd: git submodule update --init --recursive --depth=5
- cmd: choco install resharper-clt -y build_script:
- cmd: choco install nvika -y - cmd: PowerShell -Version 2.0 .\build.ps1
- 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

View File

@ -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

72
build.cake Normal file
View File

@ -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);

79
build.ps1 Normal file
View File

@ -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

37
build.sh Normal file
View File

@ -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[@]}"

5
cake.config Normal file
View File

@ -0,0 +1,5 @@
[Nuget]
Source=https://api.nuget.org/v3/index.json
UseInProcessClient=true
LoadDependencies=true

@ -1 +1 @@
Subproject commit c3848d8b1c84966abe851d915bcca878415614b4 Subproject commit 9ee64e369fe6fdafc6aed40f5a35b5f01eb82c53

View File

@ -27,7 +27,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup Label="Package References"> <ItemGroup Label="Package References">
<PackageReference Include="System.IO.Packaging" Version="4.5.0" /> <PackageReference Include="System.IO.Packaging" Version="4.5.0" />
<PackageReference Include="ppy.squirrel.windows" Version="1.8.0.8" /> <PackageReference Include="ppy.squirrel.windows" Version="1.9.0.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="2.1.4" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="2.1.4" />
</ItemGroup> </ItemGroup>

View File

@ -1,7 +1,6 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Collections.Generic;
using System.Linq; using System.Linq;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.Objects;
@ -38,12 +37,12 @@ namespace osu.Game.Rulesets.Catch.Tests
beatmap.HitObjects.Add(new JuiceStream beatmap.HitObjects.Add(new JuiceStream
{ {
X = 0.5f - width / 2, X = 0.5f - width / 2,
ControlPoints = new List<Vector2> ControlPoints = new[]
{ {
Vector2.Zero, Vector2.Zero,
new Vector2(width * CatchPlayfield.BASE_WIDTH, 0) new Vector2(width * CatchPlayfield.BASE_WIDTH, 0)
}, },
CurveType = CurveType.Linear, PathType = PathType.Linear,
Distance = width * CatchPlayfield.BASE_WIDTH, Distance = width * CatchPlayfield.BASE_WIDTH,
StartTime = i * 2000, StartTime = i * 2000,
NewCombo = i % 8 == 0 NewCombo = i % 8 == 0

View File

@ -35,7 +35,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
StartTime = obj.StartTime, StartTime = obj.StartTime,
Samples = obj.Samples, Samples = obj.Samples,
ControlPoints = curveData.ControlPoints, ControlPoints = curveData.ControlPoints,
CurveType = curveData.CurveType, PathType = curveData.PathType,
Distance = curveData.Distance, Distance = curveData.Distance,
NodeSamples = curveData.NodeSamples, NodeSamples = curveData.NodeSamples,
RepeatCount = curveData.RepeatCount, RepeatCount = curveData.RepeatCount,

View File

@ -50,7 +50,7 @@ namespace osu.Game.Rulesets.Catch.Objects
if (TickDistance == 0) if (TickDistance == 0)
return; return;
var length = Curve.Distance; var length = Path.Distance;
var tickDistance = Math.Min(TickDistance, length); var tickDistance = Math.Min(TickDistance, length);
var spanDuration = length / Velocity; var spanDuration = length / Velocity;
@ -95,7 +95,7 @@ namespace osu.Game.Rulesets.Catch.Objects
AddNested(new TinyDroplet AddNested(new TinyDroplet
{ {
StartTime = t, StartTime = t,
X = X + Curve.PositionAt(progress).X / CatchPlayfield.BASE_WIDTH, X = X + Path.PositionAt(progress).X / CatchPlayfield.BASE_WIDTH,
Samples = new List<SampleInfo>(Samples.Select(s => new SampleInfo Samples = new List<SampleInfo>(Samples.Select(s => new SampleInfo
{ {
Bank = s.Bank, Bank = s.Bank,
@ -110,7 +110,7 @@ namespace osu.Game.Rulesets.Catch.Objects
AddNested(new Droplet AddNested(new Droplet
{ {
StartTime = time, StartTime = time,
X = X + Curve.PositionAt(distanceProgress).X / CatchPlayfield.BASE_WIDTH, X = X + Path.PositionAt(distanceProgress).X / CatchPlayfield.BASE_WIDTH,
Samples = new List<SampleInfo>(Samples.Select(s => new SampleInfo Samples = new List<SampleInfo>(Samples.Select(s => new SampleInfo
{ {
Bank = s.Bank, Bank = s.Bank,
@ -127,12 +127,12 @@ namespace osu.Game.Rulesets.Catch.Objects
{ {
Samples = Samples, Samples = Samples,
StartTime = spanStartTime + spanDuration, StartTime = spanStartTime + spanDuration,
X = X + Curve.PositionAt(reversed ? 0 : 1).X / CatchPlayfield.BASE_WIDTH X = X + Path.PositionAt(reversed ? 0 : 1).X / CatchPlayfield.BASE_WIDTH
}); });
} }
} }
public double EndTime => StartTime + this.SpanCount() * Curve.Distance / Velocity; public double EndTime => StartTime + this.SpanCount() * Path.Distance / Velocity;
public float EndX => X + this.CurvePositionAt(1).X / CatchPlayfield.BASE_WIDTH; public float EndX => X + this.CurvePositionAt(1).X / CatchPlayfield.BASE_WIDTH;
@ -140,24 +140,24 @@ namespace osu.Game.Rulesets.Catch.Objects
public double Distance public double Distance
{ {
get { return Curve.Distance; } get { return Path.Distance; }
set { Curve.Distance = value; } set { Path.Distance = value; }
} }
public SliderCurve Curve { get; } = new SliderCurve(); public SliderPath Path { get; } = new SliderPath();
public List<Vector2> ControlPoints public Vector2[] ControlPoints
{ {
get { return Curve.ControlPoints; } get { return Path.ControlPoints; }
set { Curve.ControlPoints = value; } set { Path.ControlPoints = value; }
} }
public List<List<SampleInfo>> NodeSamples { get; set; } = new List<List<SampleInfo>>(); public List<List<SampleInfo>> NodeSamples { get; set; } = new List<List<SampleInfo>>();
public CurveType CurveType public PathType PathType
{ {
get { return Curve.CurveType; } get { return Path.PathType; }
set { Curve.CurveType = value; } set { Path.PathType = value; }
} }
public double? LegacyLastTickOffset { get; set; } public double? LegacyLastTickOffset { get; set; }

View File

@ -31,7 +31,7 @@ namespace osu.Game.Rulesets.Catch.UI
public override PassThroughInputManager CreateInputManager() => new CatchInputManager(Ruleset.RulesetInfo); public override PassThroughInputManager CreateInputManager() => new CatchInputManager(Ruleset.RulesetInfo);
protected override DrawableHitObject<CatchHitObject> GetVisualRepresentation(CatchHitObject h) public override DrawableHitObject<CatchHitObject> GetVisualRepresentation(CatchHitObject h)
{ {
switch (h) switch (h)
{ {

View File

@ -48,7 +48,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
TargetColumns = (int)Math.Max(1, roundedCircleSize); TargetColumns = (int)Math.Max(1, roundedCircleSize);
else 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) if (percentSliderOrSpinner < 0.2)
TargetColumns = 7; TargetColumns = 7;
else if (percentSliderOrSpinner < 0.3 || roundedCircleSize >= 5) else if (percentSliderOrSpinner < 0.3 || roundedCircleSize >= 5)

View File

@ -112,7 +112,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
drainTime = 10000; drainTime = 10000;
BeatmapDifficulty difficulty = OriginalBeatmap.BeatmapInfo.BaseDifficulty; 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); conversionDifficulty = Math.Min(conversionDifficulty.Value, 12);
return conversionDifficulty.Value; return conversionDifficulty.Value;

View File

@ -1,22 +1,23 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Edit.Tools; 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;
using osu.Game.Rulesets.Mania.Objects.Drawables; using osu.Game.Rulesets.Mania.Objects.Drawables;
using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.UI;
using System.Collections.Generic; using System.Collections.Generic;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Game.Rulesets.Mania.Configuration; using osu.Game.Rulesets.Mania.Configuration;
using osu.Game.Rulesets.Mania.Edit.Masks;
using osu.Game.Rulesets.Mania.UI; using osu.Game.Rulesets.Mania.UI;
using osu.Game.Rulesets.UI;
namespace osu.Game.Rulesets.Mania.Edit namespace osu.Game.Rulesets.Mania.Edit
{ {
public class ManiaHitObjectComposer : HitObjectComposer public class ManiaHitObjectComposer : HitObjectComposer<ManiaHitObject>
{ {
protected new ManiaConfigManager Config => (ManiaConfigManager)base.Config; protected new ManiaConfigManager Config => (ManiaConfigManager)base.Config;
@ -32,22 +33,19 @@ namespace osu.Game.Rulesets.Mania.Edit
return dependencies; return dependencies;
} }
protected override RulesetContainer CreateRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap) => new ManiaEditRulesetContainer(ruleset, beatmap); protected override RulesetContainer<ManiaHitObject> CreateRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap)
=> new ManiaEditRulesetContainer(ruleset, beatmap);
protected override IReadOnlyList<ICompositionTool> CompositionTools => new ICompositionTool[] protected override IReadOnlyList<HitObjectCompositionTool> CompositionTools => Array.Empty<HitObjectCompositionTool>();
{
new HitObjectCompositionTool<Note>("Note"),
new HitObjectCompositionTool<HoldNote>("Hold"),
};
public override HitObjectMask CreateMaskFor(DrawableHitObject hitObject) public override SelectionMask CreateMaskFor(DrawableHitObject hitObject)
{ {
switch (hitObject) switch (hitObject)
{ {
case DrawableNote note: case DrawableNote note:
return new NoteMask(note); return new NoteSelectionMask(note);
case DrawableHoldNote holdNote: case DrawableHoldNote holdNote:
return new HoldNoteMask(holdNote); return new HoldNoteSelectionMask(holdNote);
} }
return base.CreateMaskFor(hitObject); return base.CreateMaskFor(hitObject);

View File

@ -13,9 +13,9 @@ using osu.Game.Rulesets.UI.Scrolling;
using OpenTK; using OpenTK;
using OpenTK.Graphics; 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; public new DrawableHoldNote HitObject => (DrawableHoldNote)base.HitObject;
@ -23,13 +23,13 @@ namespace osu.Game.Rulesets.Mania.Edit.Layers.Selection.Overlays
private readonly BodyPiece body; private readonly BodyPiece body;
public HoldNoteMask(DrawableHoldNote hold) public HoldNoteSelectionMask(DrawableHoldNote hold)
: base(hold) : base(hold)
{ {
InternalChildren = new Drawable[] InternalChildren = new Drawable[]
{ {
new HoldNoteNoteMask(hold.Head), new HoldNoteNoteSelectionMask(hold.Head),
new HoldNoteNoteMask(hold.Tail), new HoldNoteNoteSelectionMask(hold.Tail),
body = new BodyPiece body = new BodyPiece
{ {
AccentColour = Color4.Transparent AccentColour = Color4.Transparent
@ -59,9 +59,9 @@ namespace osu.Game.Rulesets.Mania.Edit.Layers.Selection.Overlays
Y -= HitObject.Tail.DrawHeight; Y -= HitObject.Tail.DrawHeight;
} }
private class HoldNoteNoteMask : NoteMask private class HoldNoteNoteSelectionMask : NoteSelectionMask
{ {
public HoldNoteNoteMask(DrawableNote note) public HoldNoteNoteSelectionMask(DrawableNote note)
: base(note) : base(note)
{ {
Select(); Select();

View File

@ -7,11 +7,11 @@ using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Mania.Objects.Drawables; using osu.Game.Rulesets.Mania.Objects.Drawables;
using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces; 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) : base(note)
{ {
Scale = note.Scale; Scale = note.Scale;

View File

@ -5,13 +5,11 @@ using System.Collections.Generic;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.UI;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.UI;
namespace osu.Game.Rulesets.Mania.Mods namespace osu.Game.Rulesets.Mania.Mods
{ {
public class ManiaModDualStages : Mod, IPlayfieldTypeMod, IApplicableToBeatmapConverter, IApplicableToRulesetContainer<ManiaHitObject> public class ManiaModDualStages : Mod, IPlayfieldTypeMod, IApplicableToBeatmapConverter, IApplicableToBeatmap<ManiaHitObject>
{ {
public override string Name => "Dual Stages"; public override string Name => "Dual Stages";
public override string ShortenedName => "DS"; public override string ShortenedName => "DS";
@ -34,22 +32,21 @@ namespace osu.Game.Rulesets.Mania.Mods
mbc.TargetColumns *= 2; mbc.TargetColumns *= 2;
} }
public void ApplyToRulesetContainer(RulesetContainer<ManiaHitObject> rulesetContainer) public void ApplyToBeatmap(Beatmap<ManiaHitObject> beatmap)
{ {
var mrc = (ManiaRulesetContainer)rulesetContainer;
// Although this can work, for now let's not allow keymods for mania-specific beatmaps
if (isForCurrentRuleset) if (isForCurrentRuleset)
return; return;
var maniaBeatmap = (ManiaBeatmap)beatmap;
var newDefinitions = new List<StageDefinition>(); var newDefinitions = new List<StageDefinition>();
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 });
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; public PlayfieldType PlayfieldType => PlayfieldType.Dual;

View File

@ -96,7 +96,7 @@ namespace osu.Game.Rulesets.Mania.UI
public override PassThroughInputManager CreateInputManager() => new ManiaInputManager(Ruleset.RulesetInfo, Variant); public override PassThroughInputManager CreateInputManager() => new ManiaInputManager(Ruleset.RulesetInfo, Variant);
protected override DrawableHitObject<ManiaHitObject> GetVisualRepresentation(ManiaHitObject h) public override DrawableHitObject<ManiaHitObject> GetVisualRepresentation(ManiaHitObject h)
{ {
switch (h) switch (h)
{ {

View File

@ -0,0 +1,19 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Rulesets.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();
}
}

View File

@ -0,0 +1,29 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.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);
}
}

View File

@ -108,7 +108,7 @@ namespace osu.Game.Rulesets.Osu.Tests
{ {
StartTime = Time.Current + 1000, StartTime = Time.Current + 1000,
Position = new Vector2(239, 176), Position = new Vector2(239, 176),
ControlPoints = new List<Vector2> ControlPoints = new[]
{ {
Vector2.Zero, Vector2.Zero,
new Vector2(154, 28), new Vector2(154, 28),
@ -141,7 +141,7 @@ namespace osu.Game.Rulesets.Osu.Tests
{ {
StartTime = Time.Current + 1000, StartTime = Time.Current + 1000,
Position = new Vector2(-(distance / 2), 0), Position = new Vector2(-(distance / 2), 0),
ControlPoints = new List<Vector2> ControlPoints = new[]
{ {
Vector2.Zero, Vector2.Zero,
new Vector2(distance, 0), new Vector2(distance, 0),
@ -161,7 +161,7 @@ namespace osu.Game.Rulesets.Osu.Tests
{ {
StartTime = Time.Current + 1000, StartTime = Time.Current + 1000,
Position = new Vector2(-200, 0), Position = new Vector2(-200, 0),
ControlPoints = new List<Vector2> ControlPoints = new[]
{ {
Vector2.Zero, Vector2.Zero,
new Vector2(200, 200), new Vector2(200, 200),
@ -181,10 +181,10 @@ namespace osu.Game.Rulesets.Osu.Tests
{ {
var slider = new Slider var slider = new Slider
{ {
CurveType = CurveType.Linear, PathType = PathType.Linear,
StartTime = Time.Current + 1000, StartTime = Time.Current + 1000,
Position = new Vector2(-200, 0), Position = new Vector2(-200, 0),
ControlPoints = new List<Vector2> ControlPoints = new[]
{ {
Vector2.Zero, Vector2.Zero,
new Vector2(150, 75), new Vector2(150, 75),
@ -207,10 +207,10 @@ namespace osu.Game.Rulesets.Osu.Tests
{ {
var slider = new Slider var slider = new Slider
{ {
CurveType = CurveType.Bezier, PathType = PathType.Bezier,
StartTime = Time.Current + 1000, StartTime = Time.Current + 1000,
Position = new Vector2(-200, 0), Position = new Vector2(-200, 0),
ControlPoints = new List<Vector2> ControlPoints = new[]
{ {
Vector2.Zero, Vector2.Zero,
new Vector2(150, 75), new Vector2(150, 75),
@ -232,10 +232,10 @@ namespace osu.Game.Rulesets.Osu.Tests
{ {
var slider = new Slider var slider = new Slider
{ {
CurveType = CurveType.Linear, PathType = PathType.Linear,
StartTime = Time.Current + 1000, StartTime = Time.Current + 1000,
Position = new Vector2(0, 0), Position = new Vector2(0, 0),
ControlPoints = new List<Vector2> ControlPoints = new[]
{ {
Vector2.Zero, Vector2.Zero,
new Vector2(-200, 0), new Vector2(-200, 0),
@ -264,8 +264,8 @@ namespace osu.Game.Rulesets.Osu.Tests
{ {
StartTime = Time.Current + 1000, StartTime = Time.Current + 1000,
Position = new Vector2(-100, 0), Position = new Vector2(-100, 0),
CurveType = CurveType.Catmull, PathType = PathType.Catmull,
ControlPoints = new List<Vector2> ControlPoints = new[]
{ {
Vector2.Zero, Vector2.Zero,
new Vector2(50, -50), new Vector2(50, -50),

View File

@ -0,0 +1,19 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Rulesets.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();
}
}

View File

@ -0,0 +1,55 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
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<Type> 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);
}
}

View File

@ -0,0 +1,20 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Rulesets.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();
}
}

View File

@ -0,0 +1,50 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
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<Type> 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) };
}
}

View File

@ -36,14 +36,17 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
StartTime = original.StartTime, StartTime = original.StartTime,
Samples = original.Samples, Samples = original.Samples,
ControlPoints = curveData.ControlPoints, ControlPoints = curveData.ControlPoints,
CurveType = curveData.CurveType, PathType = curveData.PathType,
Distance = curveData.Distance, Distance = curveData.Distance,
NodeSamples = curveData.NodeSamples, NodeSamples = curveData.NodeSamples,
RepeatCount = curveData.RepeatCount, RepeatCount = curveData.RepeatCount,
Position = positionData?.Position ?? Vector2.Zero, Position = positionData?.Position ?? Vector2.Zero,
NewCombo = comboData?.NewCombo ?? false, NewCombo = comboData?.NewCombo ?? false,
ComboOffset = comboData?.ComboOffset ?? 0, 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 <v8 maps for the same time duration.
TickDistanceMultiplier = beatmap.BeatmapInfo.BeatmapVersion < 8 ? 1f / beatmap.ControlPointInfo.DifficultyPointAt(original.StartTime).SpeedMultiplier : 1
}; };
} }
else if (endTimeData != null) else if (endTimeData != null)

View File

@ -65,7 +65,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
double hitWindowGreat = (int)(beatmap.HitObjects.First().HitWindows.Great / 2) / timeRate; double hitWindowGreat = (int)(beatmap.HitObjects.First().HitWindows.Great / 2) / timeRate;
double preempt = (int)BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.ApproachRate, 1800, 1200, 450) / timeRate; double preempt = (int)BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.ApproachRate, 1800, 1200, 450) / timeRate;
int maxCombo = beatmap.HitObjects.Count(); int maxCombo = beatmap.HitObjects.Count;
// Add the ticks + tail of the slider. 1 is subtracted because the head circle would be counted twice (once for the slider itself in the line above) // Add the ticks + tail of the slider. 1 is subtracted because the head circle would be counted twice (once for the slider itself in the line above)
maxCombo += beatmap.HitObjects.OfType<Slider>().Sum(s => s.NestedHitObjects.Count - 1); maxCombo += beatmap.HitObjects.OfType<Slider>().Sum(s => s.NestedHitObjects.Count - 1);

View File

@ -34,7 +34,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
{ {
countHitCircles = Beatmap.HitObjects.Count(h => h is HitCircle); 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) // 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<Slider>().Sum(s => s.NestedHitObjects.Count - 1); beatmapMaxCombo += Beatmap.HitObjects.OfType<Slider>().Sum(s => s.NestedHitObjects.Count - 1);
} }

View File

@ -75,15 +75,13 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
{ {
computeSliderCursorPosition(lastSlider); computeSliderCursorPosition(lastSlider);
lastCursorPosition = lastSlider.LazyEndPosition ?? lastCursorPosition; lastCursorPosition = lastSlider.LazyEndPosition ?? lastCursorPosition;
TravelDistance = lastSlider.LazyTravelDistance * scalingFactor;
} }
// Don't need to jump to reach spinners // Don't need to jump to reach spinners
if (!(BaseObject is Spinner)) if (!(BaseObject is Spinner))
JumpDistance = (BaseObject.StackedPosition * scalingFactor - lastCursorPosition * scalingFactor).Length; 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() private void setTimingValues()
@ -110,7 +108,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
progress = progress % 1; progress = progress % 1;
// ReSharper disable once PossibleInvalidOperationException (bugged in current r# version) // 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; float dist = diff.Length;
if (dist > approxFollowCircleRadius) if (dist > approxFollowCircleRadius)

View File

@ -0,0 +1,20 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Rulesets.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();
}
}

View File

@ -1,37 +0,0 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.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;
}
}
}

View File

@ -1,61 +0,0 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.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;
}
}

View File

@ -1,67 +0,0 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.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;
}
}

View File

@ -0,0 +1,44 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.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;
}
}

View File

@ -0,0 +1,42 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.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;
}
}
}

View File

@ -0,0 +1,19 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Rulesets.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);
}
}
}

View File

@ -0,0 +1,113 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.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];
}
}

View File

@ -0,0 +1,34 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.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<PathControlPointPiece> pieces;
public PathControlPointVisualiser(Slider slider)
{
this.slider = slider;
InternalChild = pieces = new Container<PathControlPointPiece> { 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]);
}
}
}

View File

@ -0,0 +1,59 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Collections.Generic;
using 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<Vector2>();
slider.Path.GetPathToProgress(vertices, 0, 1);
body.SetVertices(vertices);
Size = body.Size;
OriginPosition = body.PathOffset;
}
}
}

View File

@ -0,0 +1,36 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Rulesets.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;
}
}
}
}

View File

@ -0,0 +1,24 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Rulesets.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;
}
}

View File

@ -0,0 +1,180 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
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<Segment> segments = new List<Segment>();
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<Vector2> ControlPoints = new List<Vector2>();
public Segment(Vector2 offset)
{
ControlPoints.Add(offset);
}
public void Calculate(Vector2? cursor = null)
{
Span<Vector2> 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<Vector2> 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]);
}
}
}
}

View File

@ -0,0 +1,11 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// 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
}
}

View File

@ -0,0 +1,33 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.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;
}
}

View File

@ -0,0 +1,66 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.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);
}
}

View File

@ -0,0 +1,45 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.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;
}
}
}

View File

@ -0,0 +1,24 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Rulesets.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);
}
}

View File

@ -8,7 +8,9 @@ using osu.Game.Beatmaps;
using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Edit.Tools; using osu.Game.Rulesets.Edit.Tools;
using osu.Game.Rulesets.Objects.Drawables; 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;
using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects.Drawables;
using osu.Game.Rulesets.Osu.UI; using osu.Game.Rulesets.Osu.UI;
@ -16,32 +18,35 @@ using osu.Game.Rulesets.UI;
namespace osu.Game.Rulesets.Osu.Edit namespace osu.Game.Rulesets.Osu.Edit
{ {
public class OsuHitObjectComposer : HitObjectComposer public class OsuHitObjectComposer : HitObjectComposer<OsuHitObject>
{ {
public OsuHitObjectComposer(Ruleset ruleset) public OsuHitObjectComposer(Ruleset ruleset)
: base(ruleset) : base(ruleset)
{ {
} }
protected override RulesetContainer CreateRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap) => new OsuEditRulesetContainer(ruleset, beatmap); protected override RulesetContainer<OsuHitObject> CreateRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap)
=> new OsuEditRulesetContainer(ruleset, beatmap);
protected override IReadOnlyList<ICompositionTool> CompositionTools => new ICompositionTool[] protected override IReadOnlyList<HitObjectCompositionTool> CompositionTools => new HitObjectCompositionTool[]
{ {
new HitObjectCompositionTool<HitCircle>(), new HitCircleCompositionTool(),
new HitObjectCompositionTool<Slider>(), new SliderCompositionTool(),
new HitObjectCompositionTool<Spinner>() new SpinnerCompositionTool()
}; };
protected override Container CreateLayerContainer() => new PlayfieldAdjustmentContainer { RelativeSizeAxes = Axes.Both }; protected override Container CreateLayerContainer() => new PlayfieldAdjustmentContainer { RelativeSizeAxes = Axes.Both };
public override HitObjectMask CreateMaskFor(DrawableHitObject hitObject) public override SelectionMask CreateMaskFor(DrawableHitObject hitObject)
{ {
switch (hitObject) switch (hitObject)
{ {
case DrawableHitCircle circle: case DrawableHitCircle circle:
return new HitCircleMask(circle); return new HitCircleSelectionMask(circle);
case DrawableSlider slider: case DrawableSlider slider:
return new SliderMask(slider); return new SliderSelectionMask(slider);
case DrawableSpinner spinner:
return new SpinnerSelectionMask(spinner);
} }
return base.CreateMaskFor(hitObject); return base.CreateMaskFor(hitObject);

View File

@ -0,0 +1,20 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Rulesets.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();
}
}

View File

@ -0,0 +1,20 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Rulesets.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();
}
}

View File

@ -1,7 +1,6 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Collections.Generic;
using System.Linq; using System.Linq;
using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
@ -33,11 +32,12 @@ namespace osu.Game.Rulesets.Osu.Mods
slider.NestedHitObjects.OfType<SliderTick>().ForEach(h => h.Position = new Vector2(h.Position.X, OsuPlayfield.BASE_SIZE.Y - h.Position.Y)); slider.NestedHitObjects.OfType<SliderTick>().ForEach(h => h.Position = new Vector2(h.Position.X, OsuPlayfield.BASE_SIZE.Y - h.Position.Y));
slider.NestedHitObjects.OfType<RepeatPoint>().ForEach(h => h.Position = new Vector2(h.Position.X, OsuPlayfield.BASE_SIZE.Y - h.Position.Y)); slider.NestedHitObjects.OfType<RepeatPoint>().ForEach(h => h.Position = new Vector2(h.Position.X, OsuPlayfield.BASE_SIZE.Y - h.Position.Y));
var newControlPoints = new List<Vector2>(); var newControlPoints = new Vector2[slider.ControlPoints.Length];
slider.ControlPoints.ForEach(c => newControlPoints.Add(new Vector2(c.X, -c.Y))); 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.ControlPoints = newControlPoints;
slider.Curve?.Calculate(); // Recalculate the slider curve slider.Path?.Calculate(); // Recalculate the slider curve
} }
} }
} }

View File

@ -61,6 +61,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
Size = circle.DrawSize; Size = circle.DrawSize;
HitObject.PositionChanged += _ => Position = HitObject.StackedPosition; HitObject.PositionChanged += _ => Position = HitObject.StackedPosition;
HitObject.StackHeightChanged += _ => Position = HitObject.StackedPosition;
HitObject.ScaleChanged += s => Scale = new Vector2(s);
} }
public override Color4 AccentColour public override Color4 AccentColour

View File

@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
public readonly DrawableHitCircle HeadCircle; public readonly DrawableHitCircle HeadCircle;
public readonly DrawableSliderTail TailCircle; public readonly DrawableSliderTail TailCircle;
public readonly SliderBody Body; public readonly SnakingSliderBody Body;
public readonly SliderBall Ball; public readonly SliderBall Ball;
public DrawableSlider(Slider s) public DrawableSlider(Slider s)
@ -38,7 +38,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
InternalChildren = new Drawable[] InternalChildren = new Drawable[]
{ {
Body = new SliderBody(s) Body = new SnakingSliderBody(s)
{ {
PathWidth = s.Scale * 64, PathWidth = s.Scale * 64,
}, },
@ -85,6 +85,13 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
} }
HitObject.PositionChanged += _ => Position = HitObject.StackedPosition; 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 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); double completionProgress = MathHelper.Clamp((Time.Current - slider.StartTime) / slider.Duration, 0, 1);
foreach (var c in components.OfType<ISliderProgress>()) c.UpdateProgress(completionProgress); foreach (var c in components.OfType<ISliderProgress>()) c.UpdateProgress(completionProgress);
foreach (var c in components.OfType<ITrackSnaking>()) c.UpdateSnakingPosition(slider.Curve.PositionAt(Body.SnakedStart ?? 0), slider.Curve.PositionAt(Body.SnakedEnd ?? 0)); foreach (var c in components.OfType<ITrackSnaking>()) c.UpdateSnakingPosition(slider.Path.PositionAt(Body.SnakedStart ?? 0), slider.Path.PositionAt(Body.SnakedEnd ?? 0));
foreach (var t in components.OfType<IRequireTracking>()) t.Tracking = Ball.Tracking; foreach (var t in components.OfType<IRequireTracking>()) t.Tracking = Ball.Tracking;
Size = Body.Size; Size = Body.Size;

View File

@ -16,7 +16,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
{ {
this.slider = slider; this.slider = slider;
Position = HitObject.Position - slider.Position; h.PositionChanged += _ => updatePosition();
slider.ControlPointsChanged += _ => updatePosition();
updatePosition();
} }
protected override void Update() protected override void Update()
@ -33,5 +36,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
public Action<double> OnShake; public Action<double> OnShake;
protected override void Shake(double maximumLength) => OnShake?.Invoke(maximumLength); protected override void Shake(double maximumLength) => OnShake?.Invoke(maximumLength);
private void updatePosition() => Position = HitObject.Position - slider.Position;
} }
} }

View File

@ -8,6 +8,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
{ {
public class DrawableSliderTail : DrawableOsuHitObject, IRequireTracking public class DrawableSliderTail : DrawableOsuHitObject, IRequireTracking
{ {
private readonly Slider slider;
/// <summary> /// <summary>
/// The judgement text is provided by the <see cref="DrawableSlider"/>. /// The judgement text is provided by the <see cref="DrawableSlider"/>.
/// </summary> /// </summary>
@ -18,6 +20,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
public DrawableSliderTail(Slider slider, SliderTailCircle hitCircle) public DrawableSliderTail(Slider slider, SliderTailCircle hitCircle)
: base(hitCircle) : base(hitCircle)
{ {
this.slider = slider;
Origin = Anchor.Centre; Origin = Anchor.Centre;
RelativeSizeAxes = Axes.Both; RelativeSizeAxes = Axes.Both;
@ -25,7 +29,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
AlwaysPresent = true; AlwaysPresent = true;
Position = HitObject.Position - slider.Position; hitCircle.PositionChanged += _ => updatePosition();
slider.ControlPointsChanged += _ => updatePosition();
updatePosition();
} }
protected override void CheckForResult(bool userTriggered, double timeOffset) protected override void CheckForResult(bool userTriggered, double timeOffset)
@ -33,5 +40,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
if (!userTriggered && timeOffset >= 0) if (!userTriggered && timeOffset >= 0)
ApplyResult(r => r.Type = Tracking ? HitResult.Great : HitResult.Miss); ApplyResult(r => r.Type = Tracking ? HitResult.Great : HitResult.Miss);
} }
private void updatePosition() => Position = HitObject.Position - slider.Position;
} }
} }

View File

@ -112,6 +112,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
Alpha = 0 Alpha = 0
} }
}; };
s.PositionChanged += _ => Position = s.Position;
} }
public float Progress => MathHelper.Clamp(Disc.RotationAbsolute / 360 / Spinner.SpinsRequired, 0, 1); 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() 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) if (!spmCounter.IsPresent && Disc.Tracking)
spmCounter.FadeIn(HitObject.TimeFadeIn); spmCounter.FadeIn(HitObject.TimeFadeIn);

View File

@ -0,0 +1,20 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Collections.Generic;
using OpenTK;
namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
{
/// <summary>
/// A <see cref="SliderBody"/> with the ability to set the drawn vertices manually.
/// </summary>
public class ManualSliderBody : SliderBody
{
public new void SetVertices(IReadOnlyList<Vector2> vertices)
{
base.SetVertices(vertices);
Size = Path.Size;
}
}
}

View File

@ -4,7 +4,6 @@
using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Sprites;
using OpenTK.Graphics; using OpenTK.Graphics;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
@ -14,7 +13,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
{ {
public class NumberPiece : Container public class NumberPiece : Container
{ {
private readonly SpriteText number; private readonly SkinnableSpriteText number;
public string Text public string Text
{ {
@ -41,15 +40,14 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
}, },
Child = new Box() Child = new Box()
}, s => s.GetTexture("Play/osu/hitcircle") == null), }, s => s.GetTexture("Play/osu/hitcircle") == null),
number = new OsuSpriteText number = new SkinnableSpriteText("Play/osu/number-text", _ => new OsuSpriteText
{ {
Text = @"1",
Font = @"Venera", Font = @"Venera",
UseFullGlyphHeight = false, UseFullGlyphHeight = false,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
TextSize = 40, TextSize = 40,
Alpha = 1 }, restrictSize: false)
{
Text = @"1"
} }
}; };
} }

View File

@ -1,24 +1,22 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using System.Collections.Generic; using System.Collections.Generic;
using osu.Framework.Allocation;
using osu.Framework.Configuration;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Lines; using osu.Framework.Graphics.Lines;
using OpenTK.Graphics.ES30;
using OpenTK.Graphics;
using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.Primitives;
using osu.Game.Rulesets.Objects.Types;
using OpenTK; using OpenTK;
using OpenTK.Graphics;
using OpenTK.Graphics.ES30;
namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
{ {
public class SliderBody : Container, ISliderProgress public abstract class SliderBody : CompositeDrawable
{ {
private readonly SliderPath path; private readonly SliderPath path;
protected Path Path => path;
private readonly BufferedContainer container; private readonly BufferedContainer container;
public float PathWidth public float PathWidth
@ -30,15 +28,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
/// <summary> /// <summary>
/// Offset in absolute coordinates from the start of the curve. /// Offset in absolute coordinates from the start of the curve.
/// </summary> /// </summary>
public Vector2 PathOffset { get; private set; } public virtual Vector2 PathOffset => path.PositionInBoundingBox(path.Vertices[0]);
public readonly List<Vector2> CurrentCurve = new List<Vector2>();
public readonly Bindable<bool> SnakingIn = new Bindable<bool>();
public readonly Bindable<bool> SnakingOut = new Bindable<bool>();
public double? SnakedStart { get; private set; }
public double? SnakedEnd { get; private set; }
/// <summary> /// <summary>
/// Used to colour the path. /// Used to colour the path.
@ -74,28 +64,13 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
public Quad PathDrawQuad => container.ScreenSpaceDrawQuad; public Quad PathDrawQuad => container.ScreenSpaceDrawQuad;
private Vector2 topLeftOffset; protected SliderBody()
private readonly Slider slider;
public SliderBody(Slider s)
{ {
slider = s; InternalChild = container = new BufferedContainer
Children = new Drawable[]
{ {
container = new BufferedContainer RelativeSizeAxes = Axes.Both,
{ CacheDrawnFrameBuffer = true,
RelativeSizeAxes = Axes.Both, Child = path = new SliderPath { Blending = BlendingMode.None }
CacheDrawnFrameBuffer = true,
Children = new Drawable[]
{
path = new SliderPath
{
Blending = BlendingMode.None,
},
}
},
}; };
container.Attach(RenderbufferInternalFormat.DepthComponent16); 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 override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => path.ReceivePositionalInputAt(screenSpacePos);
public void SetRange(double p0, double p1) /// <summary>
/// Sets the vertices of the path which should be drawn by this <see cref="SliderBody"/>.
/// </summary>
/// <param name="vertices">The vertices</param>
protected void SetVertices(IReadOnlyList<Vector2> vertices)
{ {
if (p0 > p1) path.Vertices = vertices;
MathHelper.Swap(ref p0, ref p1); container.ForceRedraw();
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);
} }
private class SliderPath : SmoothPath private class SliderPath : SmoothPath

View File

@ -0,0 +1,118 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
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
{
/// <summary>
/// A <see cref="SliderBody"/> which changes its curve depending on the snaking progress.
/// </summary>
public class SnakingSliderBody : SliderBody, ISliderProgress
{
public readonly List<Vector2> CurrentCurve = new List<Vector2>();
public readonly Bindable<bool> SnakingIn = new Bindable<bool>();
public readonly Bindable<bool> SnakingOut = new Bindable<bool>();
public double? SnakedStart { get; private set; }
public double? SnakedEnd { get; private set; }
public override Vector2 PathOffset => snakedPathOffset;
/// <summary>
/// The top-left position of the path when fully snaked.
/// </summary>
private Vector2 snakedPosition;
/// <summary>
/// The offset of the path from <see cref="snakedPosition"/> when fully snaked.
/// </summary>
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);
}
}
}

View File

@ -16,13 +16,15 @@ namespace osu.Game.Rulesets.Osu.Objects
public const double OBJECT_RADIUS = 64; public const double OBJECT_RADIUS = 64;
public event Action<Vector2> PositionChanged; public event Action<Vector2> PositionChanged;
public event Action<int> StackHeightChanged;
public event Action<float> ScaleChanged;
public double TimePreempt = 600; public double TimePreempt = 600;
public double TimeFadeIn = 400; public double TimeFadeIn = 400;
private Vector2 position; private Vector2 position;
public Vector2 Position public virtual Vector2 Position
{ {
get => position; get => position;
set set
@ -44,13 +46,39 @@ namespace osu.Game.Rulesets.Osu.Objects
public Vector2 StackedEndPosition => EndPosition + StackOffset; 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 Vector2 StackOffset => new Vector2(StackHeight * Scale * -6.4f);
public double Radius => OBJECT_RADIUS * Scale; 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; } public virtual bool NewCombo { get; set; }

View File

@ -22,7 +22,9 @@ namespace osu.Game.Rulesets.Osu.Objects
/// </summary> /// </summary>
private const float base_scoring_distance = 100; private const float base_scoring_distance = 100;
public double EndTime => StartTime + this.SpanCount() * Curve.Distance / Velocity; public event Action<Vector2[]> ControlPointsChanged;
public double EndTime => StartTime + this.SpanCount() * Path.Distance / Velocity;
public double Duration => EndTime - StartTime; public double Duration => EndTime - StartTime;
public Vector2 StackedPositionAt(double t) => StackedPosition + this.CurvePositionAt(t); 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<Vector2> ControlPoints public Vector2[] ControlPoints
{ {
get { return Curve.ControlPoints; } get => Path.ControlPoints;
set { Curve.ControlPoints = value; } 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; } get { return Path.PathType; }
set { Curve.CurveType = value; } set { Path.PathType = value; }
} }
public double Distance public double Distance
{ {
get { return Curve.Distance; } get { return Path.Distance; }
set { Curve.Distance = value; } 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; } public double? LegacyLastTickOffset { get; set; }
@ -93,8 +120,21 @@ namespace osu.Game.Rulesets.Osu.Objects
/// </summary> /// </summary>
public double SpanDuration => Duration / this.SpanCount(); public double SpanDuration => Duration / this.SpanCount();
public double Velocity; /// <summary>
public double TickDistance; /// Velocity of this <see cref="Slider"/>.
/// </summary>
public double Velocity { get; private set; }
/// <summary>
/// Spacing between <see cref="SliderTick"/>s of this <see cref="Slider"/>.
/// </summary>
public double TickDistance { get; private set; }
/// <summary>
/// An extra multiplier that affects the number of <see cref="SliderTick"/>s generated by this <see cref="Slider"/>.
/// An increase in this value increases <see cref="TickDistance"/>, which reduces the number of ticks generated.
/// </summary>
public double TickDistanceMultiplier = 1;
public HitCircle HeadCircle; public HitCircle HeadCircle;
public SliderTailCircle TailCircle; public SliderTailCircle TailCircle;
@ -109,7 +149,7 @@ namespace osu.Game.Rulesets.Osu.Objects
double scoringDistance = base_scoring_distance * difficulty.SliderMultiplier * difficultyPoint.SpeedMultiplier; double scoringDistance = base_scoring_distance * difficulty.SliderMultiplier * difficultyPoint.SpeedMultiplier;
Velocity = scoringDistance / timingPoint.BeatLength; Velocity = scoringDistance / timingPoint.BeatLength;
TickDistance = scoringDistance / difficulty.SliderTickRate; TickDistance = scoringDistance / difficulty.SliderTickRate * TickDistanceMultiplier;
} }
protected override void CreateNestedHitObjects() protected override void CreateNestedHitObjects()
@ -150,7 +190,7 @@ namespace osu.Game.Rulesets.Osu.Objects
private void createTicks() private void createTicks()
{ {
var length = Curve.Distance; var length = Path.Distance;
var tickDistance = MathHelper.Clamp(TickDistance, 0, length); var tickDistance = MathHelper.Clamp(TickDistance, 0, length);
if (tickDistance == 0) return; if (tickDistance == 0) return;
@ -189,7 +229,7 @@ namespace osu.Game.Rulesets.Osu.Objects
SpanIndex = span, SpanIndex = span,
SpanStartTime = spanStartTime, SpanStartTime = spanStartTime,
StartTime = spanStartTime + timeProgress * SpanDuration, StartTime = spanStartTime + timeProgress * SpanDuration,
Position = Position + Curve.PositionAt(distanceProgress), Position = Position + Path.PositionAt(distanceProgress),
StackHeight = StackHeight, StackHeight = StackHeight,
Scale = Scale, Scale = Scale,
Samples = sampleList Samples = sampleList
@ -207,7 +247,7 @@ namespace osu.Game.Rulesets.Osu.Objects
RepeatIndex = repeatIndex, RepeatIndex = repeatIndex,
SpanDuration = SpanDuration, SpanDuration = SpanDuration,
StartTime = StartTime + repeat * SpanDuration, StartTime = StartTime + repeat * SpanDuration,
Position = Position + Curve.PositionAt(repeat % 2), Position = Position + Path.PositionAt(repeat % 2),
StackHeight = StackHeight, StackHeight = StackHeight,
Scale = Scale, Scale = Scale,
Samples = getNodeSamples(1 + repeatIndex) Samples = getNodeSamples(1 + repeatIndex)

View File

@ -7,6 +7,7 @@ using osu.Game.Rulesets.Objects.Types;
using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Osu.Judgements; using osu.Game.Rulesets.Osu.Judgements;
using OpenTK;
namespace osu.Game.Rulesets.Osu.Objects namespace osu.Game.Rulesets.Osu.Objects
{ {
@ -31,5 +32,10 @@ namespace osu.Game.Rulesets.Osu.Objects
} }
public override Judgement CreateJudgement() => new OsuJudgement(); 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.
}
} }
} }

View File

@ -31,7 +31,7 @@ namespace osu.Game.Rulesets.Osu.UI
public override PassThroughInputManager CreateInputManager() => new OsuInputManager(Ruleset.RulesetInfo); public override PassThroughInputManager CreateInputManager() => new OsuInputManager(Ruleset.RulesetInfo);
protected override DrawableHitObject<OsuHitObject> GetVisualRepresentation(OsuHitObject h) public override DrawableHitObject<OsuHitObject> GetVisualRepresentation(OsuHitObject h)
{ {
switch (h) switch (h)
{ {

View File

@ -78,7 +78,7 @@ namespace osu.Game.Rulesets.Taiko.UI
protected override Playfield CreatePlayfield() => new TaikoPlayfield(Beatmap.ControlPointInfo); protected override Playfield CreatePlayfield() => new TaikoPlayfield(Beatmap.ControlPointInfo);
protected override DrawableHitObject<TaikoHitObject> GetVisualRepresentation(TaikoHitObject h) public override DrawableHitObject<TaikoHitObject> GetVisualRepresentation(TaikoHitObject h)
{ {
switch (h) switch (h)
{ {

View File

@ -8,12 +8,14 @@ using OpenTK.Graphics;
using osu.Game.Tests.Resources; using osu.Game.Tests.Resources;
using System.Linq; using System.Linq;
using osu.Game.Audio; using osu.Game.Audio;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Objects.Types;
using osu.Game.Beatmaps.Formats; using osu.Game.Beatmaps.Formats;
using osu.Game.Beatmaps.Timing; using osu.Game.Beatmaps.Timing;
using osu.Game.Rulesets.Catch.Beatmaps; using osu.Game.Rulesets.Catch.Beatmaps;
using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Legacy; using osu.Game.Rulesets.Objects.Legacy;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Beatmaps; using osu.Game.Rulesets.Osu.Beatmaps;
using osu.Game.Skinning; using osu.Game.Skinning;
@ -22,6 +24,25 @@ namespace osu.Game.Tests.Beatmaps.Formats
[TestFixture] [TestFixture]
public class LegacyBeatmapDecoderTest 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<Beatmap>(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] [Test]
public void TestDecodeBeatmapGeneral() public void TestDecodeBeatmapGeneral()
{ {

View File

@ -0,0 +1 @@
osu file format v6

View File

@ -13,14 +13,18 @@ using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Edit; 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.Rulesets.Osu.Objects;
using osu.Game.Screens.Edit.Screens.Compose;
using osu.Game.Screens.Edit.Screens.Compose.Layers; using osu.Game.Screens.Edit.Screens.Compose.Layers;
using osu.Game.Tests.Beatmaps; using osu.Game.Tests.Beatmaps;
namespace osu.Game.Tests.Visual namespace osu.Game.Tests.Visual
{ {
[TestFixture] [TestFixture]
public class TestCaseHitObjectComposer : OsuTestCase [Cached(Type = typeof(IPlacementHandler))]
public class TestCaseHitObjectComposer : OsuTestCase, IPlacementHandler
{ {
public override IReadOnlyList<Type> RequiredTypes => new[] public override IReadOnlyList<Type> RequiredTypes => new[]
{ {
@ -29,9 +33,14 @@ namespace osu.Game.Tests.Visual
typeof(HitObjectComposer), typeof(HitObjectComposer),
typeof(OsuHitObjectComposer), typeof(OsuHitObjectComposer),
typeof(HitObjectMaskLayer), typeof(HitObjectMaskLayer),
typeof(NotNullAttribute) typeof(NotNullAttribute),
typeof(HitCirclePiece),
typeof(HitCircleSelectionMask),
typeof(HitCirclePlacementMask),
}; };
private HitObjectComposer composer;
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load() private void load()
{ {
@ -44,14 +53,12 @@ namespace osu.Game.Tests.Visual
new Slider new Slider
{ {
Position = new Vector2(128, 256), Position = new Vector2(128, 256),
ControlPoints = new List<Vector2> ControlPoints = new[]
{ {
Vector2.Zero, Vector2.Zero,
new Vector2(216, 0), new Vector2(216, 0),
}, },
Distance = 400, Distance = 216,
Velocity = 1,
TickDistance = 100,
Scale = 0.5f, Scale = 0.5f,
} }
}, },
@ -61,7 +68,15 @@ namespace osu.Game.Tests.Visual
Dependencies.CacheAs<IAdjustableClock>(clock); Dependencies.CacheAs<IAdjustableClock>(clock);
Dependencies.CacheAs<IFrameBasedClock>(clock); Dependencies.CacheAs<IFrameBasedClock>(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);
} }
} }

View File

@ -1,7 +1,7 @@
<!-- Contains required properties for osu!framework projects. --> <!-- Contains required properties for osu!framework projects. -->
<Project> <Project>
<PropertyGroup Label="C#"> <PropertyGroup Label="C#">
<LangVersion>7</LangVersion> <LangVersion>7.2</LangVersion>
</PropertyGroup> </PropertyGroup>
<PropertyGroup> <PropertyGroup>
<ApplicationManifest>..\app.manifest</ApplicationManifest> <ApplicationManifest>..\app.manifest</ApplicationManifest>

View File

@ -48,7 +48,7 @@ namespace osu.Game.Beatmaps
[JsonConverter(typeof(TypedListConverter<HitObject>))] [JsonConverter(typeof(TypedListConverter<HitObject>))]
public List<T> HitObjects = new List<T>(); public List<T> HitObjects = new List<T>();
IEnumerable<HitObject> IBeatmap.HitObjects => HitObjects; IReadOnlyList<HitObject> IBeatmap.HitObjects => HitObjects;
public virtual IEnumerable<BeatmapStatistic> GetStatistics() => Enumerable.Empty<BeatmapStatistic>(); public virtual IEnumerable<BeatmapStatistic> GetStatistics() => Enumerable.Empty<BeatmapStatistic>();

View File

@ -55,39 +55,40 @@ namespace osu.Game.Beatmaps
beatmap.BeatmapInfo = original.BeatmapInfo; beatmap.BeatmapInfo = original.BeatmapInfo;
beatmap.ControlPointInfo = original.ControlPointInfo; beatmap.ControlPointInfo = original.ControlPointInfo;
beatmap.HitObjects = original.HitObjects.SelectMany(h => convert(h, original)).ToList(); beatmap.HitObjects = convertHitObjects(original.HitObjects, original);
beatmap.Breaks = original.Breaks; beatmap.Breaks = original.Breaks;
return beatmap; return beatmap;
} }
/// <summary> private List<T> convertHitObjects(IReadOnlyList<HitObject> hitObjects, IBeatmap beatmap)
/// Converts a hit object.
/// </summary>
/// <param name="original">The hit object to convert.</param>
/// <param name="beatmap">The un-converted Beatmap.</param>
/// <returns>The converted hit object.</returns>
private IEnumerable<T> convert(HitObject original, IBeatmap beatmap)
{ {
// Check if the hitobject is already the converted type var result = new List<T>(hitObjects.Count);
T tObject = original as T;
if (tObject != null)
{
yield return tObject;
yield break;
}
var converted = ConvertHitObject(original, beatmap).ToList(); foreach (var obj in hitObjects)
ObjectConverted?.Invoke(original, converted);
// Convert the hit object
foreach (var obj in converted)
{ {
if (obj == null) if (obj is T tObj)
{
result.Add(tObj);
continue; 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;
} }
/// <summary> /// <summary>

View File

@ -19,7 +19,6 @@ namespace osu.Game.Beatmaps
[JsonIgnore] [JsonIgnore]
public int ID { get; set; } public int ID { get; set; }
//TODO: should be in database
public int BeatmapVersion; public int BeatmapVersion;
private int? onlineBeatmapID; private int? onlineBeatmapID;

View File

@ -148,11 +148,12 @@ namespace osu.Game.Beatmaps
/// </summary> /// </summary>
/// <param name="beatmapSetInfo">The <see cref="BeatmapSetInfo"/> to be downloaded.</param> /// <param name="beatmapSetInfo">The <see cref="BeatmapSetInfo"/> to be downloaded.</param>
/// <param name="noVideo">Whether the beatmap should be downloaded without video. Defaults to false.</param> /// <param name="noVideo">Whether the beatmap should be downloaded without video. Defaults to false.</param>
public void Download(BeatmapSetInfo beatmapSetInfo, bool noVideo = false) /// <returns>Downloading can happen</returns>
public bool Download(BeatmapSetInfo beatmapSetInfo, bool noVideo = false)
{ {
var existing = GetExistingDownload(beatmapSetInfo); var existing = GetExistingDownload(beatmapSetInfo);
if (existing != null || api == null) return; if (existing != null || api == null) return false;
if (!api.LocalUser.Value.IsSupporter) if (!api.LocalUser.Value.IsSupporter)
{ {
@ -161,7 +162,7 @@ namespace osu.Game.Beatmaps
Icon = FontAwesome.fa_superpowers, Icon = FontAwesome.fa_superpowers,
Text = "You gotta be an osu!supporter to download for now 'yo" Text = "You gotta be an osu!supporter to download for now 'yo"
}); });
return; return false;
} }
var downloadNotification = new DownloadNotification 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. // don't run in the main api queue as this is a long-running task.
Task.Factory.StartNew(() => request.Perform(api), TaskCreationOptions.LongRunning); Task.Factory.StartNew(() => request.Perform(api), TaskCreationOptions.LongRunning);
BeatmapDownloadBegan?.Invoke(request); BeatmapDownloadBegan?.Invoke(request);
return true;
} }
protected override void PresentCompletedImport(IEnumerable<BeatmapSetInfo> imported) protected override void PresentCompletedImport(IEnumerable<BeatmapSetInfo> imported)
@ -348,7 +350,7 @@ namespace osu.Game.Beatmaps
OnlineBeatmapSetID = beatmap.BeatmapInfo.BeatmapSet?.OnlineBeatmapSetID, OnlineBeatmapSetID = beatmap.BeatmapInfo.BeatmapSet?.OnlineBeatmapSetID,
Beatmaps = new List<BeatmapInfo>(), Beatmaps = new List<BeatmapInfo>(),
Hash = computeBeatmapSetHash(reader), Hash = computeBeatmapSetHash(reader),
Metadata = beatmap.Metadata Metadata = beatmap.Metadata,
}; };
} }

View File

@ -55,14 +55,14 @@ namespace osu.Game.Beatmaps.ControlPoints
/// </summary> /// </summary>
/// <param name="time">The time to find the sound control point at.</param> /// <param name="time">The time to find the sound control point at.</param>
/// <returns>The sound control point.</returns> /// <returns>The sound control point.</returns>
public SampleControlPoint SamplePointAt(double time) => binarySearch(SamplePoints, time, SamplePoints.FirstOrDefault()); public SampleControlPoint SamplePointAt(double time) => binarySearch(SamplePoints, time, SamplePoints.Count > 0 ? SamplePoints[0] : null);
/// <summary> /// <summary>
/// Finds the timing control point that is active at <paramref name="time"/>. /// Finds the timing control point that is active at <paramref name="time"/>.
/// </summary> /// </summary>
/// <param name="time">The time to find the timing control point at.</param> /// <param name="time">The time to find the timing control point at.</param>
/// <returns>The timing control point.</returns> /// <returns>The timing control point.</returns>
public TimingControlPoint TimingPointAt(double time) => binarySearch(TimingPoints, time, TimingPoints.FirstOrDefault()); public TimingControlPoint TimingPointAt(double time) => binarySearch(TimingPoints, time, TimingPoints.Count > 0 ? TimingPoints[0] : null);
/// <summary> /// <summary>
/// Finds the maximum BPM represented by any timing control point. /// Finds the maximum BPM represented by any timing control point.
@ -104,17 +104,26 @@ namespace osu.Game.Beatmaps.ControlPoints
if (time < list[0].Time) if (time < list[0].Time)
return prePoint ?? new T(); 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) int l = 0;
if (index >= 0) int r = list.Count - 2;
return list[index];
index = ~index; while (l <= r)
{
int pivot = l + ((r - l) >> 1);
// BinarySearch will return the index of the first element _greater_ than the search if (list[pivot].Time < time)
// This is the inactive point - the active point is the one before it (index - 1) l = pivot + 1;
return list[index - 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];
} }
} }
} }

View File

@ -71,9 +71,11 @@ namespace osu.Game.Beatmaps.Drawables
if (DownloadState.Value > DownloadStatus.NotDownloaded) if (DownloadState.Value > DownloadStatus.NotDownloaded)
return; return;
beatmaps.Download(set, noVideo); if (beatmaps.Download(set, noVideo))
{
DownloadState.Value = DownloadStatus.Downloading; // Only change state if download can happen
DownloadState.Value = DownloadStatus.Downloading;
}
} }
private void setAdded(BeatmapSetInfo s) private void setAdded(BeatmapSetInfo s)

View File

@ -26,15 +26,7 @@ namespace osu.Game.Beatmaps
Title = "no beatmaps available!" Title = "no beatmaps available!"
}, },
BeatmapSet = new BeatmapSetInfo(), BeatmapSet = new BeatmapSetInfo(),
BaseDifficulty = new BeatmapDifficulty BaseDifficulty = new BeatmapDifficulty(),
{
DrainRate = 0,
CircleSize = 0,
OverallDifficulty = 0,
ApproachRate = 0,
SliderMultiplier = 0,
SliderTickRate = 0,
},
Ruleset = new DummyRulesetInfo() Ruleset = new DummyRulesetInfo()
}) })
{ {

View File

@ -5,6 +5,7 @@ using System;
using System.Globalization; using System.Globalization;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using osu.Framework.IO.File;
using osu.Game.Beatmaps.Timing; using osu.Game.Beatmaps.Timing;
using osu.Game.Rulesets.Objects.Legacy; using osu.Game.Rulesets.Objects.Legacy;
using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.ControlPoints;
@ -58,7 +59,7 @@ namespace osu.Game.Beatmaps.Formats
hitObject.ApplyDefaults(this.beatmap.ControlPointInfo, this.beatmap.BeatmapInfo.BaseDifficulty); 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) protected override void ParseLine(Beatmap beatmap, Section section, string line)
{ {
@ -100,7 +101,7 @@ namespace osu.Game.Beatmaps.Formats
switch (pair.Key) switch (pair.Key)
{ {
case @"AudioFilename": case @"AudioFilename":
metadata.AudioFile = pair.Value; metadata.AudioFile = FileSafety.PathStandardise(pair.Value);
break; break;
case @"AudioLeadIn": case @"AudioLeadIn":
beatmap.BeatmapInfo.AudioLeadIn = int.Parse(pair.Value); beatmap.BeatmapInfo.AudioLeadIn = int.Parse(pair.Value);
@ -256,7 +257,7 @@ namespace osu.Game.Beatmaps.Formats
{ {
case EventType.Background: case EventType.Background:
string filename = split[2].Trim('"'); string filename = split[2].Trim('"');
beatmap.BeatmapInfo.Metadata.BackgroundFile = filename; beatmap.BeatmapInfo.Metadata.BackgroundFile = FileSafety.PathStandardise(filename);
break; break;
case EventType.Break: case EventType.Break:
var breakEvent = new BreakPeriod var breakEvent = new BreakPeriod

View File

@ -69,7 +69,7 @@ namespace osu.Game.Beatmaps.Formats
protected string StripComments(string line) protected string StripComments(string line)
{ {
var index = line.IndexOf("//", StringComparison.Ordinal); var index = line.AsSpan().IndexOf("//".AsSpan());
if (index > 0) if (index > 0)
return line.Substring(0, index); return line.Substring(0, index);
return line; return line;

View File

@ -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('"'));
} }
} }

View File

@ -39,7 +39,7 @@ namespace osu.Game.Beatmaps
/// <summary> /// <summary>
/// The hitobjects contained by this beatmap. /// The hitobjects contained by this beatmap.
/// </summary> /// </summary>
IEnumerable<HitObject> HitObjects { get; } IReadOnlyList<HitObject> HitObjects { get; }
/// <summary> /// <summary>
/// Returns statistics for the <see cref="HitObjects"/> contained in this beatmap. /// Returns statistics for the <see cref="HitObjects"/> contained in this beatmap.

View File

@ -41,8 +41,13 @@ namespace osu.Game.Beatmaps
beatmap = new RecyclableLazy<IBeatmap>(() => beatmap = new RecyclableLazy<IBeatmap>(() =>
{ {
var b = GetBeatmap() ?? new Beatmap(); 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; b.BeatmapInfo = BeatmapInfo;
return b; return b;
}); });

View File

@ -404,7 +404,7 @@ namespace osu.Game.Database
using (Stream s = reader.GetStream(file)) using (Stream s = reader.GetStream(file))
fileInfos.Add(new TFileModel fileInfos.Add(new TFileModel
{ {
Filename = FileSafety.PathSanitise(file), Filename = FileSafety.PathStandardise(file),
FileInfo = files.Add(s) FileInfo = files.Add(s)
}); });

View File

@ -30,7 +30,7 @@ namespace osu.Game.Graphics.UserInterface
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(OsuColour colours) private void load(OsuColour colours)
{ {
if (accentColour == default(Color4)) if (accentColour == default)
accentColour = colours.PinkDarker; accentColour = colours.PinkDarker;
updateAccentColour(); updateAccentColour();
} }

View File

@ -53,7 +53,7 @@ namespace osu.Game.Graphics.UserInterface
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(OsuColour colours) private void load(OsuColour colours)
{ {
if (accentColour == default(Color4)) if (accentColour == default)
AccentColour = colours.Blue; AccentColour = colours.Blue;
} }
@ -142,7 +142,7 @@ namespace osu.Game.Graphics.UserInterface
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(OsuColour colours) private void load(OsuColour colours)
{ {
if (accentColour == default(Color4)) if (accentColour == default)
AccentColour = colours.Blue; AccentColour = colours.Blue;
} }

View File

@ -134,7 +134,7 @@ namespace osu.Game.Graphics.UserInterface
/// </summary> /// </summary>
public virtual void ResetCount() public virtual void ResetCount()
{ {
SetCountWithoutRolling(default(T)); SetCountWithoutRolling(default);
} }
/// <summary> /// <summary>

View File

@ -0,0 +1,380 @@
// <auto-generated />
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<int>("ID")
.ValueGeneratedOnAdd();
b.Property<float>("ApproachRate");
b.Property<float>("CircleSize");
b.Property<float>("DrainRate");
b.Property<float>("OverallDifficulty");
b.Property<double>("SliderMultiplier");
b.Property<double>("SliderTickRate");
b.HasKey("ID");
b.ToTable("BeatmapDifficulty");
});
modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b =>
{
b.Property<int>("ID")
.ValueGeneratedOnAdd();
b.Property<int>("AudioLeadIn");
b.Property<int>("BaseDifficultyID");
b.Property<int>("BeatDivisor");
b.Property<int>("BeatmapSetInfoID");
b.Property<bool>("Countdown");
b.Property<double>("DistanceSpacing");
b.Property<int>("GridSize");
b.Property<string>("Hash");
b.Property<bool>("Hidden");
b.Property<bool>("LetterboxInBreaks");
b.Property<string>("MD5Hash");
b.Property<int?>("MetadataID");
b.Property<int?>("OnlineBeatmapID");
b.Property<string>("Path");
b.Property<int>("RulesetID");
b.Property<bool>("SpecialStyle");
b.Property<float>("StackLeniency");
b.Property<double>("StarDifficulty");
b.Property<int>("Status");
b.Property<string>("StoredBookmarks");
b.Property<double>("TimelineZoom");
b.Property<string>("Version");
b.Property<bool>("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<int>("ID")
.ValueGeneratedOnAdd();
b.Property<string>("Artist");
b.Property<string>("ArtistUnicode");
b.Property<string>("AudioFile");
b.Property<string>("AuthorString")
.HasColumnName("Author");
b.Property<string>("BackgroundFile");
b.Property<int>("PreviewTime");
b.Property<string>("Source");
b.Property<string>("Tags");
b.Property<string>("Title");
b.Property<string>("TitleUnicode");
b.HasKey("ID");
b.ToTable("BeatmapMetadata");
});
modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b =>
{
b.Property<int>("ID")
.ValueGeneratedOnAdd();
b.Property<int>("BeatmapSetInfoID");
b.Property<int>("FileInfoID");
b.Property<string>("Filename")
.IsRequired();
b.HasKey("ID");
b.HasIndex("BeatmapSetInfoID");
b.HasIndex("FileInfoID");
b.ToTable("BeatmapSetFileInfo");
});
modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b =>
{
b.Property<int>("ID")
.ValueGeneratedOnAdd();
b.Property<bool>("DeletePending");
b.Property<string>("Hash");
b.Property<int?>("MetadataID");
b.Property<int?>("OnlineBeatmapSetID");
b.Property<bool>("Protected");
b.Property<int>("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<int>("ID")
.ValueGeneratedOnAdd();
b.Property<int>("IntKey")
.HasColumnName("Key");
b.Property<int?>("RulesetID");
b.Property<string>("StringValue")
.HasColumnName("Value");
b.Property<int?>("Variant");
b.HasKey("ID");
b.HasIndex("RulesetID", "Variant");
b.ToTable("Settings");
});
modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b =>
{
b.Property<int>("ID")
.ValueGeneratedOnAdd();
b.Property<int>("IntAction")
.HasColumnName("Action");
b.Property<string>("KeysString")
.HasColumnName("Keys");
b.Property<int?>("RulesetID");
b.Property<int?>("Variant");
b.HasKey("ID");
b.HasIndex("IntAction");
b.HasIndex("RulesetID", "Variant");
b.ToTable("KeyBinding");
});
modelBuilder.Entity("osu.Game.IO.FileInfo", b =>
{
b.Property<int>("ID")
.ValueGeneratedOnAdd();
b.Property<string>("Hash");
b.Property<int>("ReferenceCount");
b.HasKey("ID");
b.HasIndex("Hash")
.IsUnique();
b.HasIndex("ReferenceCount");
b.ToTable("FileInfo");
});
modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b =>
{
b.Property<int?>("ID")
.ValueGeneratedOnAdd();
b.Property<bool>("Available");
b.Property<string>("InstantiationInfo");
b.Property<string>("Name");
b.Property<string>("ShortName");
b.HasKey("ID");
b.HasIndex("Available");
b.HasIndex("ShortName")
.IsUnique();
b.ToTable("RulesetInfo");
});
modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b =>
{
b.Property<int>("ID")
.ValueGeneratedOnAdd();
b.Property<int>("FileInfoID");
b.Property<string>("Filename")
.IsRequired();
b.Property<int>("SkinInfoID");
b.HasKey("ID");
b.HasIndex("FileInfoID");
b.HasIndex("SkinInfoID");
b.ToTable("SkinFileInfo");
});
modelBuilder.Entity("osu.Game.Skinning.SkinInfo", b =>
{
b.Property<int>("ID")
.ValueGeneratedOnAdd();
b.Property<string>("Creator");
b.Property<bool>("DeletePending");
b.Property<string>("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
}
}
}

View File

@ -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)
{
}
}
}

View File

@ -14,7 +14,7 @@ namespace osu.Game.Migrations
{ {
#pragma warning disable 612, 618 #pragma warning disable 612, 618
modelBuilder modelBuilder
.HasAnnotation("ProductVersion", "2.1.2-rtm-30932"); .HasAnnotation("ProductVersion", "2.1.3-rtm-32065");
modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b =>
{ {

View File

@ -1,16 +1,14 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Collections.Generic;
using System.ComponentModel; using System.ComponentModel;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Overlays; using osu.Game.Overlays;
using osu.Game.Overlays.Direct; using osu.Game.Overlays.Direct;
using osu.Game.Rulesets; using osu.Game.Rulesets;
namespace osu.Game.Online.API.Requests namespace osu.Game.Online.API.Requests
{ {
public class SearchBeatmapSetsRequest : APIRequest<IEnumerable<APIBeatmapSet>> public class SearchBeatmapSetsRequest : APIRequest<SearchBeatmapSetsResponse>
{ {
private readonly string query; private readonly string query;
private readonly RulesetInfo ruleset; private readonly RulesetInfo ruleset;
@ -35,6 +33,7 @@ namespace osu.Game.Online.API.Requests
public enum BeatmapSearchCategory public enum BeatmapSearchCategory
{ {
Any = 7, Any = 7,
[Description("Ranked & Approved")] [Description("Ranked & Approved")]
RankedApproved = 0, RankedApproved = 0,
Approved = 1, Approved = 1,
@ -43,6 +42,7 @@ namespace osu.Game.Online.API.Requests
Qualified = 3, Qualified = 3,
Pending = 4, Pending = 4,
Graveyard = 5, Graveyard = 5,
[Description("My Maps")] [Description("My Maps")]
MyMaps = 6, MyMaps = 6,
} }

View File

@ -0,0 +1,20 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Collections.Generic;
using Newtonsoft.Json;
using osu.Game.Online.API.Requests.Responses;
namespace osu.Game.Online.API.Requests
{
public class SearchBeatmapSetsResponse
{
public IEnumerable<APIBeatmapSet> BeatmapSets;
/// <summary>
/// A collection of parameters which should be passed to the search endpoint to fetch the next page.
/// </summary>
[JsonProperty("cursor")]
public dynamic CursorJson;
}
}

View File

@ -288,7 +288,7 @@ namespace osu.Game.Overlays
{ {
Task.Run(() => 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. // may not need scheduling; loads async internally.
Schedule(() => Schedule(() =>

View File

@ -74,7 +74,7 @@ namespace osu.Game.Overlays.Profile.Header
Anchor = Anchor.BottomCentre, Anchor = Anchor.BottomCentre,
Origin = Anchor.BottomCentre, Origin = Anchor.BottomCentre,
RelativeSizeAxes = Axes.X, RelativeSizeAxes = Axes.X,
Height = 75, Height = 60,
Y = -secondary_textsize, Y = -secondary_textsize,
Alpha = 0, Alpha = 0,
} }

View File

@ -0,0 +1,106 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.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
{
/// <summary>
/// The <see cref="Playfield"/> contained by this <see cref="EditRulesetContainer"/>.
/// </summary>
public abstract Playfield Playfield { get; }
internal EditRulesetContainer()
{
RelativeSizeAxes = Axes.Both;
}
/// <summary>
/// Adds a <see cref="HitObject"/> to the <see cref="Beatmap"/> and displays a visual representation of it.
/// </summary>
/// <param name="hitObject">The <see cref="HitObject"/> to add.</param>
/// <returns>The visual representation of <paramref name="hitObject"/>.</returns>
internal abstract DrawableHitObject Add(HitObject hitObject);
/// <summary>
/// Removes a <see cref="HitObject"/> from the <see cref="Beatmap"/> and the display.
/// </summary>
/// <param name="hitObject">The <see cref="HitObject"/> to remove.</param>
/// <returns>The visual representation of the removed <paramref name="hitObject"/>.</returns>
internal abstract DrawableHitObject Remove(HitObject hitObject);
}
public class EditRulesetContainer<TObject> : EditRulesetContainer
where TObject : HitObject
{
public override Playfield Playfield => rulesetContainer.Playfield;
private Ruleset ruleset => rulesetContainer.Ruleset;
private Beatmap<TObject> beatmap => rulesetContainer.Beatmap;
private readonly RulesetContainer<TObject> rulesetContainer;
public EditRulesetContainer(RulesetContainer<TObject> 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;
}
}
}

Some files were not shown because too many files have changed in this diff Show More