1
0
mirror of https://github.com/ppy/osu.git synced 2024-09-22 01:27:29 +08:00

Merge branch 'master' into spinner-placement-2

This commit is contained in:
Dean Herbert 2018-11-01 18:05:44 +09:00 committed by GitHub
commit 1265d5ac0b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
45 changed files with 591 additions and 121 deletions

7
.gitignore vendored
View File

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

View File

@ -25,6 +25,7 @@ Build and run
- Using Visual Studio 2017, Rider or Visual Studio Code (configurations are included)
- From command line using `dotnet run --project osu.Desktop`. When building for non-development purposes, add `-c Release` to gain higher performance.
- To run with code analysis, instead use `powershell ./build.ps1` or `build.sh`. This is currently only supported under windows due to [resharper cli shortcomings](https://youtrack.jetbrains.com/issue/RSRP-410004). Alternative, you can install resharper or use rider to get inline support in your IDE of choice.
Note: If you run from command line under linux, you will need to prefix the output folder to your `LD_LIBRARY_PATH`. See `.vscode/launch.json` for an example

View File

@ -1,22 +1,8 @@
clone_depth: 1
version: '{branch}-{build}'
image: Visual Studio 2017
configuration: Debug
cache:
- C:\ProgramData\chocolatey\bin -> appveyor.yml
- C:\ProgramData\chocolatey\lib -> appveyor.yml
test: off
install:
- cmd: git submodule update --init --recursive --depth=5
- cmd: choco install resharper-clt -y
- cmd: choco install nvika -y
- cmd: dotnet tool install CodeFileSanity --version 0.0.16 --global
before_build:
- cmd: CodeFileSanity
- cmd: nuget restore -verbosity quiet
build:
project: osu.sln
parallel: true
verbosity: minimal
after_build:
- cmd: inspectcode --o="inspectcodereport.xml" --projects:osu.Game* --caches-home="inspectcode" osu.sln > NUL
- cmd: NVika parsereport "inspectcodereport.xml" --treatwarningsaserrors
build_script:
- cmd: PowerShell -Version 2.0 .\build.ps1

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

View File

@ -42,7 +42,7 @@ namespace osu.Game.Rulesets.Catch.Tests
Vector2.Zero,
new Vector2(width * CatchPlayfield.BASE_WIDTH, 0)
},
CurveType = CurveType.Linear,
PathType = PathType.Linear,
Distance = width * CatchPlayfield.BASE_WIDTH,
StartTime = i * 2000,
NewCombo = i % 8 == 0

View File

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

View File

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

View File

@ -5,13 +5,13 @@ using System;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Edit.Tools;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.Objects.Drawables;
using osu.Game.Rulesets.Objects.Drawables;
using System.Collections.Generic;
using osu.Framework.Allocation;
using osu.Game.Rulesets.Mania.Configuration;
using osu.Game.Rulesets.Mania.Edit.Masks;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.UI;
using osu.Game.Rulesets.UI;

View File

@ -5,13 +5,11 @@ using System.Collections.Generic;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.UI;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.UI;
namespace osu.Game.Rulesets.Mania.Mods
{
public class ManiaModDualStages : Mod, IPlayfieldTypeMod, IApplicableToBeatmapConverter, IApplicableToRulesetContainer<ManiaHitObject>
public class ManiaModDualStages : Mod, IPlayfieldTypeMod, IApplicableToBeatmapConverter, IApplicableToBeatmap<ManiaHitObject>
{
public override string Name => "Dual Stages";
public override string ShortenedName => "DS";
@ -34,22 +32,21 @@ namespace osu.Game.Rulesets.Mania.Mods
mbc.TargetColumns *= 2;
}
public void ApplyToRulesetContainer(RulesetContainer<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)
return;
var maniaBeatmap = (ManiaBeatmap)beatmap;
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 });
}
mrc.Beatmap.Stages = newDefinitions;
maniaBeatmap.Stages = newDefinitions;
}
public PlayfieldType PlayfieldType => PlayfieldType.Dual;

View File

@ -181,7 +181,7 @@ namespace osu.Game.Rulesets.Osu.Tests
{
var slider = new Slider
{
CurveType = CurveType.Linear,
PathType = PathType.Linear,
StartTime = Time.Current + 1000,
Position = new Vector2(-200, 0),
ControlPoints = new[]
@ -207,7 +207,7 @@ namespace osu.Game.Rulesets.Osu.Tests
{
var slider = new Slider
{
CurveType = CurveType.Bezier,
PathType = PathType.Bezier,
StartTime = Time.Current + 1000,
Position = new Vector2(-200, 0),
ControlPoints = new[]
@ -232,7 +232,7 @@ namespace osu.Game.Rulesets.Osu.Tests
{
var slider = new Slider
{
CurveType = CurveType.Linear,
PathType = PathType.Linear,
StartTime = Time.Current + 1000,
Position = new Vector2(0, 0),
ControlPoints = new[]
@ -264,7 +264,7 @@ namespace osu.Game.Rulesets.Osu.Tests
{
StartTime = Time.Current + 1000,
Position = new Vector2(-100, 0),
CurveType = CurveType.Catmull,
PathType = PathType.Catmull,
ControlPoints = new[]
{
Vector2.Zero,

View File

@ -1,11 +1,14 @@
// 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;
@ -15,6 +18,16 @@ 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()
@ -28,7 +41,7 @@ namespace osu.Game.Rulesets.Osu.Tests
new Vector2(150, 150),
new Vector2(300, 0)
},
CurveType = CurveType.Bezier,
PathType = PathType.Bezier,
Distance = 350
};

View File

@ -36,7 +36,7 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
StartTime = original.StartTime,
Samples = original.Samples,
ControlPoints = curveData.ControlPoints,
CurveType = curveData.CurveType,
PathType = curveData.PathType,
Distance = curveData.Distance,
RepeatSamples = curveData.RepeatSamples,
RepeatCount = curveData.RepeatCount,

View File

@ -108,7 +108,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
progress = progress % 1;
// ReSharper disable once PossibleInvalidOperationException (bugged in current r# version)
var diff = slider.StackedPosition + slider.Curve.PositionAt(progress) - slider.LazyEndPosition.Value;
var diff = slider.StackedPosition + slider.Path.PositionAt(progress) - slider.LazyEndPosition.Value;
float dist = diff.Length;
if (dist > approxFollowCircleRadius)

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

@ -46,6 +46,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Masks.SliderMasks.Components
// Need to cause one update
body.UpdateProgress(0);
body.Refresh();
}
}
}

View File

@ -16,6 +16,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Masks.SliderMasks.Components
{
this.slider = slider;
this.position = position;
slider.ControlPointsChanged += _ => UpdatePosition();
}
protected override void UpdatePosition()
@ -23,10 +25,10 @@ namespace osu.Game.Rulesets.Osu.Edit.Masks.SliderMasks.Components
switch (position)
{
case SliderPosition.Start:
Position = slider.StackedPosition + slider.Curve.PositionAt(0);
Position = slider.StackedPosition + slider.Path.PositionAt(0);
break;
case SliderPosition.End:
Position = slider.StackedPosition + slider.Curve.PositionAt(1);
Position = slider.StackedPosition + slider.Path.PositionAt(1);
break;
}
}

View File

@ -24,6 +24,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Masks.SliderMasks
new SliderBodyPiece(sliderObject),
headMask = new SliderCircleSelectionMask(slider.HeadCircle, sliderObject, SliderPosition.Start),
new SliderCircleSelectionMask(slider.TailCircle, sliderObject, SliderPosition.End),
new PathControlPointVisualiser(sliderObject),
};
}

View File

@ -37,7 +37,7 @@ namespace osu.Game.Rulesets.Osu.Mods
newControlPoints[i] = new Vector2(slider.ControlPoints[i].X, -slider.ControlPoints[i].Y);
slider.ControlPoints = newControlPoints;
slider.Curve?.Calculate(); // Recalculate the slider curve
slider.Path?.Calculate(); // Recalculate the slider curve
}
}
}

View File

@ -85,6 +85,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
}
HitObject.PositionChanged += _ => Position = HitObject.StackedPosition;
slider.ControlPointsChanged += _ => Body.Refresh();
}
public override Color4 AccentColour
@ -119,7 +121,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
double completionProgress = MathHelper.Clamp((Time.Current - slider.StartTime) / slider.Duration, 0, 1);
foreach (var c in components.OfType<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;
Size = Body.Size;

View File

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

View File

@ -45,15 +45,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
[BackgroundDependencyLoader]
private void load()
{
// Generate the entire curve
slider.Curve.GetPathToProgress(CurrentCurve, 0, 1);
SetVertices(CurrentCurve);
// The body is sized to the full path size to avoid excessive autosize computations
Size = Path.Size;
snakedPosition = Path.PositionInBoundingBox(Vector2.Zero);
snakedPathOffset = Path.PositionInBoundingBox(Path.Vertices[0]);
Refresh();
}
public void UpdateProgress(double completionProgress)
@ -80,6 +72,27 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
setRange(start, end);
}
public void Refresh()
{
// Generate the entire curve
slider.Path.GetPathToProgress(CurrentCurve, 0, 1);
SetVertices(CurrentCurve);
// The body is sized to the full path size to avoid excessive autosize computations
Size = Path.Size;
snakedPosition = Path.PositionInBoundingBox(Vector2.Zero);
snakedPathOffset = Path.PositionInBoundingBox(Path.Vertices[0]);
var lastSnakedStart = SnakedStart ?? 0;
var lastSnakedEnd = SnakedEnd ?? 0;
SnakedStart = null;
SnakedEnd = null;
setRange(lastSnakedStart, lastSnakedEnd);
}
private void setRange(double p0, double p1)
{
if (p0 > p1)
@ -90,7 +103,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
SnakedStart = p0;
SnakedEnd = p1;
slider.Curve.GetPathToProgress(CurrentCurve, p0, p1);
slider.Path.GetPathToProgress(CurrentCurve, p0, p1);
SetVertices(CurrentCurve);

View File

@ -22,7 +22,9 @@ namespace osu.Game.Rulesets.Osu.Objects
/// </summary>
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 Vector2 StackedPositionAt(double t) => StackedPosition + this.CurvePositionAt(t);
@ -50,24 +52,34 @@ namespace osu.Game.Rulesets.Osu.Objects
}
}
public SliderCurve Curve { get; } = new SliderCurve();
public SliderPath Path { get; } = new SliderPath();
public Vector2[] ControlPoints
{
get { return Curve.ControlPoints; }
set { Curve.ControlPoints = value; }
get => Path.ControlPoints;
set
{
if (Path.ControlPoints == value)
return;
Path.ControlPoints = value;
ControlPointsChanged?.Invoke(value);
if (TailCircle != null)
TailCircle.Position = EndPosition;
}
}
public CurveType CurveType
public PathType PathType
{
get { return Curve.CurveType; }
set { Curve.CurveType = value; }
get { return Path.PathType; }
set { Path.PathType = value; }
}
public double Distance
{
get { return Curve.Distance; }
set { Curve.Distance = value; }
get { return Path.Distance; }
set { Path.Distance = value; }
}
public override Vector2 Position
@ -177,7 +189,7 @@ namespace osu.Game.Rulesets.Osu.Objects
private void createTicks()
{
var length = Curve.Distance;
var length = Path.Distance;
var tickDistance = MathHelper.Clamp(TickDistance, 0, length);
if (tickDistance == 0) return;
@ -216,7 +228,7 @@ namespace osu.Game.Rulesets.Osu.Objects
SpanIndex = span,
SpanStartTime = spanStartTime,
StartTime = spanStartTime + timeProgress * SpanDuration,
Position = Position + Curve.PositionAt(distanceProgress),
Position = Position + Path.PositionAt(distanceProgress),
StackHeight = StackHeight,
Scale = Scale,
Samples = sampleList
@ -234,7 +246,7 @@ namespace osu.Game.Rulesets.Osu.Objects
RepeatIndex = repeatIndex,
SpanDuration = SpanDuration,
StartTime = StartTime + repeat * SpanDuration,
Position = Position + Curve.PositionAt(repeat % 2),
Position = Position + Path.PositionAt(repeat % 2),
StackHeight = StackHeight,
Scale = Scale,
Samples = new List<SampleInfo>(RepeatSamples[repeatIndex])

View File

@ -15,6 +15,9 @@ using OpenTK;
namespace osu.Game.Rulesets.Edit
{
/// <summary>
/// A mask which governs the creation of a new <see cref="HitObject"/> to actualisation.
/// </summary>
public abstract class PlacementMask : CompositeDrawable, IRequireHighFrequencyMousePosition
{
/// <summary>
@ -81,6 +84,8 @@ namespace osu.Game.Rulesets.Edit
switch (e)
{
case ScrollEvent _:
return false;
case MouseEvent _:
return true;
default:

View File

@ -0,0 +1,22 @@
// 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.Rulesets.Objects;
namespace osu.Game.Rulesets.Mods
{
/// <summary>
/// Interface for a <see cref="Mod"/> that applies changes to a <see cref="Beatmap"/>
/// after conversion and post-processing has completed.
/// </summary>
public interface IApplicableToBeatmap<TObject> : IApplicableMod
where TObject : HitObject
{
/// <summary>
/// Applies this <see cref="IApplicableToBeatmap{TObject}"/> to a <see cref="Beatmap{TObject}"/>.
/// </summary>
/// <param name="beatmap">The <see cref="Beatmap{TObject}"/> to apply to.</param>
void ApplyToBeatmap(Beatmap<TObject> beatmap);
}
}

View File

@ -37,7 +37,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Catch
};
}
protected override HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, Vector2[] controlPoints, double length, CurveType curveType, int repeatCount, List<List<SampleInfo>> repeatSamples)
protected override HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, Vector2[] controlPoints, double length, PathType pathType, int repeatCount, List<List<SampleInfo>> repeatSamples)
{
newCombo |= forceNewCombo;
comboOffset += extraComboOffset;
@ -52,7 +52,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Catch
ComboOffset = comboOffset,
ControlPoints = controlPoints,
Distance = length,
CurveType = curveType,
PathType = pathType,
RepeatSamples = repeatSamples,
RepeatCount = repeatCount
};

View File

@ -70,7 +70,7 @@ namespace osu.Game.Rulesets.Objects.Legacy
}
else if (type.HasFlag(ConvertHitObjectType.Slider))
{
CurveType curveType = CurveType.Catmull;
PathType pathType = PathType.Catmull;
double length = 0;
string[] pointSplit = split[5].Split('|');
@ -90,16 +90,16 @@ namespace osu.Game.Rulesets.Objects.Legacy
switch (t)
{
case @"C":
curveType = CurveType.Catmull;
pathType = PathType.Catmull;
break;
case @"B":
curveType = CurveType.Bezier;
pathType = PathType.Bezier;
break;
case @"L":
curveType = CurveType.Linear;
pathType = PathType.Linear;
break;
case @"P":
curveType = CurveType.PerfectCurve;
pathType = PathType.PerfectCurve;
break;
}
@ -113,8 +113,8 @@ namespace osu.Game.Rulesets.Objects.Legacy
// osu-stable special-cased colinear perfect curves to a CurveType.Linear
bool isLinear(Vector2[] p) => Precision.AlmostEquals(0, (p[1].Y - p[0].Y) * (p[2].X - p[0].X) - (p[1].X - p[0].X) * (p[2].Y - p[0].Y));
if (points.Length == 3 && curveType == CurveType.PerfectCurve && isLinear(points))
curveType = CurveType.Linear;
if (points.Length == 3 && pathType == PathType.PerfectCurve && isLinear(points))
pathType = PathType.Linear;
int repeatCount = Convert.ToInt32(split[6], CultureInfo.InvariantCulture);
@ -178,7 +178,7 @@ namespace osu.Game.Rulesets.Objects.Legacy
for (int i = 0; i < nodes; i++)
nodeSamples.Add(convertSoundType(nodeSoundTypes[i], nodeBankInfos[i]));
result = CreateSlider(pos, combo, comboOffset, points, length, curveType, repeatCount, nodeSamples);
result = CreateSlider(pos, combo, comboOffset, points, length, pathType, repeatCount, nodeSamples);
}
else if (type.HasFlag(ConvertHitObjectType.Spinner))
{
@ -268,11 +268,11 @@ namespace osu.Game.Rulesets.Objects.Legacy
/// <param name="comboOffset">When starting a new combo, the offset of the new combo relative to the current one.</param>
/// <param name="controlPoints">The slider control points.</param>
/// <param name="length">The slider length.</param>
/// <param name="curveType">The slider curve type.</param>
/// <param name="pathType">The slider curve type.</param>
/// <param name="repeatCount">The slider repeat count.</param>
/// <param name="repeatSamples">The samples to be played when the repeat nodes are hit. This includes the head and tail of the slider.</param>
/// <returns>The hit object.</returns>
protected abstract HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, Vector2[] controlPoints, double length, CurveType curveType, int repeatCount, List<List<SampleInfo>> repeatSamples);
protected abstract HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, Vector2[] controlPoints, double length, PathType pathType, int repeatCount, List<List<SampleInfo>> repeatSamples);
/// <summary>
/// Creates a legacy Spinner-type hit object.

View File

@ -20,9 +20,9 @@ namespace osu.Game.Rulesets.Objects.Legacy
/// <summary>
/// <see cref="ConvertSlider"/>s don't need a curve since they're converted to ruleset-specific hitobjects.
/// </summary>
public SliderCurve Curve { get; } = null;
public SliderPath Path { get; } = null;
public Vector2[] ControlPoints { get; set; }
public CurveType CurveType { get; set; }
public PathType PathType { get; set; }
public double Distance { get; set; }

View File

@ -26,14 +26,14 @@ namespace osu.Game.Rulesets.Objects.Legacy.Mania
};
}
protected override HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, Vector2[] controlPoints, double length, CurveType curveType, int repeatCount, List<List<SampleInfo>> repeatSamples)
protected override HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, Vector2[] controlPoints, double length, PathType pathType, int repeatCount, List<List<SampleInfo>> repeatSamples)
{
return new ConvertSlider
{
X = position.X,
ControlPoints = controlPoints,
Distance = length,
CurveType = curveType,
PathType = pathType,
RepeatSamples = repeatSamples,
RepeatCount = repeatCount
};

View File

@ -38,7 +38,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Osu
};
}
protected override HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, Vector2[] controlPoints, double length, CurveType curveType, int repeatCount, List<List<SampleInfo>> repeatSamples)
protected override HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, Vector2[] controlPoints, double length, PathType pathType, int repeatCount, List<List<SampleInfo>> repeatSamples)
{
newCombo |= forceNewCombo;
comboOffset += extraComboOffset;
@ -53,7 +53,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Osu
ComboOffset = comboOffset,
ControlPoints = controlPoints,
Distance = Math.Max(0, length),
CurveType = curveType,
PathType = pathType,
RepeatSamples = repeatSamples,
RepeatCount = repeatCount
};

View File

@ -23,13 +23,13 @@ namespace osu.Game.Rulesets.Objects.Legacy.Taiko
return new ConvertHit();
}
protected override HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, Vector2[] controlPoints, double length, CurveType curveType, int repeatCount, List<List<SampleInfo>> repeatSamples)
protected override HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, Vector2[] controlPoints, double length, PathType pathType, int repeatCount, List<List<SampleInfo>> repeatSamples)
{
return new ConvertSlider
{
ControlPoints = controlPoints,
Distance = length,
CurveType = curveType,
PathType = pathType,
RepeatSamples = repeatSamples,
RepeatCount = repeatCount
};

View File

@ -10,13 +10,13 @@ using OpenTK;
namespace osu.Game.Rulesets.Objects
{
public class SliderCurve
public class SliderPath
{
public double Distance;
public Vector2[] ControlPoints;
public CurveType CurveType = CurveType.PerfectCurve;
public PathType PathType = PathType.PerfectCurve;
public Vector2 Offset;
@ -25,15 +25,15 @@ namespace osu.Game.Rulesets.Objects
private List<Vector2> calculateSubpath(ReadOnlySpan<Vector2> subControlPoints)
{
switch (CurveType)
switch (PathType)
{
case CurveType.Linear:
case PathType.Linear:
var result = new List<Vector2>(subControlPoints.Length);
foreach (var c in subControlPoints)
result.Add(c);
return result;
case CurveType.PerfectCurve:
case PathType.PerfectCurve:
//we can only use CircularArc iff we have exactly three control points and no dissection.
if (ControlPoints.Length != 3 || subControlPoints.Length != 3)
break;
@ -46,7 +46,7 @@ namespace osu.Game.Rulesets.Objects
break;
return subpath;
case CurveType.Catmull:
case PathType.Catmull:
return new CatmullApproximator(subControlPoints).CreateCatmull();
}
@ -93,7 +93,7 @@ namespace osu.Game.Rulesets.Objects
Vector2 diff = calculatedPath[i + 1] - calculatedPath[i];
double d = diff.Length;
// Shorten slider curves that are too long compared to what's
// Shorten slider paths that are too long compared to what's
// in the .osu file.
if (Distance - l < d)
{
@ -109,7 +109,7 @@ namespace osu.Game.Rulesets.Objects
cumulativeLength.Add(l);
}
// Lengthen slider curves that are too short compared to what's
// Lengthen slider paths that are too short compared to what's
// in the .osu file.
if (l < Distance && calculatedPath.Count > 1)
{
@ -124,10 +124,33 @@ namespace osu.Game.Rulesets.Objects
}
}
public void Calculate()
private void calculateCumulativeLength()
{
double l = 0;
cumulativeLength.Clear();
cumulativeLength.Add(l);
for (int i = 0; i < calculatedPath.Count - 1; ++i)
{
Vector2 diff = calculatedPath[i + 1] - calculatedPath[i];
double d = diff.Length;
l += d;
cumulativeLength.Add(l);
}
Distance = l;
}
public void Calculate(bool updateDistance = false)
{
calculatePath();
if (!updateDistance)
calculateCumulativeLengthAndTrimPath();
else
calculateCumulativeLength();
}
private int indexOfDistance(double d)
@ -168,10 +191,10 @@ namespace osu.Game.Rulesets.Objects
}
/// <summary>
/// Computes the slider curve until a given progress that ranges from 0 (beginning of the slider)
/// Computes the slider path until a given progress that ranges from 0 (beginning of the slider)
/// to 1 (end of the slider) and stores the generated path in the given list.
/// </summary>
/// <param name="path">The list to be filled with the computed curve.</param>
/// <param name="path">The list to be filled with the computed path.</param>
/// <param name="p0">Start progress. Ranges from 0 (beginning of the slider) to 1 (end of the slider).</param>
/// <param name="p1">End progress. Ranges from 0 (beginning of the slider) to 1 (end of the slider).</param>
public void GetPathToProgress(List<Vector2> path, double p0, double p1)
@ -196,10 +219,10 @@ namespace osu.Game.Rulesets.Objects
}
/// <summary>
/// Computes the position on the slider at a given progress that ranges from 0 (beginning of the curve)
/// to 1 (end of the curve).
/// Computes the position on the slider at a given progress that ranges from 0 (beginning of the path)
/// to 1 (end of the path).
/// </summary>
/// <param name="progress">Ranges from 0 (beginning of the curve) to 1 (end of the curve).</param>
/// <param name="progress">Ranges from 0 (beginning of the path) to 1 (end of the path).</param>
/// <returns></returns>
public Vector2 PositionAt(double progress)
{

View File

@ -13,7 +13,7 @@ namespace osu.Game.Rulesets.Objects.Types
/// <summary>
/// The curve.
/// </summary>
SliderCurve Curve { get; }
SliderPath Path { get; }
/// <summary>
/// The control points that shape the curve.
@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Objects.Types
/// <summary>
/// The type of curve.
/// </summary>
CurveType CurveType { get; }
PathType PathType { get; }
}
public static class HasCurveExtensions
@ -35,7 +35,7 @@ namespace osu.Game.Rulesets.Objects.Types
/// <param name="progress">[0, 1] where 0 is the start time of the <see cref="HitObject"/> and 1 is the end time of the <see cref="HitObject"/>.</param>
/// <returns>The position on the curve.</returns>
public static Vector2 CurvePositionAt(this IHasCurve obj, double progress)
=> obj.Curve.PositionAt(obj.ProgressAt(progress));
=> obj.Path.PositionAt(obj.ProgressAt(progress));
/// <summary>
/// Computes the progress along the curve relative to how much of the <see cref="HitObject"/> has been completed.

View File

@ -3,7 +3,7 @@
namespace osu.Game.Rulesets.Objects.Types
{
public enum CurveType
public enum PathType
{
Catmull,
Bezier,

View File

@ -238,6 +238,8 @@ namespace osu.Game.Rulesets.UI
KeyBindingInputManager = CreateInputManager();
KeyBindingInputManager.RelativeSizeAxes = Axes.Both;
applyBeatmapMods(Mods);
}
[BackgroundDependencyLoader]
@ -255,16 +257,29 @@ namespace osu.Game.Rulesets.UI
KeyBindingInputManager.Add(Cursor);
// Apply mods
applyMods(Mods, config);
applyRulesetMods(Mods, config);
loadObjects();
}
/// <summary>
/// Applies the active mods to the Beatmap.
/// </summary>
/// <param name="mods"></param>
private void applyBeatmapMods(IEnumerable<Mod> mods)
{
if (mods == null)
return;
foreach (var mod in mods.OfType<IApplicableToBeatmap<TObject>>())
mod.ApplyToBeatmap(Beatmap);
}
/// <summary>
/// Applies the active mods to this RulesetContainer.
/// </summary>
/// <param name="mods"></param>
private void applyMods(IEnumerable<Mod> mods, OsuConfigManager config)
private void applyRulesetMods(IEnumerable<Mod> mods, OsuConfigManager config)
{
if (mods == null)
return;

View File

@ -87,6 +87,7 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers
placementHandler.Delete(h.HitObject.HitObject);
return true;
}
return base.OnKeyDown(e);
}

View File

@ -134,11 +134,13 @@ namespace osu.Game.Screens.Play
{
PlayerSettingsOverlay.Show();
ModDisplay.FadeIn(200);
KeyCounter.Margin = new MarginPadding(10) { Bottom = 30 };
}
else
{
PlayerSettingsOverlay.Hide();
ModDisplay.Delay(2000).FadeOut(200);
KeyCounter.Margin = new MarginPadding(10);
}
}

View File

@ -21,7 +21,7 @@ namespace osu.Game.Screens.Play
{
private const int bottom_bar_height = 5;
private static readonly Vector2 handle_size = new Vector2(14, 25);
private static readonly Vector2 handle_size = new Vector2(10, 18);
private const float transition_duration = 200;
@ -135,6 +135,8 @@ namespace osu.Game.Screens.Play
{
bar.FadeTo(allowSeeking ? 1 : 0, transition_duration, Easing.In);
this.MoveTo(new Vector2(0, allowSeeking ? 0 : bottom_bar_height), transition_duration, Easing.In);
info.Margin = new MarginPadding { Bottom = Height - (allowSeeking ? 0 : handle_size.Y) };
}
protected override void PopIn()

View File

@ -350,7 +350,7 @@ namespace osu.Game.Screens.Select
}
}
ensurePlayingSelected(preview);
if (IsCurrentScreen) ensurePlayingSelected(preview);
UpdateBeatmap(Beatmap.Value);
}

View File

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

11
tools/cakebuild.csproj Normal file
View File

@ -0,0 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<PackAsTool>true</PackAsTool>
<TargetFrameworks>netcoreapp2.0</TargetFrameworks>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Cake" Version="0.30.0" />
<PackageReference Include="Cake.CoreCLR" Version="0.30.0" />
</ItemGroup>
</Project>