1
0
mirror of https://github.com/ppy/osu.git synced 2026-05-14 18:22:54 +08:00

Compare commits

...

753 Commits

441 changed files with 6926 additions and 3771 deletions
+14
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
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 -->
@@ -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 -->
@@ -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
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
+4 -1
View File
@@ -24,7 +24,10 @@ Clone the repository including submodules
Build and run
- Using Visual Studio 2017, Rider or Visual Studio Code (configurations are included)
- From command line using `dotnet run --project osu.Desktop`
- 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
If you run into issues building you may need to restore nuget packages (commonly via `dotnet restore`). Visual Studio Code users must run `Restore` task from debug tab before attempt to build.
+3 -17
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
-30
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/A6g5K/4d08705438.enc # signing certificate
- cmd: appveyor-tools\secure-file -decrypt 4d08705438.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
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
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
Executable
+37
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
View File
@@ -0,0 +1,5 @@
[Nuget]
Source=https://api.nuget.org/v3/index.json
UseInProcessClient=true
LoadDependencies=true
+2 -6
View File
@@ -2,7 +2,6 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using System.Threading.Tasks;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
@@ -28,11 +27,8 @@ namespace osu.Desktop.Overlays
private NotificationOverlay notificationOverlay;
private GameHost host;
public override bool HandleKeyboardInput => false;
public override bool HandleMouseInput => false;
[BackgroundDependencyLoader]
private async Task load(NotificationOverlay notification, OsuColour colours, TextureStore textures, OsuGameBase game, OsuConfigManager config, GameHost host)
private void load(NotificationOverlay notification, OsuColour colours, TextureStore textures, OsuGameBase game, OsuConfigManager config, GameHost host)
{
notificationOverlay = notification;
this.config = config;
@@ -87,7 +83,7 @@ namespace osu.Desktop.Overlays
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
Texture = await textures.GetAsync(@"Menu/dev-build-footer"),
Texture = textures.Get(@"Menu/dev-build-footer"),
},
}
}
+3 -3
View File
@@ -27,9 +27,9 @@
</ItemGroup>
<ItemGroup Label="Package References">
<PackageReference Include="System.IO.Packaging" Version="4.5.0" />
<PackageReference Include="ppy.squirrel.windows" Version="1.8.0.5" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="2.1.2" />
<PackageReference Include="ppy.squirrel.windows" Version="1.9.0.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="2.1.4" />
</ItemGroup>
<ItemGroup Label="Resources">
<EmbeddedResource Include="lazer.ico" />
@@ -1,7 +1,6 @@
// 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 System.Linq;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Catch.Objects;
@@ -38,12 +37,12 @@ namespace osu.Game.Rulesets.Catch.Tests
beatmap.HitObjects.Add(new JuiceStream
{
X = 0.5f - width / 2,
ControlPoints = new List<Vector2>
ControlPoints = new[]
{
Vector2.Zero,
new Vector2(width * CatchPlayfield.BASE_WIDTH, 0)
},
CurveType = CurveType.Linear,
PathType = PathType.Linear,
Distance = width * CatchPlayfield.BASE_WIDTH,
StartTime = i * 2000,
NewCombo = i % 8 == 0
@@ -2,9 +2,10 @@
<Import Project="..\osu.TestProject.props" />
<ItemGroup Label="Package References">
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.8.0" />
<PackageReference Include="NUnit" Version="3.10.1" />
<PackageReference Include="NUnit3TestAdapter" Version="3.10.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.9.0" />
<PackageReference Include="NUnit" Version="3.11.0" />
<PackageReference Include="NUnit3TestAdapter" Version="3.11.0" />
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
</ItemGroup>
<PropertyGroup Label="Project">
<OutputType>WinExe</OutputType>
@@ -35,9 +35,9 @@ 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,
NodeSamples = curveData.NodeSamples,
RepeatCount = curveData.RepeatCount,
X = (positionData?.X ?? 0) / CatchPlayfield.BASE_WIDTH,
NewCombo = comboData?.NewCombo ?? false,
@@ -74,42 +74,42 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
private void initialiseHyperDash(List<CatchHitObject> objects)
{
// todo: add difficulty adjust.
double halfCatcherWidth = CatcherArea.CATCHER_SIZE * (objects.FirstOrDefault()?.Scale ?? 1) / CatchPlayfield.BASE_WIDTH / 2;
List<CatchHitObject> objectWithDroplets = new List<CatchHitObject>();
foreach (var currentObject in objects)
{
if (currentObject is Fruit)
objectWithDroplets.Add(currentObject);
if (currentObject is JuiceStream)
foreach (var currentJuiceElement in currentObject.NestedHitObjects)
if (!(currentJuiceElement is TinyDroplet))
objectWithDroplets.Add((CatchHitObject)currentJuiceElement);
}
objectWithDroplets.Sort((h1, h2) => h1.StartTime.CompareTo(h2.StartTime));
double halfCatcherWidth = CatcherArea.GetCatcherSize(Beatmap.BeatmapInfo.BaseDifficulty) / 2;
int lastDirection = 0;
double lastExcess = halfCatcherWidth;
int objCount = objects.Count;
for (int i = 0; i < objCount - 1; i++)
for (int i = 0; i < objectWithDroplets.Count - 1; i++)
{
CatchHitObject currentObject = objects[i];
// not needed?
// if (currentObject is TinyDroplet) continue;
CatchHitObject nextObject = objects[i + 1];
// while (nextObject is TinyDroplet)
// {
// if (++i == objCount - 1) break;
// nextObject = objects[i + 1];
// }
CatchHitObject currentObject = objectWithDroplets[i];
CatchHitObject nextObject = objectWithDroplets[i + 1];
int thisDirection = nextObject.X > currentObject.X ? 1 : -1;
double timeToNext = nextObject.StartTime - ((currentObject as IHasEndTime)?.EndTime ?? currentObject.StartTime) - 4;
double timeToNext = nextObject.StartTime - currentObject.StartTime;
double distanceToNext = Math.Abs(nextObject.X - currentObject.X) - (lastDirection == thisDirection ? lastExcess : halfCatcherWidth);
if (timeToNext * CatcherArea.Catcher.BASE_SPEED < distanceToNext)
float distanceToHyper = (float)(timeToNext * CatcherArea.Catcher.BASE_SPEED - distanceToNext);
if (distanceToHyper < 0)
{
currentObject.HyperDashTarget = nextObject;
lastExcess = halfCatcherWidth;
}
else
{
//currentObject.DistanceToHyperDash = timeToNext - distanceToNext;
lastExcess = MathHelper.Clamp(timeToNext - distanceToNext, 0, halfCatcherWidth);
currentObject.DistanceToHyperDash = distanceToHyper;
lastExcess = MathHelper.Clamp(distanceToHyper, 0, halfCatcherWidth);
}
lastDirection = thisDirection;
@@ -16,7 +16,7 @@ namespace osu.Game.Rulesets.Catch.Objects
public int IndexInBeatmap { get; set; }
public virtual FruitVisualRepresentation VisualRepresentation => (FruitVisualRepresentation)(IndexInBeatmap % 4);
public virtual FruitVisualRepresentation VisualRepresentation => (FruitVisualRepresentation)(ComboIndex % 4);
public virtual bool NewCombo { get; set; }
@@ -27,7 +27,9 @@ namespace osu.Game.Rulesets.Catch.Objects
public int ComboIndex { get; set; }
/// <summary>
/// The distance for a fruit to to next hyper if it's not a hyper.
/// Difference between the distance to the next object
/// and the distance that would have triggered a hyper dash.
/// A value close to 0 indicates a difficult jump (for difficulty calculation).
/// </summary>
public float DistanceToHyperDash { get; set; }
+15 -15
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 List<Vector2> ControlPoints
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 List<List<SampleInfo>> NodeSamples { 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; }
+26 -22
View File
@@ -5,11 +5,13 @@ using System;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Beatmaps;
using osu.Game.Configuration;
using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Catch.Objects.Drawable;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.UI.Scrolling;
using OpenTK;
namespace osu.Game.Rulesets.Catch.UI
{
@@ -17,13 +19,13 @@ namespace osu.Game.Rulesets.Catch.UI
{
public const float BASE_WIDTH = 512;
protected override Container<Drawable> Content => content;
private readonly Container<Drawable> content;
private readonly CatcherArea catcherArea;
protected override bool UserScrollSpeedAdjustment => false;
protected override SpeedChangeVisualisationMethod VisualisationMethod => SpeedChangeVisualisationMethod.Constant;
public CatchPlayfield(BeatmapDifficulty difficulty, Func<CatchHitObject, DrawableHitObject<CatchHitObject>> getVisualRepresentation)
: base(BASE_WIDTH)
{
Direction.Value = ScrollingDirection.Down;
@@ -32,27 +34,29 @@ namespace osu.Game.Rulesets.Catch.UI
Anchor = Anchor.TopCentre;
Origin = Anchor.TopCentre;
base.Content.Anchor = Anchor.BottomLeft;
base.Content.Origin = Anchor.BottomLeft;
Size = new Vector2(0.86f); // matches stable's vertical offset for catcher plate
base.Content.AddRange(new Drawable[]
InternalChild = new PlayfieldAdjustmentContainer
{
explodingFruitContainer = new Container
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
{
RelativeSizeAxes = Axes.Both,
},
catcherArea = new CatcherArea(difficulty)
{
GetVisualRepresentation = getVisualRepresentation,
ExplodingFruitTarget = explodingFruitContainer,
Anchor = Anchor.BottomLeft,
Origin = Anchor.TopLeft,
},
content = new Container<Drawable>
{
RelativeSizeAxes = Axes.Both,
},
});
explodingFruitContainer = new Container
{
RelativeSizeAxes = Axes.Both,
},
catcherArea = new CatcherArea(difficulty)
{
GetVisualRepresentation = getVisualRepresentation,
ExplodingFruitTarget = explodingFruitContainer,
Anchor = Anchor.BottomLeft,
Origin = Anchor.TopLeft,
},
HitObjectContainer
}
};
VisibleTimeRange.Value = BeatmapDifficulty.DifficultyRange(difficulty.ApproachRate, 1800, 1200, 450);
}
public bool CheckIfWeCanCatch(CatchHitObject obj) => catcherArea.AttemptCatch(obj);
@@ -13,7 +13,6 @@ using osu.Game.Rulesets.Replays;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.UI;
using osu.Game.Rulesets.UI.Scrolling;
using OpenTK;
namespace osu.Game.Rulesets.Catch.UI
{
@@ -32,9 +31,7 @@ namespace osu.Game.Rulesets.Catch.UI
public override PassThroughInputManager CreateInputManager() => new CatchInputManager(Ruleset.RulesetInfo);
protected override Vector2 PlayfieldArea => new Vector2(0.86f); // matches stable's vertical offset for catcher plate
protected override DrawableHitObject<CatchHitObject> GetVisualRepresentation(CatchHitObject h)
public override DrawableHitObject<CatchHitObject> GetVisualRepresentation(CatchHitObject h)
{
switch (h)
{
+14 -12
View File
@@ -3,7 +3,6 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
@@ -26,7 +25,7 @@ namespace osu.Game.Rulesets.Catch.UI
{
public class CatcherArea : Container
{
public const float CATCHER_SIZE = 84;
public const float CATCHER_SIZE = 100;
protected readonly Catcher MovableCatcher;
@@ -108,6 +107,11 @@ namespace osu.Game.Rulesets.Catch.UI
public bool AttemptCatch(CatchHitObject obj) => MovableCatcher.AttemptCatch(obj);
public static float GetCatcherSize(BeatmapDifficulty difficulty)
{
return CATCHER_SIZE / CatchPlayfield.BASE_WIDTH * (1.0f - 0.7f * (difficulty.CircleSize - 5) / 5);
}
public class Catcher : Container, IKeyBindingHandler<CatchAction>
{
/// <summary>
@@ -408,9 +412,7 @@ namespace osu.Game.Rulesets.Catch.UI
/// </summary>
public void Explode()
{
var fruit = caughtFruit.ToArray();
foreach (var f in fruit)
foreach (var f in caughtFruit.ToArray())
Explode(f);
}
@@ -423,15 +425,15 @@ namespace osu.Game.Rulesets.Catch.UI
fruit.Anchor = Anchor.TopLeft;
fruit.Position = caughtFruit.ToSpaceOfOtherDrawable(fruit.DrawPosition, ExplodingFruitTarget);
caughtFruit.Remove(fruit);
if (!caughtFruit.Remove(fruit))
// we may have already been removed by a previous operation (due to the weird OnLoadComplete scheduling).
// this avoids a crash on potentially attempting to Add a fruit to ExplodingFruitTarget twice.
return;
ExplodingFruitTarget.Add(fruit);
}
fruit.MoveToY(fruit.Y - 50, 250, Easing.OutSine)
.Then()
.MoveToY(fruit.Y + 50, 500, Easing.InSine);
fruit.MoveToY(fruit.Y - 50, 250, Easing.OutSine).Then().MoveToY(fruit.Y + 50, 500, Easing.InSine);
fruit.MoveToX(fruit.X + originalX * 6, 1000);
fruit.FadeOut(750);
@@ -449,9 +451,9 @@ namespace osu.Game.Rulesets.Catch.UI
}
[BackgroundDependencyLoader]
private async Task load(TextureStore textures)
private void load(TextureStore textures)
{
Texture = await textures.GetAsync(@"Play/Catch/fruit-catcher-idle");
Texture = textures.Get(@"Play/Catch/fruit-catcher-idle");
}
}
}
@@ -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.Graphics;
using osu.Framework.Graphics.Containers;
using OpenTK;
namespace osu.Game.Rulesets.Catch.UI
{
public class PlayfieldAdjustmentContainer : Container
{
protected override Container<Drawable> Content => content;
private readonly Container content;
public PlayfieldAdjustmentContainer()
{
InternalChild = new Container
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both,
FillMode = FillMode.Fit,
FillAspectRatio = 4f / 3,
Child = content = new ScalingContainer { RelativeSizeAxes = Axes.Both }
};
}
/// <summary>
/// A <see cref="Container"/> which scales its content relative to a target width.
/// </summary>
private class ScalingContainer : Container
{
protected override void Update()
{
base.Update();
Scale = new Vector2(Parent.ChildSize.X / CatchPlayfield.BASE_WIDTH);
Size = Vector2.Divide(Vector2.One, Scale);
}
}
}
}
@@ -2,9 +2,10 @@
<Import Project="..\osu.TestProject.props" />
<ItemGroup Label="Package References">
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.8.0" />
<PackageReference Include="NUnit" Version="3.10.1" />
<PackageReference Include="NUnit3TestAdapter" Version="3.10.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.9.0" />
<PackageReference Include="NUnit" Version="3.11.0" />
<PackageReference Include="NUnit3TestAdapter" Version="3.11.0" />
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
</ItemGroup>
<PropertyGroup Label="Project">
<OutputType>WinExe</OutputType>
@@ -48,7 +48,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
TargetColumns = (int)Math.Max(1, roundedCircleSize);
else
{
float percentSliderOrSpinner = (float)beatmap.HitObjects.Count(h => h is IHasEndTime) / beatmap.HitObjects.Count();
float percentSliderOrSpinner = (float)beatmap.HitObjects.Count(h => h is IHasEndTime) / beatmap.HitObjects.Count;
if (percentSliderOrSpinner < 0.2)
TargetColumns = 7;
else if (percentSliderOrSpinner < 0.3 || roundedCircleSize >= 5)
@@ -247,7 +247,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
double segmentTime = (curveData.EndTime - HitObject.StartTime) / curveData.SpanCount();
int index = (int)(segmentTime == 0 ? 0 : (time - HitObject.StartTime) / segmentTime);
return curveData.RepeatSamples[index];
return curveData.NodeSamples[index];
}
}
}
@@ -470,7 +470,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
double segmentTime = (EndTime - HitObject.StartTime) / spanCount;
int index = (int)(segmentTime == 0 ? 0 : (time - HitObject.StartTime) / segmentTime);
return curveData.RepeatSamples[index];
return curveData.NodeSamples[index];
}
/// <summary>
@@ -112,7 +112,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
drainTime = 10000;
BeatmapDifficulty difficulty = OriginalBeatmap.BeatmapInfo.BaseDifficulty;
conversionDifficulty = ((difficulty.DrainRate + MathHelper.Clamp(difficulty.ApproachRate, 4, 7)) / 1.5 + (double)OriginalBeatmap.HitObjects.Count() / drainTime * 9f) / 38f * 5f / 1.15;
conversionDifficulty = ((difficulty.DrainRate + MathHelper.Clamp(difficulty.ApproachRate, 4, 7)) / 1.5 + (double)OriginalBeatmap.HitObjects.Count / drainTime * 9f) / 38f * 5f / 1.15;
conversionDifficulty = Math.Min(conversionDifficulty.Value, 12);
return conversionDifficulty.Value;
@@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Mania.Configuration
{
base.InitialiseDefaults();
Set(ManiaSetting.ScrollTime, 1500.0, 50.0, 10000.0, 50.0);
Set(ManiaSetting.ScrollTime, 2250.0, 50.0, 10000.0, 50.0);
Set(ManiaSetting.ScrollDirection, ManiaScrollingDirection.Down);
}
@@ -13,9 +13,9 @@ using osu.Game.Rulesets.UI.Scrolling;
using OpenTK;
using OpenTK.Graphics;
namespace osu.Game.Rulesets.Mania.Edit.Layers.Selection.Overlays
namespace osu.Game.Rulesets.Mania.Edit.Blueprints
{
public class HoldNoteMask : HitObjectMask
public class HoldNoteSelectionBlueprint : SelectionBlueprint
{
public new DrawableHoldNote HitObject => (DrawableHoldNote)base.HitObject;
@@ -23,13 +23,13 @@ namespace osu.Game.Rulesets.Mania.Edit.Layers.Selection.Overlays
private readonly BodyPiece body;
public HoldNoteMask(DrawableHoldNote hold)
public HoldNoteSelectionBlueprint(DrawableHoldNote hold)
: base(hold)
{
InternalChildren = new Drawable[]
{
new HoldNoteNoteMask(hold.Head),
new HoldNoteNoteMask(hold.Tail),
new HoldNoteNoteSelectionBlueprint(hold.Head),
new HoldNoteNoteSelectionBlueprint(hold.Tail),
body = new BodyPiece
{
AccentColour = Color4.Transparent
@@ -59,9 +59,9 @@ namespace osu.Game.Rulesets.Mania.Edit.Layers.Selection.Overlays
Y -= HitObject.Tail.DrawHeight;
}
private class HoldNoteNoteMask : NoteMask
private class HoldNoteNoteSelectionBlueprint : NoteSelectionBlueprint
{
public HoldNoteNoteMask(DrawableNote note)
public HoldNoteNoteSelectionBlueprint(DrawableNote note)
: base(note)
{
Select();
@@ -78,7 +78,7 @@ namespace osu.Game.Rulesets.Mania.Edit.Layers.Selection.Overlays
}
// Todo: This is temporary, since the note masks don't do anything special yet. In the future they will handle input.
public override bool HandleMouseInput => false;
public override bool HandlePositionalInput => false;
}
}
}
@@ -7,11 +7,11 @@ using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Mania.Objects.Drawables;
using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces;
namespace osu.Game.Rulesets.Mania.Edit.Layers.Selection.Overlays
namespace osu.Game.Rulesets.Mania.Edit.Blueprints
{
public class NoteMask : HitObjectMask
public class NoteSelectionBlueprint : SelectionBlueprint
{
public NoteMask(DrawableNote note)
public NoteSelectionBlueprint(DrawableNote note)
: base(note)
{
Scale = note.Scale;
@@ -20,8 +20,7 @@ namespace osu.Game.Rulesets.Mania.Edit
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = Vector2.One
};
protected override Vector2 PlayfieldArea => Vector2.One;
}
}
@@ -1,22 +1,23 @@
// 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 osu.Game.Beatmaps;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Edit.Tools;
using osu.Game.Rulesets.Mania.Edit.Layers.Selection.Overlays;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.Objects.Drawables;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.UI;
using System.Collections.Generic;
using osu.Framework.Allocation;
using osu.Game.Rulesets.Mania.Configuration;
using osu.Game.Rulesets.Mania.Edit.Blueprints;
using osu.Game.Rulesets.Mania.UI;
using osu.Game.Rulesets.UI;
namespace osu.Game.Rulesets.Mania.Edit
{
public class ManiaHitObjectComposer : HitObjectComposer
public class ManiaHitObjectComposer : HitObjectComposer<ManiaHitObject>
{
protected new ManiaConfigManager Config => (ManiaConfigManager)base.Config;
@@ -32,25 +33,22 @@ namespace osu.Game.Rulesets.Mania.Edit
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[]
{
new HitObjectCompositionTool<Note>("Note"),
new HitObjectCompositionTool<HoldNote>("Hold"),
};
protected override IReadOnlyList<HitObjectCompositionTool> CompositionTools => Array.Empty<HitObjectCompositionTool>();
public override HitObjectMask CreateMaskFor(DrawableHitObject hitObject)
public override SelectionBlueprint CreateBlueprintFor(DrawableHitObject hitObject)
{
switch (hitObject)
{
case DrawableNote note:
return new NoteMask(note);
return new NoteSelectionBlueprint(note);
case DrawableHoldNote holdNote:
return new HoldNoteMask(holdNote);
return new HoldNoteSelectionBlueprint(holdNote);
}
return base.CreateMaskFor(hitObject);
return base.CreateBlueprintFor(hitObject);
}
}
}
@@ -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;
@@ -111,6 +111,18 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
bodyPiece.Height = DrawHeight - Head.Height / 2 + Tail.Height / 2;
}
protected void BeginHold()
{
holdStartTime = Time.Current;
bodyPiece.Hitting = true;
}
protected void EndHold()
{
holdStartTime = null;
bodyPiece.Hitting = false;
}
public bool OnPressed(ManiaAction action)
{
// Make sure the action happened within the body of the hold note
@@ -123,8 +135,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
// The user has pressed during the body of the hold note, after the head note and its hit windows have passed
// and within the limited range of the above if-statement. This state will be managed by the head note if the
// user has pressed during the hit windows of the head note.
holdStartTime = Time.Current;
BeginHold();
return true;
}
@@ -137,7 +148,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
if (action != Action.Value)
return false;
holdStartTime = null;
EndHold();
// If the key has been released too early, the user should not receive full score for the release
if (!Tail.IsHit)
@@ -170,7 +181,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
// The head note also handles early hits before the body, but we want accurate early hits to count as the body being held
// The body doesn't handle these early early hits, so we have to explicitly set the holding state here
holdNote.holdStartTime = Time.Current;
holdNote.BeginHold();
return true;
}
@@ -32,6 +32,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces
background = new Box { RelativeSizeAxes = Axes.Both },
foreground = new BufferedContainer
{
Blending = BlendingMode.Additive,
RelativeSizeAxes = Axes.Both,
CacheDrawnFrameBuffer = true,
Children = new Drawable[]
@@ -73,6 +74,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces
}
private Color4 accentColour;
public Color4 AccentColour
{
get { return accentColour; }
@@ -86,6 +88,16 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces
}
}
public bool Hitting
{
get { return hitting; }
set
{
hitting = value;
updateAccentColour();
}
}
private Cached subtractionCache = new Cached();
public override bool Invalidate(Invalidation invalidation = Invalidation.All, Drawable source = null, bool shallPropagate = true)
@@ -118,13 +130,26 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces
}
}
private bool hitting;
private void updateAccentColour()
{
if (!IsLoaded)
return;
foreground.Colour = AccentColour.Opacity(0.9f);
background.Colour = AccentColour.Opacity(0.6f);
foreground.Colour = AccentColour.Opacity(0.5f);
background.Colour = AccentColour.Opacity(0.7f);
const float animation_length = 50;
foreground.ClearTransforms(false, nameof(foreground.Colour));
if (hitting)
{
// wait for the next sync point
double synchronisedOffset = animation_length * 2 - Time.Current % (animation_length * 2);
using (foreground.BeginDelayedSequence(synchronisedOffset))
foreground.FadeColour(AccentColour.Lighten(0.2f), animation_length).Then().FadeColour(foreground.Colour, animation_length).Loop();
}
subtractionCache.Invalidate();
}
+4 -3
View File
@@ -30,8 +30,6 @@ namespace osu.Game.Rulesets.Mania.UI
internal readonly Container TopLevelContainer;
private readonly Container explosionContainer;
protected override Container<Drawable> Content => hitObjectArea;
public Column()
{
RelativeSizeAxes = Axes.Y;
@@ -54,7 +52,10 @@ namespace osu.Game.Rulesets.Mania.UI
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
{
hitObjectArea = new ColumnHitObjectArea { RelativeSizeAxes = Axes.Both },
hitObjectArea = new ColumnHitObjectArea(HitObjectContainer)
{
RelativeSizeAxes = Axes.Both,
},
explosionContainer = new Container
{
Name = "Hit explosions",
@@ -8,28 +8,24 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Game.Graphics;
using osu.Game.Rulesets.UI;
using osu.Game.Rulesets.UI.Scrolling;
using OpenTK.Graphics;
namespace osu.Game.Rulesets.Mania.UI.Components
{
public class ColumnHitObjectArea : Container, IHasAccentColour
public class ColumnHitObjectArea : CompositeDrawable, IHasAccentColour
{
private const float hit_target_height = 10;
private const float hit_target_bar_height = 2;
private Container<Drawable> content;
protected override Container<Drawable> Content => content;
private readonly IBindable<ScrollingDirection> direction = new Bindable<ScrollingDirection>();
private Container hitTargetLine;
private readonly Container hitTargetLine;
private readonly Drawable hitTargetBar;
[BackgroundDependencyLoader]
private void load(IScrollingInfo scrollingInfo)
public ColumnHitObjectArea(HitObjectContainer hitObjectContainer)
{
Drawable hitTargetBar;
InternalChildren = new[]
{
hitTargetBar = new Box
@@ -45,13 +41,13 @@ namespace osu.Game.Rulesets.Mania.UI.Components
Masking = true,
Child = new Box { RelativeSizeAxes = Axes.Both }
},
content = new Container
{
Name = "Hit objects",
RelativeSizeAxes = Axes.Both,
},
hitObjectContainer
};
}
[BackgroundDependencyLoader]
private void load(IScrollingInfo scrollingInfo)
{
direction.BindTo(scrollingInfo.Direction);
direction.BindValueChanged(direction =>
{
@@ -30,10 +30,11 @@ namespace osu.Game.Rulesets.Mania.UI
if (Result.IsHit)
{
this.ScaleTo(0.8f);
this.ScaleTo(1, 250, Easing.OutElastic);
JudgementBody.ScaleTo(0.8f);
JudgementBody.ScaleTo(1, 250, Easing.OutElastic);
this.Delay(50).FadeOut(200).ScaleTo(0.75f, 250);
JudgementBody.Delay(50).ScaleTo(0.75f, 250);
this.Delay(50).FadeOut(200);
}
Expire();
@@ -10,6 +10,7 @@ using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Mania.Configuration;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Objects.Drawables;
using OpenTK;
namespace osu.Game.Rulesets.Mania.UI
{
@@ -25,6 +26,8 @@ namespace osu.Game.Rulesets.Mania.UI
if (stageDefinitions.Count <= 0)
throw new ArgumentException("Can't have zero or fewer stages.");
Size = new Vector2(1, 0.8f);
GridContainer playfieldGrid;
AddInternal(playfieldGrid = new GridContainer
{
@@ -24,7 +24,6 @@ using osu.Game.Rulesets.Replays;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.UI;
using osu.Game.Rulesets.UI.Scrolling;
using OpenTK;
namespace osu.Game.Rulesets.Mania.UI
{
@@ -97,7 +96,7 @@ namespace osu.Game.Rulesets.Mania.UI
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)
{
@@ -110,8 +109,6 @@ namespace osu.Game.Rulesets.Mania.UI
}
}
protected override Vector2 PlayfieldArea => new Vector2(1, 0.8f);
protected override ReplayInputHandler CreateReplayInputHandler(Replay replay) => new ManiaFramedReplayInputHandler(replay);
}
}
@@ -7,7 +7,7 @@ using osu.Game.Rulesets.UI.Scrolling;
namespace osu.Game.Rulesets.Mania.UI
{
public class ManiaScrollingPlayfield : ScrollingPlayfield
public abstract class ManiaScrollingPlayfield : ScrollingPlayfield
{
private readonly IBindable<ScrollingDirection> direction = new Bindable<ScrollingDirection>();
+2 -2
View File
@@ -30,8 +30,7 @@ namespace osu.Game.Rulesets.Mania.UI
public IReadOnlyList<Column> Columns => columnFlow.Children;
private readonly FillFlowContainer<Column> columnFlow;
protected override Container<Drawable> Content => barLineContainer;
private readonly Container<Drawable> barLineContainer;
private readonly Container barLineContainer;
public Container<DrawableManiaJudgement> Judgements => judgements;
private readonly JudgementContainer<DrawableManiaJudgement> judgements;
@@ -105,6 +104,7 @@ namespace osu.Game.Rulesets.Mania.UI
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
RelativeSizeAxes = Axes.Y,
Child = HitObjectContainer
}
},
judgements = new JudgementContainer<DrawableManiaJudgement>
@@ -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.Blueprints.HitCircles;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables;
using osu.Game.Tests.Visual;
namespace osu.Game.Rulesets.Osu.Tests
{
public class TestCaseHitCirclePlacementBlueprint : PlacementBlueprintTestCase
{
protected override DrawableHitObject CreateHitObject(HitObject hitObject) => new DrawableHitCircle((HitCircle)hitObject);
protected override PlacementBlueprint CreateBlueprint() => new HitCirclePlacementBlueprint();
}
}
@@ -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.Blueprints.HitCircles;
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 TestCaseHitCircleSelectionBlueprint : SelectionBlueprintTestCase
{
private readonly DrawableHitCircle drawableObject;
public TestCaseHitCircleSelectionBlueprint()
{
var hitCircle = new HitCircle { Position = new Vector2(256, 192) };
hitCircle.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty { CircleSize = 2 });
Add(drawableObject = new DrawableHitCircle(hitCircle));
}
protected override SelectionBlueprint CreateBlueprint() => new HitCircleSelectionBlueprint(drawableObject);
}
}
+18 -18
View File
@@ -108,7 +108,7 @@ namespace osu.Game.Rulesets.Osu.Tests
{
StartTime = Time.Current + 1000,
Position = new Vector2(239, 176),
ControlPoints = new List<Vector2>
ControlPoints = new[]
{
Vector2.Zero,
new Vector2(154, 28),
@@ -116,7 +116,7 @@ namespace osu.Game.Rulesets.Osu.Tests
},
Distance = 700,
RepeatCount = repeats,
RepeatSamples = createEmptySamples(repeats),
NodeSamples = createEmptySamples(repeats),
StackHeight = 10
};
@@ -141,14 +141,14 @@ namespace osu.Game.Rulesets.Osu.Tests
{
StartTime = Time.Current + 1000,
Position = new Vector2(-(distance / 2), 0),
ControlPoints = new List<Vector2>
ControlPoints = new[]
{
Vector2.Zero,
new Vector2(distance, 0),
},
Distance = distance,
RepeatCount = repeats,
RepeatSamples = createEmptySamples(repeats),
NodeSamples = createEmptySamples(repeats),
StackHeight = stackHeight
};
@@ -161,7 +161,7 @@ namespace osu.Game.Rulesets.Osu.Tests
{
StartTime = Time.Current + 1000,
Position = new Vector2(-200, 0),
ControlPoints = new List<Vector2>
ControlPoints = new[]
{
Vector2.Zero,
new Vector2(200, 200),
@@ -169,7 +169,7 @@ namespace osu.Game.Rulesets.Osu.Tests
},
Distance = 600,
RepeatCount = repeats,
RepeatSamples = createEmptySamples(repeats)
NodeSamples = createEmptySamples(repeats)
};
addSlider(slider, 2, 3);
@@ -181,10 +181,10 @@ namespace osu.Game.Rulesets.Osu.Tests
{
var slider = new Slider
{
CurveType = CurveType.Linear,
PathType = PathType.Linear,
StartTime = Time.Current + 1000,
Position = new Vector2(-200, 0),
ControlPoints = new List<Vector2>
ControlPoints = new[]
{
Vector2.Zero,
new Vector2(150, 75),
@@ -195,7 +195,7 @@ namespace osu.Game.Rulesets.Osu.Tests
},
Distance = 793.4417,
RepeatCount = repeats,
RepeatSamples = createEmptySamples(repeats)
NodeSamples = createEmptySamples(repeats)
};
addSlider(slider, 2, 3);
@@ -207,10 +207,10 @@ namespace osu.Game.Rulesets.Osu.Tests
{
var slider = new Slider
{
CurveType = CurveType.Bezier,
PathType = PathType.Bezier,
StartTime = Time.Current + 1000,
Position = new Vector2(-200, 0),
ControlPoints = new List<Vector2>
ControlPoints = new[]
{
Vector2.Zero,
new Vector2(150, 75),
@@ -220,7 +220,7 @@ namespace osu.Game.Rulesets.Osu.Tests
},
Distance = 480,
RepeatCount = repeats,
RepeatSamples = createEmptySamples(repeats)
NodeSamples = createEmptySamples(repeats)
};
addSlider(slider, 2, 3);
@@ -232,10 +232,10 @@ namespace osu.Game.Rulesets.Osu.Tests
{
var slider = new Slider
{
CurveType = CurveType.Linear,
PathType = PathType.Linear,
StartTime = Time.Current + 1000,
Position = new Vector2(0, 0),
ControlPoints = new List<Vector2>
ControlPoints = new[]
{
Vector2.Zero,
new Vector2(-200, 0),
@@ -246,7 +246,7 @@ namespace osu.Game.Rulesets.Osu.Tests
},
Distance = 1000,
RepeatCount = repeats,
RepeatSamples = createEmptySamples(repeats)
NodeSamples = createEmptySamples(repeats)
};
addSlider(slider, 2, 3);
@@ -264,8 +264,8 @@ namespace osu.Game.Rulesets.Osu.Tests
{
StartTime = Time.Current + 1000,
Position = new Vector2(-100, 0),
CurveType = CurveType.Catmull,
ControlPoints = new List<Vector2>
PathType = PathType.Catmull,
ControlPoints = new[]
{
Vector2.Zero,
new Vector2(50, -50),
@@ -274,7 +274,7 @@ namespace osu.Game.Rulesets.Osu.Tests
},
Distance = 300,
RepeatCount = repeats,
RepeatSamples = repeatSamples
NodeSamples = repeatSamples
};
addSlider(slider, 3, 1);
@@ -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.Blueprints.Sliders;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables;
using osu.Game.Tests.Visual;
namespace osu.Game.Rulesets.Osu.Tests
{
public class TestCaseSliderPlacementBlueprint : PlacementBlueprintTestCase
{
protected override DrawableHitObject CreateHitObject(HitObject hitObject) => new DrawableSlider((Slider)hitObject);
protected override PlacementBlueprint CreateBlueprint() => new SliderPlacementBlueprint();
}
}
@@ -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.Blueprints.Sliders;
using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables;
using osu.Game.Tests.Visual;
using OpenTK;
namespace osu.Game.Rulesets.Osu.Tests
{
public class TestCaseSliderSelectionBlueprint : SelectionBlueprintTestCase
{
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(SliderSelectionBlueprint),
typeof(SliderCircleSelectionBlueprint),
typeof(SliderBodyPiece),
typeof(SliderCircle),
typeof(PathControlPointVisualiser),
typeof(PathControlPointPiece)
};
private readonly DrawableSlider drawableObject;
public TestCaseSliderSelectionBlueprint()
{
var slider = new Slider
{
Position = new Vector2(256, 192),
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 SelectionBlueprint CreateBlueprint() => new SliderSelectionBlueprint(drawableObject);
}
}
@@ -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.Blueprints.Spinners;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables;
using osu.Game.Tests.Visual;
namespace osu.Game.Rulesets.Osu.Tests
{
public class TestCaseSpinnerPlacementBlueprint : PlacementBlueprintTestCase
{
protected override DrawableHitObject CreateHitObject(HitObject hitObject) => new DrawableSpinner((Spinner)hitObject);
protected override PlacementBlueprint CreateBlueprint() => new SpinnerPlacementBlueprint();
}
}
@@ -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.Blueprints.Spinners;
using osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners.Components;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables;
using osu.Game.Tests.Visual;
using OpenTK;
namespace osu.Game.Rulesets.Osu.Tests
{
public class TestCaseSpinnerSelectionBlueprint : SelectionBlueprintTestCase
{
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(SpinnerSelectionBlueprint),
typeof(SpinnerPiece)
};
private readonly DrawableSpinner drawableSpinner;
public TestCaseSpinnerSelectionBlueprint()
{
var spinner = new Spinner
{
Position = new Vector2(256, 256),
StartTime = -1000,
EndTime = 2000
};
spinner.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty { CircleSize = 2 });
Add(new Container
{
RelativeSizeAxes = Axes.Both,
Size = new Vector2(0.5f),
Child = drawableSpinner = new DrawableSpinner(spinner)
});
}
protected override SelectionBlueprint CreateBlueprint() => new SpinnerSelectionBlueprint(drawableSpinner) { Size = new Vector2(0.5f) };
}
}
@@ -2,9 +2,10 @@
<Import Project="..\osu.TestProject.props" />
<ItemGroup Label="Package References">
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.8.0" />
<PackageReference Include="NUnit" Version="3.10.1" />
<PackageReference Include="NUnit3TestAdapter" Version="3.10.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.9.0" />
<PackageReference Include="NUnit" Version="3.11.0" />
<PackageReference Include="NUnit3TestAdapter" Version="3.11.0" />
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
</ItemGroup>
<PropertyGroup Label="Project">
<OutputType>WinExe</OutputType>
@@ -36,14 +36,17 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
StartTime = original.StartTime,
Samples = original.Samples,
ControlPoints = curveData.ControlPoints,
CurveType = curveData.CurveType,
PathType = curveData.PathType,
Distance = curveData.Distance,
RepeatSamples = curveData.RepeatSamples,
NodeSamples = curveData.NodeSamples,
RepeatCount = curveData.RepeatCount,
Position = positionData?.Position ?? Vector2.Zero,
NewCombo = comboData?.NewCombo ?? false,
ComboOffset = comboData?.ComboOffset ?? 0,
LegacyLastTickOffset = legacyOffset?.LegacyLastTickOffset
LegacyLastTickOffset = legacyOffset?.LegacyLastTickOffset,
// prior to v8, speed multipliers don't adjust for how many ticks are generated over the same distance.
// this results in more (or less) ticks being generated in <v8 maps for the same time duration.
TickDistanceMultiplier = beatmap.BeatmapInfo.BeatmapVersion < 8 ? 1f / beatmap.ControlPointInfo.DifficultyPointAt(original.StartTime).SpeedMultiplier : 1
};
}
else if (endTimeData != null)
@@ -10,6 +10,8 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
{
public class OsuBeatmapProcessor : BeatmapProcessor
{
private const int stack_distance = 3;
public OsuBeatmapProcessor(IBeatmap beatmap)
: base(beatmap)
{
@@ -18,17 +20,21 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
public override void PostProcess()
{
base.PostProcess();
applyStacking((Beatmap<OsuHitObject>)Beatmap);
var osuBeatmap = (Beatmap<OsuHitObject>)Beatmap;
// Reset stacking
foreach (var h in osuBeatmap.HitObjects)
h.StackHeight = 0;
if (Beatmap.BeatmapInfo.BeatmapVersion >= 6)
applyStacking(osuBeatmap);
else
applyStackingOld(osuBeatmap);
}
private void applyStacking(Beatmap<OsuHitObject> beatmap)
{
const int stack_distance = 3;
// Reset stacking
for (int i = 0; i <= beatmap.HitObjects.Count - 1; i++)
beatmap.HitObjects[i].StackHeight = 0;
// Extend the end index to include objects they are stacked on
int extendedEndIndex = beatmap.HitObjects.Count - 1;
for (int i = beatmap.HitObjects.Count - 1; i >= 0; i--)
@@ -167,5 +173,40 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
}
}
}
private void applyStackingOld(Beatmap<OsuHitObject> beatmap)
{
for (int i = 0; i < beatmap.HitObjects.Count; i++)
{
OsuHitObject currHitObject = beatmap.HitObjects[i];
if (currHitObject.StackHeight != 0 && !(currHitObject is Slider))
continue;
double startTime = (currHitObject as IHasEndTime)?.EndTime ?? currHitObject.StartTime;
int sliderStack = 0;
for (int j = i + 1; j < beatmap.HitObjects.Count; j++)
{
double stackThreshold = beatmap.HitObjects[i].TimePreempt * beatmap.BeatmapInfo.StackLeniency;
if (beatmap.HitObjects[j].StartTime - stackThreshold > startTime)
break;
if (Vector2Extensions.Distance(beatmap.HitObjects[j].Position, currHitObject.Position) < stack_distance)
{
currHitObject.StackHeight++;
startTime = (beatmap.HitObjects[j] as IHasEndTime)?.EndTime ?? beatmap.HitObjects[i].StartTime;
}
else if (Vector2Extensions.Distance(beatmap.HitObjects[j].Position, currHitObject.EndPosition) < stack_distance)
{
//Case for sliders - bump notes down and right, rather than up and left.
sliderStack++;
beatmap.HitObjects[j].StackHeight -= sliderStack;
startTime = (beatmap.HitObjects[j] as IHasEndTime)?.EndTime ?? beatmap.HitObjects[i].StartTime;
}
}
}
}
}
}
@@ -38,7 +38,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
double sectionLength = section_length * timeRate;
// The first object doesn't generate a strain, so we begin with an incremented section end
double currentSectionEnd = 2 * sectionLength;
double currentSectionEnd = Math.Ceiling(beatmap.HitObjects.First().StartTime / sectionLength) * sectionLength;
foreach (OsuDifficultyHitObject h in difficultyBeatmap)
{
@@ -65,7 +65,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
double hitWindowGreat = (int)(beatmap.HitObjects.First().HitWindows.Great / 2) / 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)
maxCombo += beatmap.HitObjects.OfType<Slider>().Sum(s => s.NestedHitObjects.Count - 1);
@@ -34,7 +34,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
{
countHitCircles = Beatmap.HitObjects.Count(h => h is HitCircle);
beatmapMaxCombo = Beatmap.HitObjects.Count();
beatmapMaxCombo = Beatmap.HitObjects.Count;
// Add the ticks + tail of the slider. 1 is subtracted because the "headcircle" would be counted twice (once for the slider itself in the line above)
beatmapMaxCombo += Beatmap.HitObjects.OfType<Slider>().Sum(s => s.NestedHitObjects.Count - 1);
}
@@ -3,6 +3,7 @@
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using osu.Game.Rulesets.Osu.Objects;
namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
@@ -23,8 +24,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
{
// Sort OsuHitObjects by StartTime - they are not correctly ordered in some cases.
// This should probably happen before the objects reach the difficulty calculator.
objects.Sort((a, b) => a.StartTime.CompareTo(b.StartTime));
difficultyObjects = createDifficultyObjectEnumerator(objects, timeRate);
difficultyObjects = createDifficultyObjectEnumerator(objects.OrderBy(h => h.StartTime).ToList(), timeRate);
}
/// <summary>
@@ -21,15 +21,25 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
public OsuHitObject BaseObject { get; }
/// <summary>
/// Normalized distance from the <see cref="OsuHitObject.StackedPosition"/> of the previous <see cref="OsuDifficultyHitObject"/>.
/// Normalized distance from the end position of the previous <see cref="OsuDifficultyHitObject"/> to the start position of this <see cref="OsuDifficultyHitObject"/>.
/// </summary>
public double Distance { get; private set; }
public double JumpDistance { get; private set; }
/// <summary>
/// Normalized distance between the start and end position of the previous <see cref="OsuDifficultyHitObject"/>.
/// </summary>
public double TravelDistance { get; private set; }
/// <summary>
/// Milliseconds elapsed since the StartTime of the previous <see cref="OsuDifficultyHitObject"/>.
/// </summary>
public double DeltaTime { get; private set; }
/// <summary>
/// Milliseconds elapsed since the start time of the previous <see cref="OsuDifficultyHitObject"/>, with a minimum of 50ms.
/// </summary>
public double StrainTime { get; private set; }
private readonly OsuHitObject lastObject;
private readonly double timeRate;
@@ -51,31 +61,35 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
private void setDistances()
{
// We will scale distances by this factor, so we can assume a uniform CircleSize among beatmaps.
double scalingFactor = normalized_radius / BaseObject.Radius;
float scalingFactor = normalized_radius / (float)BaseObject.Radius;
if (BaseObject.Radius < 30)
{
double smallCircleBonus = Math.Min(30 - BaseObject.Radius, 5) / 50;
float smallCircleBonus = Math.Min(30 - (float)BaseObject.Radius, 5) / 50;
scalingFactor *= 1 + smallCircleBonus;
}
Vector2 lastCursorPosition = lastObject.StackedPosition;
float lastTravelDistance = 0;
var lastSlider = lastObject as Slider;
if (lastSlider != null)
{
computeSliderCursorPosition(lastSlider);
lastCursorPosition = lastSlider.LazyEndPosition ?? lastCursorPosition;
lastTravelDistance = lastSlider.LazyTravelDistance;
TravelDistance = lastSlider.LazyTravelDistance * scalingFactor;
}
Distance = (lastTravelDistance + (BaseObject.StackedPosition - lastCursorPosition).Length) * scalingFactor;
// Don't need to jump to reach spinners
if (!(BaseObject is Spinner))
JumpDistance = (BaseObject.StackedPosition * scalingFactor - lastCursorPosition * scalingFactor).Length;
}
private void setTimingValues()
{
// Every timing inverval is hard capped at the equivalent of 375 BPM streaming speed as a safety measure.
DeltaTime = Math.Max(50, (BaseObject.StartTime - lastObject.StartTime) / timeRate);
DeltaTime = (BaseObject.StartTime - lastObject.StartTime) / timeRate;
// Every strain interval is hard capped at the equivalent of 375 BPM streaming speed as a safety measure
StrainTime = Math.Max(50, DeltaTime);
}
private void computeSliderCursorPosition(Slider slider)
@@ -87,8 +101,14 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
float approxFollowCircleRadius = (float)(slider.Radius * 3);
var computeVertex = new Action<double>(t =>
{
double progress = ((int)t - (int)slider.StartTime) / (float)(int)slider.SpanDuration;
if (progress % 2 > 1)
progress = 1 - progress % 1;
else
progress = progress % 1;
// ReSharper disable once PossibleInvalidOperationException (bugged in current r# version)
var diff = slider.StackedPositionAt(t) - slider.LazyEndPosition.Value;
var diff = slider.StackedPosition + slider.Path.PositionAt(progress) - slider.LazyEndPosition.Value;
float dist = diff.Length;
if (dist > approxFollowCircleRadius)
@@ -14,6 +14,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
protected override double SkillMultiplier => 26.25;
protected override double StrainDecayBase => 0.15;
protected override double StrainValueOf(OsuDifficultyHitObject current) => Math.Pow(current.Distance, 0.99) / current.DeltaTime;
protected override double StrainValueOf(OsuDifficultyHitObject current)
=> (Math.Pow(current.TravelDistance, 0.99) + Math.Pow(current.JumpDistance, 0.99)) / current.StrainTime;
}
}
@@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
protected override double StrainValueOf(OsuDifficultyHitObject current)
{
double distance = current.Distance;
double distance = current.TravelDistance + current.JumpDistance;
double speedValue;
if (distance > single_spacing_threshold)
@@ -33,7 +33,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
else
speedValue = 0.95;
return speedValue / current.DeltaTime;
return speedValue / current.StrainTime;
}
}
}
@@ -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.Blueprints.HitCircles.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;
}
}
@@ -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.Blueprints.HitCircles.Components;
using osu.Game.Rulesets.Osu.Objects;
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles
{
public class HitCirclePlacementBlueprint : PlacementBlueprint
{
public new HitCircle HitObject => (HitCircle)base.HitObject;
public HitCirclePlacementBlueprint()
: base(new HitCircle())
{
InternalChild = new HitCirclePiece(HitObject);
}
protected override void LoadComplete()
{
base.LoadComplete();
// Fixes a 1-frame position discrpancy due to the first mouse move event happening in the next frame
HitObject.Position = GetContainingInputManager().CurrentState.Mouse.Position;
}
protected override bool OnClick(ClickEvent e)
{
HitObject.StartTime = EditorClock.CurrentTime;
EndPlacement();
return true;
}
protected override bool OnMouseMove(MouseMoveEvent e)
{
HitObject.Position = e.MousePosition;
return true;
}
}
}
@@ -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.Blueprints.HitCircles.Components;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables;
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles
{
public class HitCircleSelectionBlueprint : SelectionBlueprint
{
public HitCircleSelectionBlueprint(DrawableHitCircle hitCircle)
: base(hitCircle)
{
InternalChild = new HitCirclePiece((HitCircle)hitCircle.HitObject);
}
}
}
@@ -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.Blueprints.Sliders.Components
{
public class PathControlPointPiece : CompositeDrawable
{
private readonly Slider slider;
private readonly int index;
private readonly Path path;
private readonly CircularContainer marker;
[Resolved]
private OsuColour colours { get; set; }
public PathControlPointPiece(Slider slider, int index)
{
this.slider = slider;
this.index = index;
Origin = Anchor.Centre;
AutoSizeAxes = Axes.Both;
InternalChildren = new Drawable[]
{
path = new SmoothPath
{
Anchor = Anchor.Centre,
PathWidth = 1
},
marker = new CircularContainer
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = new Vector2(10),
Masking = true,
Child = new Box { RelativeSizeAxes = Axes.Both }
}
};
}
protected override void Update()
{
base.Update();
Position = slider.StackedPosition + slider.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];
}
}
@@ -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.Blueprints.Sliders.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]);
}
}
}
@@ -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.Blueprints.Sliders.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;
}
}
}
@@ -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.Blueprints.HitCircles.Components;
using osu.Game.Rulesets.Osu.Objects;
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.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;
}
}
}
}
@@ -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.Blueprints.Sliders.Components;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables;
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
{
public class SliderCircleSelectionBlueprint : SelectionBlueprint
{
public SliderCircleSelectionBlueprint(DrawableOsuHitObject hitObject, Slider slider, SliderPosition position)
: base(hitObject)
{
InternalChild = new SliderCirclePiece(slider, position);
Select();
}
// Todo: This is temporary, since the slider circle masks don't do anything special yet. In the future they will handle input.
public override bool HandlePositionalInput => false;
}
}
@@ -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.Blueprints.Sliders.Components;
using OpenTK;
using OpenTK.Input;
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
{
public class SliderPlacementBlueprint : PlacementBlueprint
{
public new Objects.Slider HitObject => (Objects.Slider)base.HitObject;
private readonly List<Segment> segments = new List<Segment>();
private Vector2 cursor;
private PlacementState state;
public SliderPlacementBlueprint()
: base(new Objects.Slider())
{
RelativeSizeAxes = Axes.Both;
segments.Add(new Segment(Vector2.Zero));
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
InternalChildren = new Drawable[]
{
new SliderBodyPiece(HitObject),
new SliderCirclePiece(HitObject, SliderPosition.Start),
new SliderCirclePiece(HitObject, SliderPosition.End),
new PathControlPointVisualiser(HitObject),
};
setState(PlacementState.Initial);
}
protected override bool OnMouseMove(MouseMoveEvent e)
{
switch (state)
{
case PlacementState.Initial:
HitObject.Position = e.MousePosition;
return true;
case PlacementState.Body:
cursor = e.MousePosition - HitObject.Position;
return true;
}
return false;
}
protected override bool OnClick(ClickEvent e)
{
switch (state)
{
case PlacementState.Initial:
beginCurve();
break;
case PlacementState.Body:
switch (e.Button)
{
case MouseButton.Left:
segments.Last().ControlPoints.Add(cursor);
break;
}
break;
}
return true;
}
protected override bool OnMouseUp(MouseUpEvent e)
{
if (state == PlacementState.Body && e.Button == MouseButton.Right)
endCurve();
return base.OnMouseUp(e);
}
protected override bool OnDoubleClick(DoubleClickEvent e)
{
segments.Add(new Segment(segments[segments.Count - 1].ControlPoints.Last()));
return true;
}
private void beginCurve()
{
BeginPlacement();
HitObject.StartTime = EditorClock.CurrentTime;
setState(PlacementState.Body);
}
private void endCurve()
{
updateSlider();
EndPlacement();
}
protected override void Update()
{
base.Update();
updateSlider();
}
private void updateSlider()
{
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]);
}
}
}
}
@@ -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.Blueprints.Sliders
{
public enum SliderPosition
{
Start,
End
}
}
@@ -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.Blueprints.Sliders.Components;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables;
using OpenTK;
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
{
public class SliderSelectionBlueprint : SelectionBlueprint
{
private readonly SliderCircleSelectionBlueprint headBlueprint;
public SliderSelectionBlueprint(DrawableSlider slider)
: base(slider)
{
var sliderObject = (Slider)slider.HitObject;
InternalChildren = new Drawable[]
{
new SliderBodyPiece(sliderObject),
headBlueprint = new SliderCircleSelectionBlueprint(slider.HeadCircle, sliderObject, SliderPosition.Start),
new SliderCircleSelectionBlueprint(slider.TailCircle, sliderObject, SliderPosition.End),
new PathControlPointVisualiser(sliderObject),
};
}
public override Vector2 SelectionPoint => headBlueprint.SelectionPoint;
}
}
@@ -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.Blueprints.Spinners.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);
}
}
@@ -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.Blueprints.Spinners.Components;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.UI;
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners
{
public class SpinnerPlacementBlueprint : PlacementBlueprint
{
public new Spinner HitObject => (Spinner)base.HitObject;
private readonly SpinnerPiece piece;
private bool isPlacingEnd;
public SpinnerPlacementBlueprint()
: base(new Spinner { Position = OsuPlayfield.BASE_SIZE / 2 })
{
InternalChild = piece = new SpinnerPiece(HitObject) { Alpha = 0.5f };
}
protected override bool OnClick(ClickEvent e)
{
if (isPlacingEnd)
{
HitObject.EndTime = EditorClock.CurrentTime;
EndPlacement();
}
else
{
HitObject.StartTime = EditorClock.CurrentTime;
isPlacingEnd = true;
piece.FadeTo(1f, 150, Easing.OutQuint);
}
return true;
}
}
}
@@ -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.Blueprints.Spinners.Components;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables;
using OpenTK;
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners
{
public class SpinnerSelectionBlueprint : SelectionBlueprint
{
private readonly SpinnerPiece piece;
public SpinnerSelectionBlueprint(DrawableSpinner spinner)
: base(spinner)
{
InternalChild = piece = new SpinnerPiece((Spinner)spinner.HitObject);
}
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => piece.ReceivePositionalInputAt(screenSpacePos);
}
}
@@ -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.Blueprints.HitCircles;
using osu.Game.Rulesets.Osu.Objects;
namespace osu.Game.Rulesets.Osu.Edit
{
public class HitCircleCompositionTool : HitObjectCompositionTool
{
public HitCircleCompositionTool()
: base(nameof(HitCircle))
{
}
public override PlacementBlueprint CreatePlacementBlueprint() => new HitCirclePlacementBlueprint();
}
}
@@ -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;
}
}
}
@@ -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 HandleMouseInput => false;
}
}
@@ -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 ReceiveMouseInputAt(Vector2 screenSpacePos) => body.ReceiveMouseInputAt(screenSpacePos);
public override Vector2 SelectionPoint => ToScreenSpace(OriginPosition);
public override Quad SelectionQuad => body.PathDrawQuad;
}
}
@@ -4,6 +4,7 @@
using osu.Framework.Graphics.Cursor;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Osu.UI;
using osu.Game.Rulesets.UI;
using OpenTK;
namespace osu.Game.Rulesets.Osu.Edit
@@ -15,8 +16,8 @@ namespace osu.Game.Rulesets.Osu.Edit
{
}
protected override Vector2 PlayfieldArea => Vector2.One;
protected override CursorContainer CreateCursor() => null;
protected override Playfield CreatePlayfield() => new OsuPlayfield { Size = Vector2.One };
}
}
@@ -3,11 +3,14 @@
using System.Collections.Generic;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Edit.Tools;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Osu.Edit.Layers.Selection.Overlays;
using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles;
using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders;
using osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables;
using osu.Game.Rulesets.Osu.UI;
@@ -15,35 +18,38 @@ using osu.Game.Rulesets.UI;
namespace osu.Game.Rulesets.Osu.Edit
{
public class OsuHitObjectComposer : HitObjectComposer
public class OsuHitObjectComposer : HitObjectComposer<OsuHitObject>
{
public OsuHitObjectComposer(Ruleset 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 HitObjectCompositionTool<Slider>(),
new HitObjectCompositionTool<Spinner>()
new HitCircleCompositionTool(),
new SliderCompositionTool(),
new SpinnerCompositionTool()
};
protected override ScalableContainer CreateLayerContainer() => new ScalableContainer(OsuPlayfield.BASE_SIZE.X) { RelativeSizeAxes = Axes.Both };
protected override Container CreateLayerContainer() => new PlayfieldAdjustmentContainer { RelativeSizeAxes = Axes.Both };
public override HitObjectMask CreateMaskFor(DrawableHitObject hitObject)
public override SelectionBlueprint CreateBlueprintFor(DrawableHitObject hitObject)
{
switch (hitObject)
{
case DrawableHitCircle circle:
return new HitCircleMask(circle);
return new HitCircleSelectionBlueprint(circle);
case DrawableSlider slider:
return new SliderMask(slider);
return new SliderSelectionBlueprint(slider);
case DrawableSpinner spinner:
return new SpinnerSelectionBlueprint(spinner);
}
return base.CreateMaskFor(hitObject);
return base.CreateBlueprintFor(hitObject);
}
}
}
@@ -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.Blueprints.Sliders;
using osu.Game.Rulesets.Osu.Objects;
namespace osu.Game.Rulesets.Osu.Edit
{
public class SliderCompositionTool : HitObjectCompositionTool
{
public SliderCompositionTool()
: base(nameof(Slider))
{
}
public override PlacementBlueprint CreatePlacementBlueprint() => new SliderPlacementBlueprint();
}
}
@@ -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.Blueprints.Spinners;
using osu.Game.Rulesets.Osu.Objects;
namespace osu.Game.Rulesets.Osu.Edit
{
public class SpinnerCompositionTool : HitObjectCompositionTool
{
public SpinnerCompositionTool()
: base(nameof(Spinner))
{
}
public override PlacementBlueprint CreatePlacementBlueprint() => new SpinnerPlacementBlueprint();
}
}
+4 -4
View File
@@ -1,7 +1,6 @@
// 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 System.Linq;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Game.Rulesets.Mods;
@@ -33,11 +32,12 @@ namespace osu.Game.Rulesets.Osu.Mods
slider.NestedHitObjects.OfType<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));
var newControlPoints = new List<Vector2>();
slider.ControlPoints.ForEach(c => newControlPoints.Add(new Vector2(c.X, -c.Y)));
var newControlPoints = new Vector2[slider.ControlPoints.Length];
for (int i = 0; i < slider.ControlPoints.Length; i++)
newControlPoints[i] = new Vector2(slider.ControlPoints[i].X, -slider.ControlPoints[i].Y);
slider.ControlPoints = newControlPoints;
slider.Curve?.Calculate(); // Recalculate the slider curve
slider.Path?.Calculate(); // Recalculate the slider curve
}
}
}
+1 -2
View File
@@ -4,7 +4,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Input.States;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Osu.Objects;
@@ -77,7 +76,7 @@ namespace osu.Game.Rulesets.Osu.Mods
wasLeft = !wasLeft;
}
osuInputManager.HandleCustomInput(new InputState(), state);
state.Apply(osuInputManager.CurrentState, osuInputManager);
}
public void ApplyToRulesetContainer(RulesetContainer<OsuHitObject> rulesetContainer)
@@ -0,0 +1,53 @@
// 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.Game.Graphics;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Osu.Objects;
using OpenTK;
namespace osu.Game.Rulesets.Osu.Mods
{
internal class OsuModTransform : Mod, IApplicableToDrawableHitObjects
{
public override string Name => "Transform";
public override string ShortenedName => "TR";
public override FontAwesome Icon => FontAwesome.fa_arrows;
public override ModType Type => ModType.Fun;
public override string Description => "Everything rotates. EVERYTHING.";
public override double ScoreMultiplier => 1;
public override Type[] IncompatibleMods => new[] { typeof(OsuModWiggle) };
private float theta;
public void ApplyToDrawableHitObjects(IEnumerable<DrawableHitObject> drawables)
{
foreach (var drawable in drawables)
{
var hitObject = (OsuHitObject) drawable.HitObject;
float appearDistance = (float)(hitObject.TimePreempt - hitObject.TimeFadeIn) / 2;
Vector2 originalPosition = drawable.Position;
Vector2 appearOffset = new Vector2((float)Math.Cos(theta), (float)Math.Sin(theta)) * appearDistance;
//the - 1 and + 1 prevents the hit objects to appear in the wrong position.
double appearTime = hitObject.StartTime - hitObject.TimePreempt - 1;
double moveDuration = hitObject.TimePreempt + 1;
using (drawable.BeginAbsoluteSequence(appearTime, true))
{
drawable
.MoveToOffset(appearOffset)
.MoveTo(originalPosition, moveDuration, Easing.InOutSine);
}
theta += (float) hitObject.TimeFadeIn / 1000;
}
}
}
}
@@ -0,0 +1,72 @@
// 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.Game.Graphics;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Osu.Objects;
using OpenTK;
namespace osu.Game.Rulesets.Osu.Mods
{
internal class OsuModWiggle : Mod, IApplicableToDrawableHitObjects
{
public override string Name => "Wiggle";
public override string ShortenedName => "WG";
public override FontAwesome Icon => FontAwesome.fa_certificate;
public override ModType Type => ModType.Fun;
public override string Description => "They just won't stay still...";
public override double ScoreMultiplier => 1;
public override Type[] IncompatibleMods => new[] { typeof(OsuModTransform) };
private const int wiggle_duration = 90; // (ms) Higher = fewer wiggles
private const int wiggle_strength = 10; // Higher = stronger wiggles
public void ApplyToDrawableHitObjects(IEnumerable<DrawableHitObject> drawables)
{
foreach (var drawable in drawables)
drawable.ApplyCustomUpdateState += drawableOnApplyCustomUpdateState;
}
private void drawableOnApplyCustomUpdateState(DrawableHitObject drawable, ArmedState state)
{
var osuObject = (OsuHitObject)drawable.HitObject;
Vector2 origin = drawable.Position;
// Wiggle the repeat points with the slider instead of independently.
// Also fixes an issue with repeat points being positioned incorrectly.
if (osuObject is RepeatPoint)
return;
Random objRand = new Random((int)osuObject.StartTime);
// Wiggle all objects during TimePreempt
int amountWiggles = (int)osuObject.TimePreempt / wiggle_duration;
void wiggle()
{
float nextAngle = (float)(objRand.NextDouble() * 2 * Math.PI);
float nextDist = (float)(objRand.NextDouble() * wiggle_strength);
drawable.MoveTo(new Vector2((float)(nextDist * Math.Cos(nextAngle) + origin.X), (float)(nextDist * Math.Sin(nextAngle) + origin.Y)), wiggle_duration);
}
for (int i = 0; i < amountWiggles; i++)
using (drawable.BeginAbsoluteSequence(osuObject.StartTime - osuObject.TimePreempt + i * wiggle_duration, true))
wiggle();
// Keep wiggling sliders and spinners for their duration
if (!(osuObject is IHasEndTime endTime))
return;
amountWiggles = (int)(endTime.Duration / wiggle_duration);
for (int i = 0; i < amountWiggles; i++)
using (drawable.BeginAbsoluteSequence(osuObject.StartTime + i * wiggle_duration, true))
wiggle();
}
}
}
@@ -61,6 +61,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
Size = circle.DrawSize;
HitObject.PositionChanged += _ => Position = HitObject.StackedPosition;
HitObject.StackHeightChanged += _ => Position = HitObject.StackedPosition;
HitObject.ScaleChanged += s => Scale = new Vector2(s);
}
public override Color4 AccentColour
@@ -16,7 +16,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
{
public class DrawableOsuHitObject : DrawableHitObject<OsuHitObject>
{
public override bool IsPresent => base.IsPresent || State.Value == ArmedState.Idle && Time.Current >= HitObject.StartTime - HitObject.TimePreempt;
public override bool IsPresent => base.IsPresent || State.Value == ArmedState.Idle && Clock?.CurrentTime >= HitObject.StartTime - HitObject.TimePreempt;
private readonly ShakeContainer shakeContainer;
@@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
public readonly DrawableHitCircle HeadCircle;
public readonly DrawableSliderTail TailCircle;
public readonly SliderBody Body;
public readonly SnakingSliderBody Body;
public readonly SliderBall Ball;
public DrawableSlider(Slider s)
@@ -38,7 +38,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
InternalChildren = new Drawable[]
{
Body = new SliderBody(s)
Body = new SnakingSliderBody(s)
{
PathWidth = s.Scale * 64,
},
@@ -85,6 +85,13 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
}
HitObject.PositionChanged += _ => Position = HitObject.StackedPosition;
HitObject.ScaleChanged += _ =>
{
Body.PathWidth = HitObject.Scale * 64;
Ball.Scale = new Vector2(HitObject.Scale);
};
slider.ControlPointsChanged += _ => Body.Refresh();
}
public override Color4 AccentColour
@@ -119,7 +126,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
double completionProgress = MathHelper.Clamp((Time.Current - slider.StartTime) / slider.Duration, 0, 1);
foreach (var c in components.OfType<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;
@@ -184,6 +191,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
public Drawable ProxiedLayer => HeadCircle.ApproachCircle;
public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => Body.ReceiveMouseInputAt(screenSpacePos);
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => Body.ReceivePositionalInputAt(screenSpacePos);
}
}
@@ -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;
}
}
@@ -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;
}
}
@@ -112,6 +112,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
Alpha = 0
}
};
s.PositionChanged += _ => Position = s.Position;
}
public float Progress => MathHelper.Clamp(Disc.RotationAbsolute / 360 / Spinner.SpinsRequired, 0, 1);
@@ -167,7 +169,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
protected override void Update()
{
Disc.Tracking = OsuActionInputManager.PressedActions.Any(x => x == OsuAction.LeftButton || x == OsuAction.RightButton);
Disc.Tracking = OsuActionInputManager?.PressedActions.Any(x => x == OsuAction.LeftButton || x == OsuAction.RightButton) ?? false;
if (!spmCounter.IsPresent && Disc.Tracking)
spmCounter.FadeIn(HitObject.TimeFadeIn);
@@ -12,6 +12,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
{
public class CirclePiece : Container, IKeyBindingHandler<OsuAction>
{
// IsHovered is used
public override bool HandlePositionalInput => true;
public Func<bool> Hit;
public CirclePiece()
@@ -1,7 +1,6 @@
// 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.Threading.Tasks;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
@@ -13,7 +12,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
public class DefaultCirclePiece : Container
{
[BackgroundDependencyLoader]
private async Task load(TextureStore textures)
private void load(TextureStore textures)
{
RelativeSizeAxes = Axes.Both;
Children = new Drawable[]
@@ -22,7 +21,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Texture = await textures.GetAsync(@"Play/osu/disc"),
Texture = textures.Get(@"Play/osu/disc"),
},
new TrianglesPiece
{
@@ -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;
}
}
}
@@ -4,7 +4,6 @@
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Game.Graphics.Sprites;
using OpenTK.Graphics;
using osu.Framework.Graphics.Shapes;
@@ -14,7 +13,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
{
public class NumberPiece : Container
{
private readonly SpriteText number;
private readonly SkinnableSpriteText number;
public string Text
{
@@ -41,15 +40,14 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
},
Child = new Box()
}, s => s.GetTexture("Play/osu/hitcircle") == null),
number = new OsuSpriteText
number = new SkinnableSpriteText("Play/osu/number-text", _ => new OsuSpriteText
{
Text = @"1",
Font = @"Venera",
UseFullGlyphHeight = false,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
TextSize = 40,
Alpha = 1
}, restrictSize: false)
{
Text = @"1"
}
};
}
@@ -5,11 +5,11 @@ using System.Linq;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Input.EventArgs;
using osu.Framework.Input.States;
using osu.Framework.Input.Events;
using osu.Game.Rulesets.Objects.Types;
using OpenTK.Graphics;
using osu.Game.Skinning;
using OpenTK;
namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
{
@@ -102,24 +102,24 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
};
}
private InputState lastState;
private Vector2? lastScreenSpaceMousePosition;
protected override bool OnMouseDown(InputState state, MouseDownEventArgs args)
protected override bool OnMouseDown(MouseDownEvent e)
{
lastState = state;
return base.OnMouseDown(state, args);
lastScreenSpaceMousePosition = e.ScreenSpaceMousePosition;
return base.OnMouseDown(e);
}
protected override bool OnMouseUp(InputState state, MouseUpEventArgs args)
protected override bool OnMouseUp(MouseUpEvent e)
{
lastState = state;
return base.OnMouseUp(state, args);
lastScreenSpaceMousePosition = e.ScreenSpaceMousePosition;
return base.OnMouseUp(e);
}
protected override bool OnMouseMove(InputState state)
protected override bool OnMouseMove(MouseMoveEvent e)
{
lastState = state;
return base.OnMouseMove(state);
lastScreenSpaceMousePosition = e.ScreenSpaceMousePosition;
return base.OnMouseMove(e);
}
public override void ClearTransformsAfter(double time, bool propagateChildren = false, string targetMember = null)
@@ -153,10 +153,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
if (Time.Current < slider.EndTime)
{
// Make sure to use the base version of ReceiveMouseInputAt so that we correctly check the position.
// Make sure to use the base version of ReceivePositionalInputAt so that we correctly check the position.
Tracking = canCurrentlyTrack
&& lastState != null
&& ReceiveMouseInputAt(lastState.Mouse.NativeState.Position)
&& lastScreenSpaceMousePosition.HasValue
&& ReceivePositionalInputAt(lastScreenSpaceMousePosition.Value)
&& (drawableSlider?.OsuActionInputManager?.PressedActions.Any(x => x == OsuAction.LeftButton || x == OsuAction.RightButton) ?? false);
}
}
@@ -1,233 +1,139 @@
// 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
using System;
using System.Collections.Generic;
using osu.Framework.Allocation;
using osu.Framework.Configuration;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.OpenGL.Textures;
using osu.Framework.Graphics.Lines;
using osu.Framework.Graphics.Textures;
using OpenTK;
using OpenTK.Graphics.ES30;
using OpenTK.Graphics;
using osu.Framework.Graphics.Primitives;
using osu.Game.Rulesets.Objects.Types;
using OpenTK;
using OpenTK.Graphics;
using OpenTK.Graphics.ES30;
namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
{
public class SliderBody : Container, ISliderProgress
public abstract class SliderBody : CompositeDrawable
{
private readonly Path path;
private readonly SliderPath path;
protected Path Path => path;
private readonly BufferedContainer container;
public float PathWidth
{
get { return path.PathWidth; }
set { path.PathWidth = value; }
get => path.PathWidth;
set => path.PathWidth = value;
}
/// <summary>
/// Offset in absolute coordinates from the start of the curve.
/// </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; }
private Color4 accentColour = Color4.White;
/// <summary>
/// Used to colour the path.
/// </summary>
public Color4 AccentColour
{
get { return accentColour; }
get => path.AccentColour;
set
{
if (accentColour == value)
if (path.AccentColour == value)
return;
accentColour = value;
if (LoadState >= LoadState.Ready)
reloadTexture();
}
}
private Color4 borderColour = Color4.White;
/// <summary>
/// Used to colour the path border.
/// </summary>
public new Color4 BorderColour
{
get { return borderColour; }
set
{
if (borderColour == value)
return;
borderColour = value;
if (LoadState >= LoadState.Ready)
reloadTexture();
}
}
public Quad PathDrawQuad => container.ScreenSpaceDrawQuad;
private int textureWidth => (int)PathWidth * 2;
private Vector2 topLeftOffset;
private readonly Slider slider;
public SliderBody(Slider s)
{
slider = s;
Children = new Drawable[]
{
container = new BufferedContainer
{
RelativeSizeAxes = Axes.Both,
CacheDrawnFrameBuffer = true,
Children = new Drawable[]
{
path = new Path
{
Blending = BlendingMode.None,
},
}
},
};
container.Attach(RenderbufferInternalFormat.DepthComponent16);
}
public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => path.ReceiveMouseInputAt(screenSpacePos);
public void SetRange(double p0, double p1)
{
if (p0 > p1)
MathHelper.Swap(ref p0, ref p1);
if (updateSnaking(p0, p1))
{
// The path is generated such that its size encloses it. This change of size causes the path
// to move around while snaking, so we need to offset it to make sure it maintains the
// same position as when it is fully snaked.
var newTopLeftOffset = path.PositionInBoundingBox(Vector2.Zero);
path.Position = topLeftOffset - newTopLeftOffset;
path.AccentColour = value;
container.ForceRedraw();
}
}
[BackgroundDependencyLoader]
private void load()
/// <summary>
/// Used to colour the path border.
/// </summary>
public new Color4 BorderColour
{
reloadTexture();
computeSize();
get => path.BorderColour;
set
{
if (path.BorderColour == value)
return;
path.BorderColour = value;
container.ForceRedraw();
}
}
private void reloadTexture()
public Quad PathDrawQuad => container.ScreenSpaceDrawQuad;
protected SliderBody()
{
var texture = new Texture(textureWidth, 1);
//initialise background
var raw = new RawTexture(textureWidth, 1);
var bytes = raw.Data;
const float aa_portion = 0.02f;
const float border_portion = 0.128f;
const float gradient_portion = 1 - border_portion;
const float opacity_at_centre = 0.3f;
const float opacity_at_edge = 0.8f;
for (int i = 0; i < textureWidth; i++)
InternalChild = container = new BufferedContainer
{
float progress = (float)i / (textureWidth - 1);
RelativeSizeAxes = Axes.Both,
CacheDrawnFrameBuffer = true,
Child = path = new SliderPath { Blending = BlendingMode.None }
};
if (progress <= border_portion)
{
bytes[i * 4] = (byte)(BorderColour.R * 255);
bytes[i * 4 + 1] = (byte)(BorderColour.G * 255);
bytes[i * 4 + 2] = (byte)(BorderColour.B * 255);
bytes[i * 4 + 3] = (byte)(Math.Min(progress / aa_portion, 1) * (BorderColour.A * 255));
}
else
{
progress -= border_portion;
container.Attach(RenderbufferInternalFormat.DepthComponent16);
}
bytes[i * 4] = (byte)(AccentColour.R * 255);
bytes[i * 4 + 1] = (byte)(AccentColour.G * 255);
bytes[i * 4 + 2] = (byte)(AccentColour.B * 255);
bytes[i * 4 + 3] = (byte)((opacity_at_edge - (opacity_at_edge - opacity_at_centre) * progress / gradient_portion) * (AccentColour.A * 255));
}
}
texture.SetData(new TextureUpload(raw));
path.Texture = texture;
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => path.ReceivePositionalInputAt(screenSpacePos);
/// <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)
{
path.Vertices = vertices;
container.ForceRedraw();
}
private void computeSize()
private class SliderPath : SmoothPath
{
// Generate the entire curve
slider.Curve.GetPathToProgress(CurrentCurve, 0, 1);
foreach (Vector2 p in CurrentCurve)
path.AddVertex(p);
private const float border_portion = 0.128f;
private const float gradient_portion = 1 - border_portion;
Size = path.Size;
private const float opacity_at_centre = 0.3f;
private const float opacity_at_edge = 0.8f;
topLeftOffset = path.PositionInBoundingBox(Vector2.Zero);
PathOffset = path.PositionInBoundingBox(CurrentCurve[0]);
}
private Color4 borderColour = Color4.White;
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)
public Color4 BorderColour
{
if (Math.Min(span, slider.SpanCount() - 1) % 2 == 1)
get => borderColour;
set
{
start = 0;
end = SnakingOut ? spanProgress : 1;
}
else
{
start = SnakingOut ? spanProgress : 0;
if (borderColour == value)
return;
borderColour = value;
InvalidateTexture();
}
}
SetRange(start, end);
private Color4 accentColour = Color4.White;
public Color4 AccentColour
{
get => accentColour;
set
{
if (accentColour == value)
return;
accentColour = value;
InvalidateTexture();
}
}
protected override Color4 ColourAt(float position)
{
if (position <= border_portion)
return BorderColour;
position -= border_portion;
return new Color4(AccentColour.R, AccentColour.G, AccentColour.B, (opacity_at_edge - (opacity_at_edge - opacity_at_centre) * position / gradient_portion) * AccentColour.A);
}
}
}
}
@@ -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);
}
}
}
@@ -11,9 +11,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
{
public class SpinnerBackground : CircularContainer, IHasAccentColour
{
public override bool HandleKeyboardInput => false;
public override bool HandleMouseInput => false;
protected Box Disc;
public Color4 AccentColour

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