mirror of
https://github.com/ppy/osu.git
synced 2025-02-16 00:22:58 +08:00
Resolve merge conflict
This commit is contained in:
commit
8b4aa708fa
8
.github/pull_request_template.md
vendored
8
.github/pull_request_template.md
vendored
@ -1,8 +0,0 @@
|
||||
Add any details pertaining to developers above the break.
|
||||
|
||||
- [ ] Depends on #PR
|
||||
- Closes #ISSUE
|
||||
|
||||
---
|
||||
|
||||
Add a sentence or two describing this change in plain english. This will be displayed on the [changelog](https://osu.ppy.sh/home/changelog). A single screenshot or short gif is also welcomed.
|
5
.gitignore
vendored
5
.gitignore
vendored
@ -11,8 +11,9 @@
|
||||
*.userprefs
|
||||
|
||||
### Cake ###
|
||||
tools/*
|
||||
!tools/cakebuild.csproj
|
||||
tools/**
|
||||
build/tools/**
|
||||
|
||||
|
||||
# Build results
|
||||
bin/[Dd]ebug/
|
||||
|
14
.vscode/launch.json
vendored
14
.vscode/launch.json
vendored
@ -68,6 +68,20 @@
|
||||
}
|
||||
},
|
||||
"console": "internalConsole"
|
||||
},
|
||||
{
|
||||
"name": "Cake: Debug Script",
|
||||
"type": "coreclr",
|
||||
"request": "launch",
|
||||
"program": "${workspaceRoot}/build/tools/Cake.CoreCLR/0.30.0/Cake.dll",
|
||||
"args": [
|
||||
"${workspaceRoot}/build/build.cake",
|
||||
"--debug",
|
||||
"--verbosity=diagnostic"
|
||||
],
|
||||
"cwd": "${workspaceRoot}/build",
|
||||
"stopAtEntry": true,
|
||||
"externalConsole": false
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -53,15 +53,16 @@ if(!$PSScriptRoot){
|
||||
}
|
||||
|
||||
# Resolve the paths for resources used for debugging.
|
||||
$TOOLS_DIR = Join-Path $PSScriptRoot "tools"
|
||||
$CAKE_CSPROJ = Join-Path $TOOLS_DIR "cakebuild.csproj"
|
||||
$BUILD_DIR = Join-Path $PSScriptRoot "build"
|
||||
$TOOLS_DIR = Join-Path $BUILD_DIR "tools"
|
||||
$CAKE_CSPROJ = Join-Path $BUILD_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
|
||||
$CAKE_EXECUTABLE = (Get-ChildItem -Path "$TOOLS_DIR/cake.coreclr/" -Filter Cake.dll -Recurse).FullName
|
||||
|
||||
# Build Cake arguments
|
||||
$cakeArguments = @("$Script");
|
||||
@ -75,5 +76,7 @@ $cakeArguments += $ScriptArgs
|
||||
|
||||
# Start Cake
|
||||
Write-Host "Running build script..."
|
||||
Push-Location -Path $BUILD_DIR
|
||||
Invoke-Expression "dotnet `"$CAKE_EXECUTABLE`" $cakeArguments"
|
||||
Pop-Location
|
||||
exit $LASTEXITCODE
|
||||
|
3
build.sh
3
build.sh
@ -6,12 +6,13 @@
|
||||
|
||||
echo "Preparing to run build script..."
|
||||
|
||||
cd build
|
||||
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"
|
||||
CAKE_CSPROJ=$SCRIPT_DIR/"cakebuild.csproj"
|
||||
|
||||
# Parse arguments.
|
||||
CAKE_ARGUMENTS=()
|
||||
|
@ -1,6 +1,7 @@
|
||||
#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"
|
||||
var nVikaToolPath = GetFiles("./tools/NVika.MSBuild.*/tools/NVika.exe").First();
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// ARGUMENTS
|
||||
@ -9,30 +10,24 @@
|
||||
var target = Argument("target", "Build");
|
||||
var configuration = Argument("configuration", "Release");
|
||||
|
||||
var osuSolution = new FilePath("./osu.sln");
|
||||
var rootDirectory = new DirectoryPath("..");
|
||||
var solution = rootDirectory.CombineWithFilePath("osu.sln");
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// TASKS
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
Task("Restore")
|
||||
.Does(() => {
|
||||
DotNetCoreRestore(osuSolution.FullPath);
|
||||
});
|
||||
|
||||
Task("Compile")
|
||||
.IsDependentOn("Restore")
|
||||
.Does(() => {
|
||||
DotNetCoreBuild(osuSolution.FullPath, new DotNetCoreBuildSettings {
|
||||
DotNetCoreBuild(solution.FullPath, new DotNetCoreBuildSettings {
|
||||
Configuration = configuration,
|
||||
NoRestore = true,
|
||||
});
|
||||
});
|
||||
|
||||
Task("Test")
|
||||
.IsDependentOn("Compile")
|
||||
.Does(() => {
|
||||
var testAssemblies = GetFiles("**/*.Tests/bin/**/*.Tests.dll");
|
||||
var testAssemblies = GetFiles(rootDirectory + "/**/*.Tests/bin/**/*.Tests.dll");
|
||||
|
||||
DotNetCoreVSTest(testAssemblies, new DotNetCoreVSTestSettings {
|
||||
Logger = AppVeyor.IsRunningOnAppVeyor ? "Appveyor" : $"trx",
|
||||
@ -46,9 +41,7 @@ Task("InspectCode")
|
||||
.WithCriteria(IsRunningOnWindows())
|
||||
.IsDependentOn("Compile")
|
||||
.Does(() => {
|
||||
var nVikaToolPath = GetFiles("./tools/NVika.MSBuild.*/tools/NVika.exe").First();
|
||||
|
||||
InspectCode(osuSolution, new InspectCodeSettings {
|
||||
InspectCode(solution, new InspectCodeSettings {
|
||||
CachesHome = "inspectcode",
|
||||
OutputFile = "inspectcodereport.xml",
|
||||
});
|
||||
@ -59,7 +52,7 @@ Task("InspectCode")
|
||||
Task("CodeFileSanity")
|
||||
.Does(() => {
|
||||
ValidateCodeSanity(new ValidateCodeSanitySettings {
|
||||
RootDirectory = ".",
|
||||
RootDirectory = rootDirectory.FullPath,
|
||||
IsAppveyorBuild = AppVeyor.IsRunningOnAppVeyor
|
||||
});
|
||||
});
|
@ -60,7 +60,7 @@ namespace osu.Desktop.Overlays
|
||||
{
|
||||
new OsuSpriteText
|
||||
{
|
||||
Font = @"Exo2.0-Bold",
|
||||
Font = OsuFont.GetFont(weight: FontWeight.Bold),
|
||||
Text = game.Name
|
||||
},
|
||||
new OsuSpriteText
|
||||
@ -74,9 +74,8 @@ namespace osu.Desktop.Overlays
|
||||
{
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre,
|
||||
TextSize = 12,
|
||||
Font = OsuFont.Numeric.With(size: 12),
|
||||
Colour = colours.Yellow,
|
||||
Font = @"Venera",
|
||||
Text = @"Development Build"
|
||||
},
|
||||
new Sprite
|
||||
|
@ -0,0 +1,24 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using NUnit.Framework;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Catch.Difficulty;
|
||||
using osu.Game.Rulesets.Difficulty;
|
||||
using osu.Game.Tests.Beatmaps;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Tests
|
||||
{
|
||||
public class CatchDifficultyCalculatorTest : DifficultyCalculatorTest
|
||||
{
|
||||
protected override string ResourceAssembly => "osu.Game.Rulesets.Catch";
|
||||
|
||||
[TestCase(4.2038001515546597d, "diffcalc-test")]
|
||||
public void Test(double expected, string name)
|
||||
=> base.Test(expected, name);
|
||||
|
||||
protected override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new CatchDifficultyCalculator(new CatchRuleset(), beatmap);
|
||||
|
||||
protected override Ruleset CreateRuleset() => new CatchRuleset();
|
||||
}
|
||||
}
|
@ -2,7 +2,6 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Game.Rulesets.Difficulty;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Difficulty
|
||||
{
|
||||
@ -10,10 +9,5 @@ namespace osu.Game.Rulesets.Catch.Difficulty
|
||||
{
|
||||
public double ApproachRate;
|
||||
public int MaxCombo;
|
||||
|
||||
public CatchDifficultyAttributes(Mod[] mods, double starRating)
|
||||
: base(mods, starRating)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,148 +1,89 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Difficulty;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Catch.Difficulty.Preprocessing;
|
||||
using osu.Game.Rulesets.Catch.Difficulty.Skills;
|
||||
using osu.Game.Rulesets.Catch.Objects;
|
||||
using osu.Game.Rulesets.Catch.UI;
|
||||
using osu.Game.Rulesets.Difficulty;
|
||||
using osu.Game.Rulesets.Difficulty.Preprocessing;
|
||||
using osu.Game.Rulesets.Difficulty.Skills;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Difficulty
|
||||
{
|
||||
public class CatchDifficultyCalculator : DifficultyCalculator
|
||||
{
|
||||
/// <summary>
|
||||
/// In milliseconds. For difficulty calculation we will only look at the highest strain value in each time interval of size STRAIN_STEP.
|
||||
/// This is to eliminate higher influence of stream over aim by simply having more HitObjects with high strain.
|
||||
/// The higher this value, the less strains there will be, indirectly giving long beatmaps an advantage.
|
||||
/// </summary>
|
||||
private const double strain_step = 750;
|
||||
|
||||
/// <summary>
|
||||
/// The weighting of each strain value decays to this number * it's previous value
|
||||
/// </summary>
|
||||
private const double decay_weight = 0.94;
|
||||
|
||||
private const double star_scaling_factor = 0.145;
|
||||
|
||||
protected override int SectionLength => 750;
|
||||
|
||||
private readonly float halfCatchWidth;
|
||||
|
||||
public CatchDifficultyCalculator(Ruleset ruleset, WorkingBeatmap beatmap)
|
||||
: base(ruleset, beatmap)
|
||||
{
|
||||
var catcher = new CatcherArea.Catcher(beatmap.BeatmapInfo.BaseDifficulty);
|
||||
halfCatchWidth = catcher.CatchWidth * 0.5f;
|
||||
|
||||
// We're only using 80% of the catcher's width to simulate imperfect gameplay.
|
||||
halfCatchWidth *= 0.8f;
|
||||
}
|
||||
|
||||
protected override DifficultyAttributes Calculate(IBeatmap beatmap, Mod[] mods, double timeRate)
|
||||
protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills, double clockRate)
|
||||
{
|
||||
if (!beatmap.HitObjects.Any())
|
||||
return new CatchDifficultyAttributes(mods, 0);
|
||||
if (beatmap.HitObjects.Count == 0)
|
||||
return new CatchDifficultyAttributes { Mods = mods };
|
||||
|
||||
var catcher = new CatcherArea.Catcher(beatmap.BeatmapInfo.BaseDifficulty);
|
||||
float halfCatchWidth = catcher.CatchWidth * 0.5f;
|
||||
// this is the same as osu!, so there's potential to share the implementation... maybe
|
||||
double preempt = BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.ApproachRate, 1800, 1200, 450) / clockRate;
|
||||
|
||||
var difficultyHitObjects = new List<CatchDifficultyHitObject>();
|
||||
|
||||
foreach (var hitObject in beatmap.HitObjects)
|
||||
return new CatchDifficultyAttributes
|
||||
{
|
||||
StarRating = Math.Sqrt(skills[0].DifficultyValue()) * star_scaling_factor,
|
||||
Mods = mods,
|
||||
ApproachRate = preempt > 1200.0 ? -(preempt - 1800.0) / 120.0 : -(preempt - 1200.0) / 150.0 + 5.0,
|
||||
MaxCombo = beatmap.HitObjects.Count(h => h is Fruit) + beatmap.HitObjects.OfType<JuiceStream>().SelectMany(j => j.NestedHitObjects).Count(h => !(h is TinyDroplet))
|
||||
};
|
||||
}
|
||||
|
||||
protected override IEnumerable<DifficultyHitObject> CreateDifficultyHitObjects(IBeatmap beatmap, double clockRate)
|
||||
{
|
||||
CatchHitObject lastObject = null;
|
||||
|
||||
foreach (var hitObject in beatmap.HitObjects.OfType<CatchHitObject>())
|
||||
{
|
||||
if (lastObject == null)
|
||||
{
|
||||
lastObject = hitObject;
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (hitObject)
|
||||
{
|
||||
// We want to only consider fruits that contribute to the combo. Droplets are addressed as accuracy and spinners are not relevant for "skill" calculations.
|
||||
case Fruit fruit:
|
||||
difficultyHitObjects.Add(new CatchDifficultyHitObject(fruit, halfCatchWidth));
|
||||
yield return new CatchDifficultyHitObject(fruit, lastObject, clockRate, halfCatchWidth);
|
||||
lastObject = hitObject;
|
||||
break;
|
||||
case JuiceStream _:
|
||||
difficultyHitObjects.AddRange(hitObject.NestedHitObjects.OfType<CatchHitObject>().Where(o => !(o is TinyDroplet)).Select(o => new CatchDifficultyHitObject(o, halfCatchWidth)));
|
||||
foreach (var nested in hitObject.NestedHitObjects.OfType<CatchHitObject>().Where(o => !(o is TinyDroplet)))
|
||||
{
|
||||
yield return new CatchDifficultyHitObject(nested, lastObject, clockRate, halfCatchWidth);
|
||||
lastObject = nested;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
difficultyHitObjects.Sort((a, b) => a.BaseHitObject.StartTime.CompareTo(b.BaseHitObject.StartTime));
|
||||
|
||||
if (!calculateStrainValues(difficultyHitObjects, timeRate))
|
||||
return new CatchDifficultyAttributes(mods, 0);
|
||||
|
||||
// this is the same as osu!, so there's potential to share the implementation... maybe
|
||||
double preempt = BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.ApproachRate, 1800, 1200, 450) / timeRate;
|
||||
double starRating = Math.Sqrt(calculateDifficulty(difficultyHitObjects, timeRate)) * star_scaling_factor;
|
||||
|
||||
return new CatchDifficultyAttributes(mods, starRating)
|
||||
protected override Skill[] CreateSkills(IBeatmap beatmap) => new Skill[]
|
||||
{
|
||||
ApproachRate = preempt > 1200.0 ? -(preempt - 1800.0) / 120.0 : -(preempt - 1200.0) / 150.0 + 5.0,
|
||||
MaxCombo = difficultyHitObjects.Count
|
||||
new Movement(),
|
||||
};
|
||||
}
|
||||
|
||||
private bool calculateStrainValues(List<CatchDifficultyHitObject> objects, double timeRate)
|
||||
{
|
||||
CatchDifficultyHitObject lastObject = null;
|
||||
|
||||
if (!objects.Any()) return false;
|
||||
|
||||
// Traverse hitObjects in pairs to calculate the strain value of NextHitObject from the strain value of CurrentHitObject and environment.
|
||||
foreach (var currentObject in objects)
|
||||
{
|
||||
if (lastObject != null)
|
||||
currentObject.CalculateStrains(lastObject, timeRate);
|
||||
|
||||
lastObject = currentObject;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private double calculateDifficulty(List<CatchDifficultyHitObject> objects, double timeRate)
|
||||
{
|
||||
// The strain step needs to be adjusted for the algorithm to be considered equal with speed changing mods
|
||||
double actualStrainStep = strain_step * timeRate;
|
||||
|
||||
// Find the highest strain value within each strain step
|
||||
var highestStrains = new List<double>();
|
||||
double intervalEndTime = actualStrainStep;
|
||||
double maximumStrain = 0; // We need to keep track of the maximum strain in the current interval
|
||||
|
||||
CatchDifficultyHitObject previousHitObject = null;
|
||||
foreach (CatchDifficultyHitObject hitObject in objects)
|
||||
{
|
||||
// While we are beyond the current interval push the currently available maximum to our strain list
|
||||
while (hitObject.BaseHitObject.StartTime > intervalEndTime)
|
||||
{
|
||||
highestStrains.Add(maximumStrain);
|
||||
|
||||
// The maximum strain of the next interval is not zero by default! We need to take the last hitObject we encountered, take its strain and apply the decay
|
||||
// until the beginning of the next interval.
|
||||
if (previousHitObject == null)
|
||||
{
|
||||
maximumStrain = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
double decay = Math.Pow(CatchDifficultyHitObject.DECAY_BASE, (intervalEndTime - previousHitObject.BaseHitObject.StartTime) / 1000);
|
||||
maximumStrain = previousHitObject.Strain * decay;
|
||||
}
|
||||
|
||||
// Go to the next time interval
|
||||
intervalEndTime += actualStrainStep;
|
||||
}
|
||||
|
||||
// Obtain maximum strain
|
||||
maximumStrain = Math.Max(hitObject.Strain, maximumStrain);
|
||||
|
||||
previousHitObject = hitObject;
|
||||
}
|
||||
|
||||
// Build the weighted sum over the highest strains for each interval
|
||||
double difficulty = 0;
|
||||
double weight = 1;
|
||||
highestStrains.Sort((a, b) => b.CompareTo(a)); // Sort from highest to lowest strain.
|
||||
|
||||
foreach (double strain in highestStrains)
|
||||
{
|
||||
difficulty += weight * strain;
|
||||
weight *= decay_weight;
|
||||
}
|
||||
|
||||
return difficulty;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,130 +0,0 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using osu.Game.Rulesets.Catch.Objects;
|
||||
using osu.Game.Rulesets.Catch.UI;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Difficulty
|
||||
{
|
||||
public class CatchDifficultyHitObject
|
||||
{
|
||||
internal static readonly double DECAY_BASE = 0.20;
|
||||
private const float normalized_hitobject_radius = 41.0f;
|
||||
private const float absolute_player_positioning_error = 16f;
|
||||
private readonly float playerPositioningError;
|
||||
|
||||
internal CatchHitObject BaseHitObject;
|
||||
|
||||
/// <summary>
|
||||
/// Measures jump difficulty. CtB doesn't have something like button pressing speed or accuracy
|
||||
/// </summary>
|
||||
internal double Strain = 1;
|
||||
|
||||
/// <summary>
|
||||
/// This is required to keep track of lazy player movement (always moving only as far as necessary)
|
||||
/// Without this quick repeat sliders / weirdly shaped streams might become ridiculously overrated
|
||||
/// </summary>
|
||||
internal float PlayerPositionOffset;
|
||||
internal float LastMovement;
|
||||
|
||||
internal float NormalizedPosition;
|
||||
internal float ActualNormalizedPosition => NormalizedPosition + PlayerPositionOffset;
|
||||
|
||||
internal CatchDifficultyHitObject(CatchHitObject baseHitObject, float catcherWidthHalf)
|
||||
{
|
||||
BaseHitObject = baseHitObject;
|
||||
|
||||
// We will scale everything by this factor, so we can assume a uniform CircleSize among beatmaps.
|
||||
float scalingFactor = normalized_hitobject_radius / catcherWidthHalf;
|
||||
|
||||
playerPositioningError = absolute_player_positioning_error; // * scalingFactor;
|
||||
NormalizedPosition = baseHitObject.X * CatchPlayfield.BASE_WIDTH * scalingFactor;
|
||||
}
|
||||
|
||||
private const double direction_change_bonus = 12.5;
|
||||
internal void CalculateStrains(CatchDifficultyHitObject previousHitObject, double timeRate)
|
||||
{
|
||||
// Rather simple, but more specialized things are inherently inaccurate due to the big difference playstyles and opinions make.
|
||||
// See Taiko feedback thread.
|
||||
double timeElapsed = (BaseHitObject.StartTime - previousHitObject.BaseHitObject.StartTime) / timeRate;
|
||||
double decay = Math.Pow(DECAY_BASE, timeElapsed / 1000);
|
||||
|
||||
// Update new position with lazy movement.
|
||||
PlayerPositionOffset =
|
||||
MathHelper.Clamp(
|
||||
previousHitObject.ActualNormalizedPosition,
|
||||
NormalizedPosition - (normalized_hitobject_radius - playerPositioningError),
|
||||
NormalizedPosition + (normalized_hitobject_radius - playerPositioningError)) // Obtain new lazy position, but be stricter by allowing for an error of a certain degree of the player.
|
||||
- NormalizedPosition; // Subtract HitObject position to obtain offset
|
||||
|
||||
LastMovement = DistanceTo(previousHitObject);
|
||||
double addition = spacingWeight(LastMovement);
|
||||
|
||||
if (NormalizedPosition < previousHitObject.NormalizedPosition)
|
||||
{
|
||||
LastMovement = -LastMovement;
|
||||
}
|
||||
|
||||
CatchHitObject previousHitCircle = previousHitObject.BaseHitObject;
|
||||
|
||||
double additionBonus = 0;
|
||||
double sqrtTime = Math.Sqrt(Math.Max(timeElapsed, 25));
|
||||
|
||||
// Direction changes give an extra point!
|
||||
if (Math.Abs(LastMovement) > 0.1)
|
||||
{
|
||||
if (Math.Abs(previousHitObject.LastMovement) > 0.1 && Math.Sign(LastMovement) != Math.Sign(previousHitObject.LastMovement))
|
||||
{
|
||||
double bonus = direction_change_bonus / sqrtTime;
|
||||
|
||||
// Weight bonus by how
|
||||
double bonusFactor = Math.Min(playerPositioningError, Math.Abs(LastMovement)) / playerPositioningError;
|
||||
|
||||
// We want time to play a role twice here!
|
||||
addition += bonus * bonusFactor;
|
||||
|
||||
// Bonus for tougher direction switches and "almost" hyperdashes at this point
|
||||
if (previousHitCircle != null && previousHitCircle.DistanceToHyperDash <= 10.0f / CatchPlayfield.BASE_WIDTH)
|
||||
{
|
||||
additionBonus += 0.3 * bonusFactor;
|
||||
}
|
||||
}
|
||||
|
||||
// Base bonus for every movement, giving some weight to streams.
|
||||
addition += 7.5 * Math.Min(Math.Abs(LastMovement), normalized_hitobject_radius * 2) / (normalized_hitobject_radius * 6) / sqrtTime;
|
||||
}
|
||||
|
||||
// Bonus for "almost" hyperdashes at corner points
|
||||
if (previousHitCircle != null && previousHitCircle.DistanceToHyperDash <= 10.0f / CatchPlayfield.BASE_WIDTH)
|
||||
{
|
||||
if (!previousHitCircle.HyperDash)
|
||||
{
|
||||
additionBonus += 1.0;
|
||||
}
|
||||
else
|
||||
{
|
||||
// After a hyperdash we ARE in the correct position. Always!
|
||||
PlayerPositionOffset = 0;
|
||||
}
|
||||
|
||||
addition *= 1.0 + additionBonus * ((10 - previousHitCircle.DistanceToHyperDash * CatchPlayfield.BASE_WIDTH) / 10);
|
||||
}
|
||||
|
||||
addition *= 850.0 / Math.Max(timeElapsed, 25);
|
||||
|
||||
Strain = previousHitObject.Strain * decay + addition;
|
||||
}
|
||||
|
||||
private static double spacingWeight(float distance)
|
||||
{
|
||||
return Math.Pow(distance, 1.3) / 500;
|
||||
}
|
||||
|
||||
internal float DistanceTo(CatchDifficultyHitObject other)
|
||||
{
|
||||
return Math.Abs(ActualNormalizedPosition - other.ActualNormalizedPosition);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using osu.Game.Rulesets.Catch.Objects;
|
||||
using osu.Game.Rulesets.Catch.UI;
|
||||
using osu.Game.Rulesets.Difficulty.Preprocessing;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Difficulty.Preprocessing
|
||||
{
|
||||
public class CatchDifficultyHitObject : DifficultyHitObject
|
||||
{
|
||||
private const float normalized_hitobject_radius = 41.0f;
|
||||
|
||||
public new CatchHitObject BaseObject => (CatchHitObject)base.BaseObject;
|
||||
|
||||
public new CatchHitObject LastObject => (CatchHitObject)base.LastObject;
|
||||
|
||||
public readonly float NormalizedPosition;
|
||||
public readonly float LastNormalizedPosition;
|
||||
|
||||
/// <summary>
|
||||
/// Milliseconds elapsed since the start time of the previous <see cref="CatchDifficultyHitObject"/>, with a minimum of 25ms.
|
||||
/// </summary>
|
||||
public readonly double StrainTime;
|
||||
|
||||
public CatchDifficultyHitObject(HitObject hitObject, HitObject lastObject, double clockRate, float halfCatcherWidth)
|
||||
: base(hitObject, lastObject, clockRate)
|
||||
{
|
||||
// We will scale everything by this factor, so we can assume a uniform CircleSize among beatmaps.
|
||||
var scalingFactor = normalized_hitobject_radius / halfCatcherWidth;
|
||||
|
||||
NormalizedPosition = BaseObject.X * CatchPlayfield.BASE_WIDTH * scalingFactor;
|
||||
LastNormalizedPosition = LastObject.X * CatchPlayfield.BASE_WIDTH * scalingFactor;
|
||||
|
||||
// Every strain interval is hard capped at the equivalent of 600 BPM streaming speed as a safety measure
|
||||
StrainTime = Math.Max(25, DeltaTime);
|
||||
}
|
||||
}
|
||||
}
|
85
osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs
Normal file
85
osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs
Normal file
@ -0,0 +1,85 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using osu.Game.Rulesets.Catch.Difficulty.Preprocessing;
|
||||
using osu.Game.Rulesets.Catch.UI;
|
||||
using osu.Game.Rulesets.Difficulty.Preprocessing;
|
||||
using osu.Game.Rulesets.Difficulty.Skills;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Difficulty.Skills
|
||||
{
|
||||
public class Movement : Skill
|
||||
{
|
||||
private const float absolute_player_positioning_error = 16f;
|
||||
private const float normalized_hitobject_radius = 41.0f;
|
||||
private const double direction_change_bonus = 12.5;
|
||||
|
||||
protected override double SkillMultiplier => 850;
|
||||
protected override double StrainDecayBase => 0.2;
|
||||
|
||||
protected override double DecayWeight => 0.94;
|
||||
|
||||
private float? lastPlayerPosition;
|
||||
private float lastDistanceMoved;
|
||||
|
||||
protected override double StrainValueOf(DifficultyHitObject current)
|
||||
{
|
||||
var catchCurrent = (CatchDifficultyHitObject)current;
|
||||
|
||||
if (lastPlayerPosition == null)
|
||||
lastPlayerPosition = catchCurrent.LastNormalizedPosition;
|
||||
|
||||
float playerPosition = MathHelper.Clamp(
|
||||
lastPlayerPosition.Value,
|
||||
catchCurrent.NormalizedPosition - (normalized_hitobject_radius - absolute_player_positioning_error),
|
||||
catchCurrent.NormalizedPosition + (normalized_hitobject_radius - absolute_player_positioning_error)
|
||||
);
|
||||
|
||||
float distanceMoved = playerPosition - lastPlayerPosition.Value;
|
||||
|
||||
double distanceAddition = Math.Pow(Math.Abs(distanceMoved), 1.3) / 500;
|
||||
double sqrtStrain = Math.Sqrt(catchCurrent.StrainTime);
|
||||
|
||||
double bonus = 0;
|
||||
|
||||
// Direction changes give an extra point!
|
||||
if (Math.Abs(distanceMoved) > 0.1)
|
||||
{
|
||||
if (Math.Abs(lastDistanceMoved) > 0.1 && Math.Sign(distanceMoved) != Math.Sign(lastDistanceMoved))
|
||||
{
|
||||
double bonusFactor = Math.Min(absolute_player_positioning_error, Math.Abs(distanceMoved)) / absolute_player_positioning_error;
|
||||
|
||||
distanceAddition += direction_change_bonus / sqrtStrain * bonusFactor;
|
||||
|
||||
// Bonus for tougher direction switches and "almost" hyperdashes at this point
|
||||
if (catchCurrent.LastObject.DistanceToHyperDash <= 10 / CatchPlayfield.BASE_WIDTH)
|
||||
bonus = 0.3 * bonusFactor;
|
||||
}
|
||||
|
||||
// Base bonus for every movement, giving some weight to streams.
|
||||
distanceAddition += 7.5 * Math.Min(Math.Abs(distanceMoved), normalized_hitobject_radius * 2) / (normalized_hitobject_radius * 6) / sqrtStrain;
|
||||
}
|
||||
|
||||
// Bonus for "almost" hyperdashes at corner points
|
||||
if (catchCurrent.LastObject.DistanceToHyperDash <= 10.0f / CatchPlayfield.BASE_WIDTH)
|
||||
{
|
||||
if (!catchCurrent.LastObject.HyperDash)
|
||||
bonus += 1.0;
|
||||
else
|
||||
{
|
||||
// After a hyperdash we ARE in the correct position. Always!
|
||||
playerPosition = catchCurrent.NormalizedPosition;
|
||||
}
|
||||
|
||||
distanceAddition *= 1.0 + bonus * ((10 - catchCurrent.LastObject.DistanceToHyperDash * CatchPlayfield.BASE_WIDTH) / 10);
|
||||
}
|
||||
|
||||
lastPlayerPosition = playerPosition;
|
||||
lastDistanceMoved = distanceMoved;
|
||||
|
||||
return distanceAddition / catchCurrent.StrainTime;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Rulesets.Catch.Objects;
|
||||
using osu.Game.Rulesets.Catch.UI;
|
||||
@ -55,9 +56,9 @@ namespace osu.Game.Rulesets.Catch.Mods
|
||||
return default_flashlight_size;
|
||||
}
|
||||
|
||||
protected override void OnComboChange(int newCombo)
|
||||
protected override void OnComboChange(ValueChangedEvent<int> e)
|
||||
{
|
||||
this.TransformTo(nameof(FlashlightSize), new Vector2(0, getSizeFor(newCombo)), FLASHLIGHT_FADE_DURATION);
|
||||
this.TransformTo(nameof(FlashlightSize), new Vector2(0, getSizeFor(e.NewValue)), FLASHLIGHT_FADE_DURATION);
|
||||
}
|
||||
|
||||
protected override string FragmentShader => "CircularFlashlight";
|
||||
|
@ -0,0 +1,138 @@
|
||||
osu file format v14
|
||||
|
||||
[General]
|
||||
StackLeniency: 0.3
|
||||
Mode: 2
|
||||
|
||||
[Difficulty]
|
||||
CircleSize:4
|
||||
OverallDifficulty:7
|
||||
ApproachRate:8.3
|
||||
SliderMultiplier:1.6
|
||||
SliderTickRate:1
|
||||
|
||||
[TimingPoints]
|
||||
500,500,4,2,1,50,1,0
|
||||
34500,-50,4,2,1,50,0,0
|
||||
|
||||
[HitObjects]
|
||||
// fruits spaced 1/1 beat apart
|
||||
32,128,0,5,0,0:0:0:0:
|
||||
96,128,500,1,0,0:0:0:0:
|
||||
160,128,1000,1,0,0:0:0:0:
|
||||
224,128,1500,1,0,0:0:0:0:
|
||||
288,128,2000,1,0,0:0:0:0:
|
||||
352,128,2500,1,0,0:0:0:0:
|
||||
416,128,3000,1,0,0:0:0:0:
|
||||
480,128,3500,1,0,0:0:0:0:
|
||||
|
||||
// fruits spaced 1/2 beat apart
|
||||
32,160,4500,1,0,0:0:0:0:
|
||||
64,160,4750,1,0,0:0:0:0:
|
||||
96,160,5000,1,0,0:0:0:0:
|
||||
128,160,5250,1,0,0:0:0:0:
|
||||
160,160,5500,1,0,0:0:0:0:
|
||||
192,160,5750,1,0,0:0:0:0:
|
||||
224,160,6000,1,0,0:0:0:0:
|
||||
256,160,6250,1,0,0:0:0:0:
|
||||
288,160,6500,1,0,0:0:0:0:
|
||||
|
||||
// fruits spaced 1/4 beat apart
|
||||
96,128,7500,1,0,0:0:0:0:
|
||||
128,128,7625,1,0,0:0:0:0:
|
||||
160,128,7750,1,0,0:0:0:0:
|
||||
192,128,7875,1,0,0:0:0:0:
|
||||
224,128,8000,1,0,0:0:0:0:
|
||||
256,128,8125,1,0,0:0:0:0:
|
||||
288,128,8250,1,0,0:0:0:0:
|
||||
320,128,8375,1,0,0:0:0:0:
|
||||
352,128,8500,1,0,0:0:0:0:
|
||||
|
||||
// fruit hyperdashes, spaced 1/2 beat apart
|
||||
32,160,9500,1,0,0:0:0:0:
|
||||
480,160,9750,1,0,0:0:0:0:
|
||||
32,160,10000,1,0,0:0:0:0:
|
||||
480,160,10250,1,0,0:0:0:0:
|
||||
32,160,10500,1,0,0:0:0:0:
|
||||
480,160,10750,1,0,0:0:0:0:
|
||||
32,160,11000,1,0,0:0:0:0:
|
||||
|
||||
// fruit hyperdashes, spaced 1/4 beat apart
|
||||
32,192,12000,1,0,0:0:0:0:
|
||||
480,192,12125,1,0,0:0:0:0:
|
||||
32,192,12250,1,0,0:0:0:0:
|
||||
480,192,12375,1,0,0:0:0:0:
|
||||
32,192,12500,1,0,0:0:0:0:
|
||||
480,192,12625,1,0,0:0:0:0:
|
||||
32,192,12750,1,0,0:0:0:0:
|
||||
480,192,12875,1,0,0:0:0:0:
|
||||
32,192,13000,1,0,0:0:0:0:
|
||||
|
||||
// stream + hyperdash + stream, spaced 1/4 beat apart
|
||||
32,192,14000,1,0,0:0:0:0:
|
||||
64,192,14125,1,0,0:0:0:0:
|
||||
96,192,14250,1,0,0:0:0:0:
|
||||
128,192,14375,1,0,0:0:0:0:
|
||||
480,192,14500,1,0,0:0:0:0:
|
||||
448,192,14625,1,0,0:0:0:0:
|
||||
416,192,14750,1,0,0:0:0:0:
|
||||
384,192,14875,1,0,0:0:0:0:
|
||||
32,192,15000,1,0,0:0:0:0:
|
||||
|
||||
// basic sliders
|
||||
32,192,16000,2,0,L|192:192,1,160
|
||||
224,192,17000,2,0,L|384:192,1,160
|
||||
416,192,17875,2,0,L|480:192,1,40
|
||||
|
||||
// slider hyperdashes, spaced 1/4 beat apart
|
||||
32,192,19000,2,0,L|128:192,1,80
|
||||
480,192,19375,2,0,L|384:192,1,80
|
||||
352,192,19750,2,0,L|256:192,1,80
|
||||
0,192,20125,2,0,L|128:192,1,120
|
||||
|
||||
// stream + slider hyperdashes, spaced 1/4 beat apart
|
||||
32,192,21500,1,0,0:0:0:0:
|
||||
64,192,21625,1,0,0:0:0:0:
|
||||
96,192,21750,1,0,0:0:0:0:
|
||||
512,192,21875,2,0,L|320:192,1,160
|
||||
320,192,22500,1,0,0:0:0:0:
|
||||
288,192,22625,1,0,0:0:0:0:
|
||||
256,192,22750,1,0,0:0:0:0:
|
||||
0,192,22875,2,0,L|64:192,1,40
|
||||
|
||||
// streams, spaced 1/4 beat apart
|
||||
64,192,24000,1,0,0:0:0:0:
|
||||
160,192,24125,1,0,0:0:0:0:
|
||||
64,192,24250,1,0,0:0:0:0:
|
||||
160,192,24375,1,0,0:0:0:0:
|
||||
64,192,24500,1,0,0:0:0:0:
|
||||
160,192,24625,1,0,0:0:0:0:
|
||||
64,192,24750,1,0,0:0:0:0:
|
||||
160,192,24875,1,0,0:0:0:0:
|
||||
64,192,25000,1,0,0:0:0:0:
|
||||
160,192,25125,1,0,0:0:0:0:
|
||||
64,192,25250,1,0,0:0:0:0:
|
||||
160,192,25375,1,0,0:0:0:0:
|
||||
64,192,25500,1,0,0:0:0:0:
|
||||
|
||||
// stream + spinner combo, spaced 1/4 beat apart
|
||||
256,192,26500,12,0,27000,0:0:0:0:
|
||||
128,192,27250,5,0,0:0:0:0:
|
||||
128,192,27375,1,0,0:0:0:0:
|
||||
160,192,27500,1,0,0:0:0:0:
|
||||
192,192,27625,1,0,0:0:0:0:
|
||||
256,192,27750,12,0,28500,0:0:0:0:
|
||||
192,192,28625,5,0,0:0:0:0:
|
||||
224,192,28750,1,0,0:0:0:0:
|
||||
256,192,28875,1,0,0:0:0:0:
|
||||
256,192,29000,1,0,0:0:0:0:
|
||||
256,192,29125,12,0,29500,0:0:0:0:
|
||||
|
||||
// long slow slider
|
||||
0,192,30500,6,0,B|480:192|480:192|0:192,2,960
|
||||
|
||||
// long fast slider
|
||||
0,192,37500,6,0,B|480:192|480:192|0:192,2,960
|
||||
|
||||
// long hyperdash slider
|
||||
0,192,41500,2,0,P|544:192|544:192,5,480
|
@ -25,7 +25,7 @@ namespace osu.Game.Rulesets.Catch.UI
|
||||
{
|
||||
public class CatcherArea : Container
|
||||
{
|
||||
public const float CATCHER_SIZE = 100;
|
||||
public const float CATCHER_SIZE = 106.75f;
|
||||
|
||||
protected internal readonly Catcher MovableCatcher;
|
||||
|
||||
|
@ -0,0 +1,24 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using NUnit.Framework;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Difficulty;
|
||||
using osu.Game.Rulesets.Mania.Difficulty;
|
||||
using osu.Game.Tests.Beatmaps;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Tests
|
||||
{
|
||||
public class ManiaDifficultyCalculatorTest : DifficultyCalculatorTest
|
||||
{
|
||||
protected override string ResourceAssembly => "osu.Game.Rulesets.Mania";
|
||||
|
||||
[TestCase(2.3683365342338796d, "diffcalc-test")]
|
||||
public void Test(double expected, string name)
|
||||
=> base.Test(expected, name);
|
||||
|
||||
protected override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new ManiaDifficultyCalculator(new ManiaRuleset(), beatmap);
|
||||
|
||||
protected override Ruleset CreateRuleset() => new ManiaRuleset();
|
||||
}
|
||||
}
|
@ -3,7 +3,7 @@
|
||||
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Configuration;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Game.Rulesets.Mania.Configuration;
|
||||
using osu.Game.Rulesets.Mania.UI;
|
||||
using osu.Game.Tests.Visual;
|
||||
|
@ -6,7 +6,7 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Configuration;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
@ -14,6 +14,7 @@ using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Rulesets.Mania.Objects;
|
||||
using osu.Game.Rulesets.Mania.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
@ -141,7 +142,7 @@ namespace osu.Game.Rulesets.Mania.Tests
|
||||
{
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre,
|
||||
TextSize = 14,
|
||||
Font = OsuFont.GetFont(size: 14),
|
||||
Text = description
|
||||
}
|
||||
}
|
||||
|
@ -2,17 +2,11 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Game.Rulesets.Difficulty;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Difficulty
|
||||
{
|
||||
public class ManiaDifficultyAttributes : DifficultyAttributes
|
||||
{
|
||||
public double GreatHitWindow;
|
||||
|
||||
public ManiaDifficultyAttributes(Mod[] mods, double starRating)
|
||||
: base(mods, starRating)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,34 +1,24 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Difficulty;
|
||||
using osu.Game.Rulesets.Difficulty.Preprocessing;
|
||||
using osu.Game.Rulesets.Difficulty.Skills;
|
||||
using osu.Game.Rulesets.Mania.Beatmaps;
|
||||
using osu.Game.Rulesets.Mania.Difficulty.Preprocessing;
|
||||
using osu.Game.Rulesets.Mania.Difficulty.Skills;
|
||||
using osu.Game.Rulesets.Mania.Mods;
|
||||
using osu.Game.Rulesets.Mania.Objects;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Difficulty
|
||||
{
|
||||
internal class ManiaDifficultyCalculator : DifficultyCalculator
|
||||
public class ManiaDifficultyCalculator : DifficultyCalculator
|
||||
{
|
||||
private const double star_scaling_factor = 0.018;
|
||||
|
||||
/// <summary>
|
||||
/// In milliseconds. For difficulty calculation we will only look at the highest strain value in each time interval of size strain_step.
|
||||
/// This is to eliminate higher influence of stream over aim by simply having more HitObjects with high strain.
|
||||
/// The higher this value, the less strains there will be, indirectly giving long beatmaps an advantage.
|
||||
/// </summary>
|
||||
private const double strain_step = 400;
|
||||
|
||||
/// <summary>
|
||||
/// The weighting of each strain value decays to this number * it's previous value
|
||||
/// </summary>
|
||||
private const double decay_weight = 0.9;
|
||||
|
||||
private readonly bool isForCurrentRuleset;
|
||||
|
||||
public ManiaDifficultyCalculator(Ruleset ruleset, WorkingBeatmap beatmap)
|
||||
@ -37,108 +27,70 @@ namespace osu.Game.Rulesets.Mania.Difficulty
|
||||
isForCurrentRuleset = beatmap.BeatmapInfo.Ruleset.Equals(ruleset.RulesetInfo);
|
||||
}
|
||||
|
||||
protected override DifficultyAttributes Calculate(IBeatmap beatmap, Mod[] mods, double timeRate)
|
||||
protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills, double clockRate)
|
||||
{
|
||||
if (!beatmap.HitObjects.Any())
|
||||
return new ManiaDifficultyAttributes(mods, 0);
|
||||
if (beatmap.HitObjects.Count == 0)
|
||||
return new ManiaDifficultyAttributes { Mods = mods };
|
||||
|
||||
var difficultyHitObjects = new List<ManiaHitObjectDifficulty>();
|
||||
|
||||
int columnCount = ((ManiaBeatmap)beatmap).TotalColumns;
|
||||
|
||||
// Sort DifficultyHitObjects by StartTime of the HitObjects - just to make sure.
|
||||
// Note: Stable sort is done so that the ordering of hitobjects with equal start times doesn't change
|
||||
difficultyHitObjects.AddRange(beatmap.HitObjects.Select(h => new ManiaHitObjectDifficulty((ManiaHitObject)h, columnCount)).OrderBy(h => h.BaseHitObject.StartTime));
|
||||
|
||||
if (!calculateStrainValues(difficultyHitObjects, timeRate))
|
||||
return new ManiaDifficultyAttributes(mods, 0);
|
||||
|
||||
double starRating = calculateDifficulty(difficultyHitObjects, timeRate) * star_scaling_factor;
|
||||
|
||||
return new ManiaDifficultyAttributes(mods, starRating)
|
||||
return new ManiaDifficultyAttributes
|
||||
{
|
||||
// Todo: This int cast is temporary to achieve 1:1 results with osu!stable, and should be remoevd in the future
|
||||
GreatHitWindow = (int)(beatmap.HitObjects.First().HitWindows.Great / 2) / timeRate
|
||||
StarRating = difficultyValue(skills) * star_scaling_factor,
|
||||
Mods = mods,
|
||||
// Todo: This int cast is temporary to achieve 1:1 results with osu!stable, and should be removed in the future
|
||||
GreatHitWindow = (int)(beatmap.HitObjects.First().HitWindows.Great / 2) / clockRate,
|
||||
};
|
||||
}
|
||||
|
||||
private bool calculateStrainValues(List<ManiaHitObjectDifficulty> objects, double timeRate)
|
||||
private double difficultyValue(Skill[] skills)
|
||||
{
|
||||
// Traverse hitObjects in pairs to calculate the strain value of NextHitObject from the strain value of CurrentHitObject and environment.
|
||||
using (var hitObjectsEnumerator = objects.GetEnumerator())
|
||||
// Preprocess the strains to find the maximum overall + individual (aggregate) strain from each section
|
||||
var overall = skills.OfType<Overall>().Single();
|
||||
var aggregatePeaks = new List<double>(Enumerable.Repeat(0.0, overall.StrainPeaks.Count));
|
||||
|
||||
foreach (var individual in skills.OfType<Individual>())
|
||||
{
|
||||
if (!hitObjectsEnumerator.MoveNext())
|
||||
return false;
|
||||
|
||||
ManiaHitObjectDifficulty current = hitObjectsEnumerator.Current;
|
||||
|
||||
// First hitObject starts at strain 1. 1 is the default for strain values, so we don't need to set it here. See DifficultyHitObject.
|
||||
while (hitObjectsEnumerator.MoveNext())
|
||||
for (int i = 0; i < individual.StrainPeaks.Count; i++)
|
||||
{
|
||||
var next = hitObjectsEnumerator.Current;
|
||||
next?.CalculateStrains(current, timeRate);
|
||||
current = next;
|
||||
}
|
||||
double aggregate = individual.StrainPeaks[i] + overall.StrainPeaks[i];
|
||||
|
||||
return true;
|
||||
if (aggregate > aggregatePeaks[i])
|
||||
aggregatePeaks[i] = aggregate;
|
||||
}
|
||||
}
|
||||
|
||||
private double calculateDifficulty(List<ManiaHitObjectDifficulty> objects, double timeRate)
|
||||
{
|
||||
double actualStrainStep = strain_step * timeRate;
|
||||
aggregatePeaks.Sort((a, b) => b.CompareTo(a)); // Sort from highest to lowest strain.
|
||||
|
||||
// Find the highest strain value within each strain step
|
||||
List<double> highestStrains = new List<double>();
|
||||
double intervalEndTime = actualStrainStep;
|
||||
double maximumStrain = 0; // We need to keep track of the maximum strain in the current interval
|
||||
|
||||
ManiaHitObjectDifficulty previousHitObject = null;
|
||||
foreach (var hitObject in objects)
|
||||
{
|
||||
// While we are beyond the current interval push the currently available maximum to our strain list
|
||||
while (hitObject.BaseHitObject.StartTime > intervalEndTime)
|
||||
{
|
||||
highestStrains.Add(maximumStrain);
|
||||
|
||||
// The maximum strain of the next interval is not zero by default! We need to take the last hitObject we encountered, take its strain and apply the decay
|
||||
// until the beginning of the next interval.
|
||||
if (previousHitObject == null)
|
||||
{
|
||||
maximumStrain = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
double individualDecay = Math.Pow(ManiaHitObjectDifficulty.INDIVIDUAL_DECAY_BASE, (intervalEndTime - previousHitObject.BaseHitObject.StartTime) / 1000);
|
||||
double overallDecay = Math.Pow(ManiaHitObjectDifficulty.OVERALL_DECAY_BASE, (intervalEndTime - previousHitObject.BaseHitObject.StartTime) / 1000);
|
||||
maximumStrain = previousHitObject.IndividualStrain * individualDecay + previousHitObject.OverallStrain * overallDecay;
|
||||
}
|
||||
|
||||
// Go to the next time interval
|
||||
intervalEndTime += actualStrainStep;
|
||||
}
|
||||
|
||||
// Obtain maximum strain
|
||||
double strain = hitObject.IndividualStrain + hitObject.OverallStrain;
|
||||
maximumStrain = Math.Max(strain, maximumStrain);
|
||||
|
||||
previousHitObject = hitObject;
|
||||
}
|
||||
|
||||
// Build the weighted sum over the highest strains for each interval
|
||||
double difficulty = 0;
|
||||
double weight = 1;
|
||||
highestStrains.Sort((a, b) => b.CompareTo(a)); // Sort from highest to lowest strain.
|
||||
|
||||
foreach (double strain in highestStrains)
|
||||
// Difficulty is the weighted sum of the highest strains from every section.
|
||||
foreach (double strain in aggregatePeaks)
|
||||
{
|
||||
difficulty += weight * strain;
|
||||
weight *= decay_weight;
|
||||
difficulty += strain * weight;
|
||||
weight *= 0.9;
|
||||
}
|
||||
|
||||
return difficulty;
|
||||
}
|
||||
|
||||
protected override IEnumerable<DifficultyHitObject> CreateDifficultyHitObjects(IBeatmap beatmap, double clockRate)
|
||||
{
|
||||
for (int i = 1; i < beatmap.HitObjects.Count; i++)
|
||||
yield return new ManiaDifficultyHitObject(beatmap.HitObjects[i], beatmap.HitObjects[i - 1], clockRate);
|
||||
}
|
||||
|
||||
protected override Skill[] CreateSkills(IBeatmap beatmap)
|
||||
{
|
||||
int columnCount = ((ManiaBeatmap)beatmap).TotalColumns;
|
||||
|
||||
var skills = new List<Skill> { new Overall(columnCount) };
|
||||
|
||||
for (int i = 0; i < columnCount; i++)
|
||||
skills.Add(new Individual(i, columnCount));
|
||||
|
||||
return skills.ToArray();
|
||||
}
|
||||
|
||||
protected override Mod[] DifficultyAdjustmentMods
|
||||
{
|
||||
get
|
||||
|
@ -0,0 +1,19 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Game.Rulesets.Difficulty.Preprocessing;
|
||||
using osu.Game.Rulesets.Mania.Objects;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Difficulty.Preprocessing
|
||||
{
|
||||
public class ManiaDifficultyHitObject : DifficultyHitObject
|
||||
{
|
||||
public new ManiaHitObject BaseObject => (ManiaHitObject)base.BaseObject;
|
||||
|
||||
public ManiaDifficultyHitObject(HitObject hitObject, HitObject lastObject, double clockRate)
|
||||
: base(hitObject, lastObject, clockRate)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
47
osu.Game.Rulesets.Mania/Difficulty/Skills/Individual.cs
Normal file
47
osu.Game.Rulesets.Mania/Difficulty/Skills/Individual.cs
Normal file
@ -0,0 +1,47 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Linq;
|
||||
using osu.Game.Rulesets.Difficulty.Preprocessing;
|
||||
using osu.Game.Rulesets.Difficulty.Skills;
|
||||
using osu.Game.Rulesets.Mania.Difficulty.Preprocessing;
|
||||
using osu.Game.Rulesets.Mania.Objects;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Difficulty.Skills
|
||||
{
|
||||
public class Individual : Skill
|
||||
{
|
||||
protected override double SkillMultiplier => 1;
|
||||
protected override double StrainDecayBase => 0.125;
|
||||
|
||||
private readonly double[] holdEndTimes;
|
||||
|
||||
private readonly int column;
|
||||
|
||||
public Individual(int column, int columnCount)
|
||||
{
|
||||
this.column = column;
|
||||
|
||||
holdEndTimes = new double[columnCount];
|
||||
}
|
||||
|
||||
protected override double StrainValueOf(DifficultyHitObject current)
|
||||
{
|
||||
var maniaCurrent = (ManiaDifficultyHitObject)current;
|
||||
var endTime = (maniaCurrent.BaseObject as HoldNote)?.EndTime ?? maniaCurrent.BaseObject.StartTime;
|
||||
|
||||
try
|
||||
{
|
||||
if (maniaCurrent.BaseObject.Column != column)
|
||||
return 0;
|
||||
|
||||
// We give a slight bonus if something is held meanwhile
|
||||
return holdEndTimes.Any(t => t > endTime) ? 2.5 : 2;
|
||||
}
|
||||
finally
|
||||
{
|
||||
holdEndTimes[maniaCurrent.BaseObject.Column] = endTime;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
56
osu.Game.Rulesets.Mania/Difficulty/Skills/Overall.cs
Normal file
56
osu.Game.Rulesets.Mania/Difficulty/Skills/Overall.cs
Normal file
@ -0,0 +1,56 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Game.Rulesets.Difficulty.Preprocessing;
|
||||
using osu.Game.Rulesets.Difficulty.Skills;
|
||||
using osu.Game.Rulesets.Mania.Difficulty.Preprocessing;
|
||||
using osu.Game.Rulesets.Mania.Objects;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Difficulty.Skills
|
||||
{
|
||||
public class Overall : Skill
|
||||
{
|
||||
protected override double SkillMultiplier => 1;
|
||||
protected override double StrainDecayBase => 0.3;
|
||||
|
||||
private readonly double[] holdEndTimes;
|
||||
|
||||
private readonly int columnCount;
|
||||
|
||||
public Overall(int columnCount)
|
||||
{
|
||||
this.columnCount = columnCount;
|
||||
|
||||
holdEndTimes = new double[columnCount];
|
||||
}
|
||||
|
||||
protected override double StrainValueOf(DifficultyHitObject current)
|
||||
{
|
||||
var maniaCurrent = (ManiaDifficultyHitObject)current;
|
||||
var endTime = (maniaCurrent.BaseObject as HoldNote)?.EndTime ?? maniaCurrent.BaseObject.StartTime;
|
||||
|
||||
double holdFactor = 1.0; // Factor in case something else is held
|
||||
double holdAddition = 0; // Addition to the current note in case it's a hold and has to be released awkwardly
|
||||
|
||||
for (int i = 0; i < columnCount; i++)
|
||||
{
|
||||
// If there is at least one other overlapping end or note, then we get an addition, buuuuuut...
|
||||
if (current.BaseObject.StartTime < holdEndTimes[i] && endTime > holdEndTimes[i])
|
||||
holdAddition = 1.0;
|
||||
|
||||
// ... this addition only is valid if there is _no_ other note with the same ending.
|
||||
// Releasing multiple notes at the same time is just as easy as releasing one
|
||||
if (endTime == holdEndTimes[i])
|
||||
holdAddition = 0;
|
||||
|
||||
// We give a slight bonus if something is held meanwhile
|
||||
if (holdEndTimes[i] > endTime)
|
||||
holdFactor = 1.25;
|
||||
}
|
||||
|
||||
holdEndTimes[maniaCurrent.BaseObject.Column] = endTime;
|
||||
|
||||
return (1 + holdAddition) * holdFactor;
|
||||
}
|
||||
}
|
||||
}
|
@ -2,7 +2,7 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Configuration;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Primitives;
|
||||
using osu.Game.Graphics;
|
||||
|
@ -2,6 +2,7 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Caching;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Rulesets.Mania.Objects;
|
||||
@ -51,7 +52,7 @@ namespace osu.Game.Rulesets.Mania.Mods
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnComboChange(int newCombo)
|
||||
protected override void OnComboChange(ValueChangedEvent<int> e)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -2,6 +2,7 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Linq;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces;
|
||||
@ -75,11 +76,11 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
||||
AddNested(Tail);
|
||||
}
|
||||
|
||||
protected override void OnDirectionChanged(ScrollingDirection direction)
|
||||
protected override void OnDirectionChanged(ValueChangedEvent<ScrollingDirection> e)
|
||||
{
|
||||
base.OnDirectionChanged(direction);
|
||||
base.OnDirectionChanged(e);
|
||||
|
||||
bodyPiece.Anchor = bodyPiece.Origin = direction == ScrollingDirection.Up ? Anchor.TopLeft : Anchor.BottomLeft;
|
||||
bodyPiece.Anchor = bodyPiece.Origin = e.NewValue == ScrollingDirection.Up ? Anchor.TopLeft : Anchor.BottomLeft;
|
||||
}
|
||||
|
||||
public override Color4 AccentColour
|
||||
|
@ -3,7 +3,7 @@
|
||||
|
||||
using JetBrains.Annotations;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Configuration;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.UI.Scrolling;
|
||||
@ -41,9 +41,9 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
||||
|
||||
protected override bool ShouldBeAlive => AlwaysAlive || base.ShouldBeAlive;
|
||||
|
||||
protected virtual void OnDirectionChanged(ScrollingDirection direction)
|
||||
protected virtual void OnDirectionChanged(ValueChangedEvent<ScrollingDirection> e)
|
||||
{
|
||||
Anchor = Origin = direction == ScrollingDirection.Up ? Anchor.TopCentre : Anchor.BottomCentre;
|
||||
Anchor = Origin = e.NewValue == ScrollingDirection.Up ? Anchor.TopCentre : Anchor.BottomCentre;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osuTK.Graphics;
|
||||
using osu.Framework.Graphics;
|
||||
@ -31,11 +32,11 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
||||
InternalChild = headPiece = new NotePiece();
|
||||
}
|
||||
|
||||
protected override void OnDirectionChanged(ScrollingDirection direction)
|
||||
protected override void OnDirectionChanged(ValueChangedEvent<ScrollingDirection> e)
|
||||
{
|
||||
base.OnDirectionChanged(direction);
|
||||
base.OnDirectionChanged(e);
|
||||
|
||||
headPiece.Anchor = headPiece.Origin = direction == ScrollingDirection.Up ? Anchor.TopCentre : Anchor.BottomCentre;
|
||||
headPiece.Anchor = headPiece.Origin = e.NewValue == ScrollingDirection.Up ? Anchor.TopCentre : Anchor.BottomCentre;
|
||||
}
|
||||
|
||||
public override Color4 AccentColour
|
||||
|
@ -2,7 +2,7 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Configuration;
|
||||
using osu.Framework.Bindables;
|
||||
using osuTK.Graphics;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
@ -49,9 +49,9 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces
|
||||
private void load(IScrollingInfo scrollingInfo)
|
||||
{
|
||||
direction.BindTo(scrollingInfo.Direction);
|
||||
direction.BindValueChanged(direction =>
|
||||
direction.BindValueChanged(dir =>
|
||||
{
|
||||
colouredBox.Anchor = colouredBox.Origin = direction == ScrollingDirection.Up ? Anchor.TopCentre : Anchor.BottomCentre;
|
||||
colouredBox.Anchor = colouredBox.Origin = dir.NewValue == ScrollingDirection.Up ? Anchor.TopCentre : Anchor.BottomCentre;
|
||||
}, true);
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Configuration;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Game.Rulesets.Mania.Objects.Types;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
|
||||
@ -13,7 +13,7 @@ namespace osu.Game.Rulesets.Mania.Objects
|
||||
|
||||
public virtual int Column
|
||||
{
|
||||
get => ColumnBindable;
|
||||
get => ColumnBindable.Value;
|
||||
set => ColumnBindable.Value = value;
|
||||
}
|
||||
|
||||
|
@ -1,112 +0,0 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
using System;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Objects
|
||||
{
|
||||
internal class ManiaHitObjectDifficulty
|
||||
{
|
||||
/// <summary>
|
||||
/// Factor by how much individual / overall strain decays per second.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// These values are results of tweaking a lot and taking into account general feedback.
|
||||
/// </remarks>
|
||||
internal const double INDIVIDUAL_DECAY_BASE = 0.125;
|
||||
internal const double OVERALL_DECAY_BASE = 0.30;
|
||||
|
||||
internal ManiaHitObject BaseHitObject;
|
||||
|
||||
private readonly int beatmapColumnCount;
|
||||
|
||||
private readonly double endTime;
|
||||
private readonly double[] heldUntil;
|
||||
|
||||
/// <summary>
|
||||
/// Measures jacks or more generally: repeated presses of the same button
|
||||
/// </summary>
|
||||
private readonly double[] individualStrains;
|
||||
|
||||
internal double IndividualStrain
|
||||
{
|
||||
get
|
||||
{
|
||||
return individualStrains[BaseHitObject.Column];
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
individualStrains[BaseHitObject.Column] = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Measures note density in a way
|
||||
/// </summary>
|
||||
internal double OverallStrain = 1;
|
||||
|
||||
public ManiaHitObjectDifficulty(ManiaHitObject baseHitObject, int columnCount)
|
||||
{
|
||||
BaseHitObject = baseHitObject;
|
||||
|
||||
endTime = (baseHitObject as IHasEndTime)?.EndTime ?? baseHitObject.StartTime;
|
||||
|
||||
beatmapColumnCount = columnCount;
|
||||
heldUntil = new double[beatmapColumnCount];
|
||||
individualStrains = new double[beatmapColumnCount];
|
||||
|
||||
for (int i = 0; i < beatmapColumnCount; ++i)
|
||||
{
|
||||
individualStrains[i] = 0;
|
||||
heldUntil[i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
internal void CalculateStrains(ManiaHitObjectDifficulty previousHitObject, double timeRate)
|
||||
{
|
||||
// TODO: Factor in holds
|
||||
double timeElapsed = (BaseHitObject.StartTime - previousHitObject.BaseHitObject.StartTime) / timeRate;
|
||||
double individualDecay = Math.Pow(INDIVIDUAL_DECAY_BASE, timeElapsed / 1000);
|
||||
double overallDecay = Math.Pow(OVERALL_DECAY_BASE, timeElapsed / 1000);
|
||||
|
||||
double holdFactor = 1.0; // Factor to all additional strains in case something else is held
|
||||
double holdAddition = 0; // Addition to the current note in case it's a hold and has to be released awkwardly
|
||||
|
||||
// Fill up the heldUntil array
|
||||
for (int i = 0; i < beatmapColumnCount; ++i)
|
||||
{
|
||||
heldUntil[i] = previousHitObject.heldUntil[i];
|
||||
|
||||
// If there is at least one other overlapping end or note, then we get an addition, buuuuuut...
|
||||
if (BaseHitObject.StartTime < heldUntil[i] && endTime > heldUntil[i])
|
||||
{
|
||||
holdAddition = 1.0;
|
||||
}
|
||||
|
||||
// ... this addition only is valid if there is _no_ other note with the same ending. Releasing multiple notes at the same time is just as easy as releasing 1
|
||||
if (endTime == heldUntil[i])
|
||||
{
|
||||
holdAddition = 0;
|
||||
}
|
||||
|
||||
// We give a slight bonus to everything if something is held meanwhile
|
||||
if (heldUntil[i] > endTime)
|
||||
{
|
||||
holdFactor = 1.25;
|
||||
}
|
||||
|
||||
// Decay individual strains
|
||||
individualStrains[i] = previousHitObject.individualStrains[i] * individualDecay;
|
||||
}
|
||||
|
||||
heldUntil[BaseHitObject.Column] = endTime;
|
||||
|
||||
// Increase individual strain in own column
|
||||
IndividualStrain += 2.0 * holdFactor;
|
||||
|
||||
OverallStrain = previousHitObject.OverallStrain * overallDecay + (1.0 + holdAddition) * holdFactor;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,180 @@
|
||||
osu file format v14
|
||||
|
||||
[General]
|
||||
Mode: 3
|
||||
|
||||
[Difficulty]
|
||||
CircleSize:4
|
||||
OverallDifficulty:7
|
||||
ApproachRate:8.3
|
||||
SliderMultiplier:1.6
|
||||
SliderTickRate:1
|
||||
|
||||
[TimingPoints]
|
||||
500,500,4,2,1,50,1,0
|
||||
37500,-50,4,2,1,50,0,0
|
||||
41500,-25,4,2,1,50,0,0
|
||||
|
||||
[HitObjects]
|
||||
// jacks spaced 1/1 beat apart
|
||||
64,192,0,1,0,0:0:0:0:
|
||||
64,192,500,1,0,0:0:0:0:
|
||||
64,192,1000,1,0,0:0:0:0:
|
||||
64,192,1500,1,0,0:0:0:0:
|
||||
64,192,2000,1,0,0:0:0:0:
|
||||
64,192,2500,1,0,0:0:0:0:
|
||||
|
||||
// jacks spaced 1/2 beat apart
|
||||
64,192,3500,1,0,0:0:0:0:
|
||||
64,192,3750,1,0,0:0:0:0:
|
||||
64,192,4000,1,0,0:0:0:0:
|
||||
64,192,4250,1,0,0:0:0:0:
|
||||
64,192,4500,1,0,0:0:0:0:
|
||||
64,192,4750,1,0,0:0:0:0:
|
||||
64,192,5000,1,0,0:0:0:0:
|
||||
64,192,6000,1,0,0:0:0:0:
|
||||
|
||||
// doubles jacks spaced 1/2 beat apart
|
||||
192,192,6000,1,0,0:0:0:0:
|
||||
64,192,6250,1,0,0:0:0:0:
|
||||
192,192,6250,1,0,0:0:0:0:
|
||||
64,192,6500,1,0,0:0:0:0:
|
||||
192,192,6500,1,0,0:0:0:0:
|
||||
64,192,6750,1,0,0:0:0:0:
|
||||
192,192,6750,1,0,0:0:0:0:
|
||||
64,192,7000,1,0,0:0:0:0:
|
||||
192,192,7000,1,0,0:0:0:0:
|
||||
64,192,7250,1,0,0:0:0:0:
|
||||
192,192,7250,1,0,0:0:0:0:
|
||||
64,192,7500,1,0,0:0:0:0:
|
||||
192,192,7500,1,0,0:0:0:0:
|
||||
|
||||
// trill spaced 1/2 beat apart
|
||||
64,192,8500,1,0,0:0:0:0:
|
||||
192,192,8750,1,0,0:0:0:0:
|
||||
64,192,9000,1,0,0:0:0:0:
|
||||
192,192,9250,1,0,0:0:0:0:
|
||||
64,192,9500,1,0,0:0:0:0:
|
||||
192,192,9750,1,0,0:0:0:0:
|
||||
64,192,10000,1,0,0:0:0:0:
|
||||
192,192,10250,1,0,0:0:0:0:
|
||||
64,192,10500,1,0,0:0:0:0:
|
||||
|
||||
// stair spaced 1/4 apart
|
||||
64,192,11500,1,0,0:0:0:0:
|
||||
192,192,11625,1,0,0:0:0:0:
|
||||
320,192,11750,1,0,0:0:0:0:
|
||||
448,192,11875,1,0,0:0:0:0:
|
||||
320,192,12000,1,0,0:0:0:0:
|
||||
192,192,12125,1,0,0:0:0:0:
|
||||
64,192,12250,1,0,0:0:0:0:
|
||||
192,192,12375,1,0,0:0:0:0:
|
||||
320,192,12500,1,0,0:0:0:0:
|
||||
448,192,12625,1,0,0:0:0:0:
|
||||
|
||||
// jumpstreams?
|
||||
64,192,13500,1,0,0:0:0:0:
|
||||
192,192,13625,1,0,0:0:0:0:
|
||||
320,192,13750,1,0,0:0:0:0:
|
||||
448,192,13875,1,0,0:0:0:0:
|
||||
320,192,14000,1,0,0:0:0:0:
|
||||
192,192,14000,1,0,0:0:0:0:
|
||||
64,192,14125,1,0,0:0:0:0:
|
||||
192,192,14250,1,0,0:0:0:0:
|
||||
320,192,14250,1,0,0:0:0:0:
|
||||
448,192,14250,1,0,0:0:0:0:
|
||||
64,192,14375,1,0,0:0:0:0:
|
||||
64,192,14500,1,0,0:0:0:0:
|
||||
320,192,14625,1,0,0:0:0:0:
|
||||
448,192,14625,1,0,0:0:0:0:
|
||||
192,192,14625,1,0,0:0:0:0:
|
||||
192,192,14750,1,0,0:0:0:0:
|
||||
64,192,14875,1,0,0:0:0:0:
|
||||
192,192,15000,1,0,0:0:0:0:
|
||||
320,192,15125,1,0,0:0:0:0:
|
||||
448,192,15125,1,0,0:0:0:0:
|
||||
|
||||
// double... jumps?
|
||||
64,192,16000,1,0,0:0:0:0:
|
||||
64,192,16250,1,0,0:0:0:0:
|
||||
192,192,16250,1,0,0:0:0:0:
|
||||
192,192,16500,1,0,0:0:0:0:
|
||||
320,192,16500,1,0,0:0:0:0:
|
||||
320,192,16750,1,0,0:0:0:0:
|
||||
448,192,16750,1,0,0:0:0:0:
|
||||
448,192,17000,1,0,0:0:0:0:
|
||||
|
||||
// notes alongside hold
|
||||
64,192,18000,128,0,18500:0:0:0:0:
|
||||
192,192,18000,1,0,0:0:0:0:
|
||||
192,192,18250,1,0,0:0:0:0:
|
||||
192,192,18500,1,0,0:0:0:0:
|
||||
|
||||
// notes overlapping hold
|
||||
64,192,19500,1,0,0:0:0:0:
|
||||
192,192,19625,128,0,20875:0:0:0:0:
|
||||
64,192,19750,1,0,0:0:0:0:
|
||||
64,192,20000,1,0,0:0:0:0:
|
||||
64,192,20250,1,0,0:0:0:0:
|
||||
64,192,20500,1,0,0:0:0:0:
|
||||
64,192,20750,1,0,0:0:0:0:
|
||||
64,192,21000,1,0,0:0:0:0:
|
||||
|
||||
// simultaneous holds
|
||||
64,192,22000,128,0,23000:0:0:0:0:
|
||||
192,192,22000,128,0,23000:0:0:0:0:
|
||||
320,192,22000,128,0,23000:0:0:0:0:
|
||||
448,192,22000,128,0,23000:0:0:0:0:
|
||||
|
||||
// hold stairs
|
||||
64,192,24500,128,0,25500:0:0:0:0:
|
||||
192,192,24625,128,0,25375:0:0:0:0:
|
||||
320,192,24750,128,0,25250:0:0:0:0:
|
||||
448,192,24875,128,0,25125:0:0:0:0:
|
||||
448,192,25375,128,0,26375:0:0:0:0:
|
||||
320,192,25500,128,0,26250:0:0:0:0:
|
||||
192,192,25625,128,0,26125:0:0:0:0:
|
||||
64,192,25750,128,0,26000:0:0:0:0:
|
||||
|
||||
// quads
|
||||
64,192,26500,1,0,0:0:0:0:
|
||||
64,192,27500,1,0,0:0:0:0:
|
||||
192,192,27500,1,0,0:0:0:0:
|
||||
320,192,27500,1,0,0:0:0:0:
|
||||
448,192,27500,1,0,0:0:0:0:
|
||||
64,192,27750,1,0,0:0:0:0:
|
||||
192,192,27750,1,0,0:0:0:0:
|
||||
320,192,27750,1,0,0:0:0:0:
|
||||
448,192,27750,1,0,0:0:0:0:
|
||||
64,192,28000,1,0,0:0:0:0:
|
||||
192,192,28000,1,0,0:0:0:0:
|
||||
320,192,28000,1,0,0:0:0:0:
|
||||
448,192,28000,1,0,0:0:0:0:
|
||||
64,192,28250,1,0,0:0:0:0:
|
||||
192,192,28250,1,0,0:0:0:0:
|
||||
320,192,28250,1,0,0:0:0:0:
|
||||
448,192,28250,1,0,0:0:0:0:
|
||||
64,192,28500,1,0,0:0:0:0:
|
||||
192,192,28500,1,0,0:0:0:0:
|
||||
320,192,28500,1,0,0:0:0:0:
|
||||
448,192,28500,1,0,0:0:0:0:
|
||||
|
||||
// double-trills
|
||||
64,192,29500,1,0,0:0:0:0:
|
||||
192,192,29500,1,0,0:0:0:0:
|
||||
320,192,29625,1,0,0:0:0:0:
|
||||
448,192,29625,1,0,0:0:0:0:
|
||||
64,192,29750,1,0,0:0:0:0:
|
||||
192,192,29750,1,0,0:0:0:0:
|
||||
320,192,29875,1,0,0:0:0:0:
|
||||
448,192,29875,1,0,0:0:0:0:
|
||||
64,192,30000,1,0,0:0:0:0:
|
||||
192,192,30000,1,0,0:0:0:0:
|
||||
320,192,30125,1,0,0:0:0:0:
|
||||
448,192,30125,1,0,0:0:0:0:
|
||||
64,192,30250,1,0,0:0:0:0:
|
||||
192,192,30250,1,0,0:0:0:0:
|
||||
320,192,30375,1,0,0:0:0:0:
|
||||
448,192,30375,1,0,0:0:0:0:
|
||||
64,192,30500,1,0,0:0:0:0:
|
||||
192,192,30500,1,0,0:0:0:0:
|
@ -8,7 +8,7 @@ using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Configuration;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Input.Bindings;
|
||||
using osu.Game.Rulesets.Judgements;
|
||||
using osu.Game.Rulesets.Mania.UI.Components;
|
||||
@ -82,15 +82,15 @@ namespace osu.Game.Rulesets.Mania.UI
|
||||
|
||||
TopLevelContainer.Add(explosionContainer.CreateProxy());
|
||||
|
||||
Direction.BindValueChanged(d =>
|
||||
Direction.BindValueChanged(dir =>
|
||||
{
|
||||
hitTargetContainer.Padding = new MarginPadding
|
||||
{
|
||||
Top = d == ScrollingDirection.Up ? ManiaStage.HIT_TARGET_POSITION : 0,
|
||||
Bottom = d == ScrollingDirection.Down ? ManiaStage.HIT_TARGET_POSITION : 0,
|
||||
Top = dir.NewValue == ScrollingDirection.Up ? ManiaStage.HIT_TARGET_POSITION : 0,
|
||||
Bottom = dir.NewValue == ScrollingDirection.Down ? ManiaStage.HIT_TARGET_POSITION : 0,
|
||||
};
|
||||
|
||||
keyArea.Anchor = keyArea.Origin= d == ScrollingDirection.Up ? Anchor.TopLeft : Anchor.BottomLeft;
|
||||
keyArea.Anchor = keyArea.Origin = dir.NewValue == ScrollingDirection.Up ? Anchor.TopLeft : Anchor.BottomLeft;
|
||||
}, true);
|
||||
}
|
||||
|
||||
@ -156,7 +156,7 @@ namespace osu.Game.Rulesets.Mania.UI
|
||||
|
||||
internal void OnNewResult(DrawableHitObject judgedObject, JudgementResult result)
|
||||
{
|
||||
if (!result.IsHit || !judgedObject.DisplayResult || !DisplayJudgements)
|
||||
if (!result.IsHit || !judgedObject.DisplayResult || !DisplayJudgements.Value)
|
||||
return;
|
||||
|
||||
explosionContainer.Add(new HitExplosion(judgedObject)
|
||||
@ -167,7 +167,7 @@ namespace osu.Game.Rulesets.Mania.UI
|
||||
|
||||
public bool OnPressed(ManiaAction action)
|
||||
{
|
||||
if (action != Action)
|
||||
if (action != Action.Value)
|
||||
return false;
|
||||
|
||||
var nextObject =
|
||||
|
@ -2,7 +2,7 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Configuration;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Colour;
|
||||
@ -48,9 +48,9 @@ namespace osu.Game.Rulesets.Mania.UI.Components
|
||||
};
|
||||
|
||||
direction.BindTo(scrollingInfo.Direction);
|
||||
direction.BindValueChanged(direction =>
|
||||
direction.BindValueChanged(dir =>
|
||||
{
|
||||
backgroundOverlay.Anchor = backgroundOverlay.Origin = direction == ScrollingDirection.Up ? Anchor.TopLeft : Anchor.BottomLeft;
|
||||
backgroundOverlay.Anchor = backgroundOverlay.Origin = dir.NewValue == ScrollingDirection.Up ? Anchor.TopLeft : Anchor.BottomLeft;
|
||||
updateColours();
|
||||
}, true);
|
||||
}
|
||||
|
@ -2,7 +2,7 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Configuration;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
@ -49,9 +49,9 @@ namespace osu.Game.Rulesets.Mania.UI.Components
|
||||
private void load(IScrollingInfo scrollingInfo)
|
||||
{
|
||||
direction.BindTo(scrollingInfo.Direction);
|
||||
direction.BindValueChanged(direction =>
|
||||
direction.BindValueChanged(dir =>
|
||||
{
|
||||
Anchor anchor = direction == ScrollingDirection.Up ? Anchor.TopLeft : Anchor.BottomLeft;
|
||||
Anchor anchor = dir.NewValue == ScrollingDirection.Up ? Anchor.TopLeft : Anchor.BottomLeft;
|
||||
|
||||
hitTargetBar.Anchor = hitTargetBar.Origin = anchor;
|
||||
hitTargetLine.Anchor = hitTargetLine.Origin = anchor;
|
||||
|
@ -2,7 +2,7 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Configuration;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Colour;
|
||||
@ -64,11 +64,11 @@ namespace osu.Game.Rulesets.Mania.UI.Components
|
||||
};
|
||||
|
||||
direction.BindTo(scrollingInfo.Direction);
|
||||
direction.BindValueChanged(direction =>
|
||||
direction.BindValueChanged(dir =>
|
||||
{
|
||||
gradient.Colour = ColourInfo.GradientVertical(
|
||||
direction == ScrollingDirection.Up ? Color4.Black : Color4.Black.Opacity(0),
|
||||
direction == ScrollingDirection.Up ? Color4.Black.Opacity(0) : Color4.Black);
|
||||
dir.NewValue == ScrollingDirection.Up ? Color4.Black : Color4.Black.Opacity(0),
|
||||
dir.NewValue == ScrollingDirection.Up ? Color4.Black.Opacity(0) : Color4.Black);
|
||||
}, true);
|
||||
}
|
||||
|
||||
|
@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Mania.UI
|
||||
private void load()
|
||||
{
|
||||
if (JudgementText != null)
|
||||
JudgementText.TextSize = 25;
|
||||
JudgementText.Font = JudgementText.Font.With(size: 25);
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
|
@ -4,7 +4,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Configuration;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Input;
|
||||
@ -76,7 +76,7 @@ namespace osu.Game.Rulesets.Mania.UI
|
||||
BarLines.ForEach(Playfield.Add);
|
||||
|
||||
Config.BindWith(ManiaSetting.ScrollDirection, configDirection);
|
||||
configDirection.BindValueChanged(v => Direction.Value = (ScrollingDirection)v, true);
|
||||
configDirection.BindValueChanged(direction => Direction.Value = (ScrollingDirection)direction.NewValue, true);
|
||||
|
||||
Config.BindWith(ManiaSetting.ScrollTime, TimeRange);
|
||||
}
|
||||
|
@ -136,12 +136,12 @@ namespace osu.Game.Rulesets.Mania.UI
|
||||
AddColumn(column);
|
||||
}
|
||||
|
||||
Direction.BindValueChanged(d =>
|
||||
Direction.BindValueChanged(dir =>
|
||||
{
|
||||
barLineContainer.Padding = new MarginPadding
|
||||
{
|
||||
Top = d == ScrollingDirection.Up ? HIT_TARGET_POSITION : 0,
|
||||
Bottom = d == ScrollingDirection.Down ? HIT_TARGET_POSITION : 0,
|
||||
Top = dir.NewValue == ScrollingDirection.Up ? HIT_TARGET_POSITION : 0,
|
||||
Bottom = dir.NewValue == ScrollingDirection.Down ? HIT_TARGET_POSITION : 0,
|
||||
};
|
||||
}, true);
|
||||
}
|
||||
@ -185,7 +185,7 @@ namespace osu.Game.Rulesets.Mania.UI
|
||||
|
||||
internal void OnNewResult(DrawableHitObject judgedObject, JudgementResult result)
|
||||
{
|
||||
if (!judgedObject.DisplayResult || !DisplayJudgements)
|
||||
if (!judgedObject.DisplayResult || !DisplayJudgements.Value)
|
||||
return;
|
||||
|
||||
judgements.Clear();
|
||||
|
25
osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs
Normal file
25
osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs
Normal file
@ -0,0 +1,25 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using NUnit.Framework;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Difficulty;
|
||||
using osu.Game.Rulesets.Osu.Difficulty;
|
||||
using osu.Game.Tests.Beatmaps;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Tests
|
||||
{
|
||||
[TestFixture]
|
||||
public class OsuDifficultyCalculatorTest : DifficultyCalculatorTest
|
||||
{
|
||||
protected override string ResourceAssembly => "osu.Game.Rulesets.Osu";
|
||||
|
||||
[TestCase(6.931145117263422, "diffcalc-test")]
|
||||
public void Test(double expected, string name)
|
||||
=> base.Test(expected, name);
|
||||
|
||||
protected override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new OsuDifficultyCalculator(new OsuRuleset(), beatmap);
|
||||
|
||||
protected override Ruleset CreateRuleset() => new OsuRuleset();
|
||||
}
|
||||
}
|
36
osu.Game.Rulesets.Osu.Tests/TestCaseHitCircleLongCombo.cs
Normal file
36
osu.Game.Rulesets.Osu.Tests/TestCaseHitCircleLongCombo.cs
Normal file
@ -0,0 +1,36 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using NUnit.Framework;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Tests
|
||||
{
|
||||
[TestFixture]
|
||||
public class TestCaseHitCircleLongCombo : Game.Tests.Visual.TestCasePlayer
|
||||
{
|
||||
public TestCaseHitCircleLongCombo()
|
||||
: base(new OsuRuleset())
|
||||
{
|
||||
}
|
||||
|
||||
protected override IBeatmap CreateBeatmap(Ruleset ruleset)
|
||||
{
|
||||
var beatmap = new Beatmap
|
||||
{
|
||||
BeatmapInfo = new BeatmapInfo
|
||||
{
|
||||
BaseDifficulty = new BeatmapDifficulty { CircleSize = 6 },
|
||||
Ruleset = ruleset.RulesetInfo
|
||||
}
|
||||
};
|
||||
|
||||
for (int i = 0; i < 512; i++)
|
||||
beatmap.HitObjects.Add(new HitCircle { Position = new Vector2(256, 192), StartTime = i * 100 });
|
||||
|
||||
return beatmap;
|
||||
}
|
||||
}
|
||||
}
|
@ -16,6 +16,7 @@ using osuTK.Graphics;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Rulesets.Judgements;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
@ -313,7 +314,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
Origin = Anchor.Centre,
|
||||
Text = result.IsHit ? "Hit!" : "Miss!",
|
||||
Colour = result.IsHit ? Color4.Green : Color4.Red,
|
||||
TextSize = 30,
|
||||
Font = OsuFont.GetFont(size: 30),
|
||||
Position = osuObject.HitObject.StackedEndPosition + judgementOffsetDirection * new Vector2(0, 45)
|
||||
});
|
||||
|
||||
|
@ -2,7 +2,6 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Game.Rulesets.Difficulty;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
{
|
||||
@ -13,10 +12,5 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
public double ApproachRate;
|
||||
public double OverallDifficulty;
|
||||
public int MaxCombo;
|
||||
|
||||
public OsuDifficultyAttributes(Mod[] mods, double starRating)
|
||||
: base(mods, starRating)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,13 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Difficulty;
|
||||
using osu.Game.Rulesets.Difficulty.Preprocessing;
|
||||
using osu.Game.Rulesets.Difficulty.Skills;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Osu.Difficulty.Preprocessing;
|
||||
using osu.Game.Rulesets.Osu.Difficulty.Skills;
|
||||
@ -15,7 +18,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
{
|
||||
public class OsuDifficultyCalculator : DifficultyCalculator
|
||||
{
|
||||
private const int section_length = 400;
|
||||
private const double difficulty_multiplier = 0.0675;
|
||||
|
||||
public OsuDifficultyCalculator(Ruleset ruleset, WorkingBeatmap beatmap)
|
||||
@ -23,58 +25,27 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
{
|
||||
}
|
||||
|
||||
protected override DifficultyAttributes Calculate(IBeatmap beatmap, Mod[] mods, double timeRate)
|
||||
protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills, double clockRate)
|
||||
{
|
||||
if (!beatmap.HitObjects.Any())
|
||||
return new OsuDifficultyAttributes(mods, 0);
|
||||
|
||||
OsuDifficultyBeatmap difficultyBeatmap = new OsuDifficultyBeatmap(beatmap.HitObjects.Cast<OsuHitObject>().ToList(), timeRate);
|
||||
Skill[] skills =
|
||||
{
|
||||
new Aim(),
|
||||
new Speed()
|
||||
};
|
||||
|
||||
double sectionLength = section_length * timeRate;
|
||||
|
||||
// The first object doesn't generate a strain, so we begin with an incremented section end
|
||||
double currentSectionEnd = Math.Ceiling(beatmap.HitObjects.First().StartTime / sectionLength) * sectionLength;
|
||||
|
||||
foreach (OsuDifficultyHitObject h in difficultyBeatmap)
|
||||
{
|
||||
while (h.BaseObject.StartTime > currentSectionEnd)
|
||||
{
|
||||
foreach (Skill s in skills)
|
||||
{
|
||||
s.SaveCurrentPeak();
|
||||
s.StartNewSectionFrom(currentSectionEnd);
|
||||
}
|
||||
|
||||
currentSectionEnd += sectionLength;
|
||||
}
|
||||
|
||||
foreach (Skill s in skills)
|
||||
s.Process(h);
|
||||
}
|
||||
|
||||
// The peak strain will not be saved for the last section in the above loop
|
||||
foreach (Skill s in skills)
|
||||
s.SaveCurrentPeak();
|
||||
if (beatmap.HitObjects.Count == 0)
|
||||
return new OsuDifficultyAttributes { Mods = mods };
|
||||
|
||||
double aimRating = Math.Sqrt(skills[0].DifficultyValue()) * difficulty_multiplier;
|
||||
double speedRating = Math.Sqrt(skills[1].DifficultyValue()) * difficulty_multiplier;
|
||||
double starRating = aimRating + speedRating + Math.Abs(aimRating - speedRating) / 2;
|
||||
|
||||
// Todo: These int casts are temporary to achieve 1:1 results with osu!stable, and should be removed in the future
|
||||
double hitWindowGreat = (int)(beatmap.HitObjects.First().HitWindows.Great / 2) / timeRate;
|
||||
double preempt = (int)BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.ApproachRate, 1800, 1200, 450) / timeRate;
|
||||
double hitWindowGreat = (int)(beatmap.HitObjects.First().HitWindows.Great / 2) / clockRate;
|
||||
double preempt = (int)BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.ApproachRate, 1800, 1200, 450) / clockRate;
|
||||
|
||||
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);
|
||||
|
||||
return new OsuDifficultyAttributes(mods, starRating)
|
||||
return new OsuDifficultyAttributes
|
||||
{
|
||||
StarRating = starRating,
|
||||
Mods = mods,
|
||||
AimStrain = aimRating,
|
||||
SpeedStrain = speedRating,
|
||||
ApproachRate = preempt > 1200 ? (1800 - preempt) / 120 : (1200 - preempt) / 150 + 5,
|
||||
@ -83,6 +54,26 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
};
|
||||
}
|
||||
|
||||
protected override IEnumerable<DifficultyHitObject> CreateDifficultyHitObjects(IBeatmap beatmap, double clockRate)
|
||||
{
|
||||
// The first jump is formed by the first two hitobjects of the map.
|
||||
// If the map has less than two OsuHitObjects, the enumerator will not return anything.
|
||||
for (int i = 1; i < beatmap.HitObjects.Count; i++)
|
||||
{
|
||||
var lastLast = i > 1 ? beatmap.HitObjects[i - 2] : null;
|
||||
var last = beatmap.HitObjects[i - 1];
|
||||
var current = beatmap.HitObjects[i];
|
||||
|
||||
yield return new OsuDifficultyHitObject(current, lastLast, last, clockRate);
|
||||
}
|
||||
}
|
||||
|
||||
protected override Skill[] CreateSkills(IBeatmap beatmap) => new Skill[]
|
||||
{
|
||||
new Aim(),
|
||||
new Speed()
|
||||
};
|
||||
|
||||
protected override Mod[] DifficultyAdjustmentMods => new Mod[]
|
||||
{
|
||||
new OsuModDoubleTime(),
|
||||
|
@ -1,50 +0,0 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
|
||||
{
|
||||
/// <summary>
|
||||
/// An enumerable container wrapping <see cref="OsuHitObject"/> input as <see cref="OsuDifficultyHitObject"/>
|
||||
/// which contains extra data required for difficulty calculation.
|
||||
/// </summary>
|
||||
public class OsuDifficultyBeatmap : IEnumerable<OsuDifficultyHitObject>
|
||||
{
|
||||
private readonly IEnumerator<OsuDifficultyHitObject> difficultyObjects;
|
||||
|
||||
/// <summary>
|
||||
/// Creates an enumerator, which preprocesses a list of <see cref="OsuHitObject"/>s recieved as input, wrapping them as
|
||||
/// <see cref="OsuDifficultyHitObject"/> which contains extra data required for difficulty calculation.
|
||||
/// </summary>
|
||||
public OsuDifficultyBeatmap(List<OsuHitObject> objects, double timeRate)
|
||||
{
|
||||
// Sort OsuHitObjects by StartTime - they are not correctly ordered in some cases.
|
||||
// This should probably happen before the objects reach the difficulty calculator.
|
||||
difficultyObjects = createDifficultyObjectEnumerator(objects.OrderBy(h => h.StartTime).ToList(), timeRate);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns an enumerator that enumerates all <see cref="OsuDifficultyHitObject"/>s in the <see cref="OsuDifficultyBeatmap"/>.
|
||||
/// </summary>
|
||||
public IEnumerator<OsuDifficultyHitObject> GetEnumerator() => difficultyObjects;
|
||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||
|
||||
private IEnumerator<OsuDifficultyHitObject> createDifficultyObjectEnumerator(List<OsuHitObject> objects, double timeRate)
|
||||
{
|
||||
// The first jump is formed by the first two hitobjects of the map.
|
||||
// If the map has less than two OsuHitObjects, the enumerator will not return anything.
|
||||
for (int i = 1; i < objects.Count; i++)
|
||||
{
|
||||
var lastLast = i > 1 ? objects[i - 2] : null;
|
||||
var last = objects[i - 1];
|
||||
var current = objects[i];
|
||||
|
||||
yield return new OsuDifficultyHitObject(lastLast, last, current, timeRate);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,24 +1,20 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using osu.Game.Rulesets.Difficulty.Preprocessing;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
|
||||
{
|
||||
/// <summary>
|
||||
/// A wrapper around <see cref="OsuHitObject"/> extending it with additional data required for difficulty calculation.
|
||||
/// </summary>
|
||||
public class OsuDifficultyHitObject
|
||||
public class OsuDifficultyHitObject : DifficultyHitObject
|
||||
{
|
||||
private const int normalized_radius = 52;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="OsuHitObject"/> this <see cref="OsuDifficultyHitObject"/> refers to.
|
||||
/// </summary>
|
||||
public OsuHitObject BaseObject { get; }
|
||||
protected new OsuHitObject BaseObject => (OsuHitObject)base.BaseObject;
|
||||
|
||||
/// <summary>
|
||||
/// Normalized distance from the end position of the previous <see cref="OsuDifficultyHitObject"/> to the start position of this <see cref="OsuDifficultyHitObject"/>.
|
||||
@ -30,40 +26,30 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
|
||||
/// </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; }
|
||||
|
||||
/// <summary>
|
||||
/// Angle the player has to take to hit this <see cref="OsuDifficultyHitObject"/>.
|
||||
/// Calculated as the angle between the circles (current-2, current-1, current).
|
||||
/// </summary>
|
||||
public double? Angle { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Milliseconds elapsed since the start time of the previous <see cref="OsuDifficultyHitObject"/>, with a minimum of 50ms.
|
||||
/// </summary>
|
||||
public readonly double StrainTime;
|
||||
|
||||
private readonly OsuHitObject lastLastObject;
|
||||
private readonly OsuHitObject lastObject;
|
||||
private readonly double timeRate;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the object calculating extra data required for difficulty calculation.
|
||||
/// </summary>
|
||||
public OsuDifficultyHitObject(OsuHitObject lastLastObject, OsuHitObject lastObject, OsuHitObject currentObject, double timeRate)
|
||||
public OsuDifficultyHitObject(HitObject hitObject, HitObject lastLastObject, HitObject lastObject, double clockRate)
|
||||
: base(hitObject, lastObject, clockRate)
|
||||
{
|
||||
this.lastLastObject = lastLastObject;
|
||||
this.lastObject = lastObject;
|
||||
this.timeRate = timeRate;
|
||||
|
||||
BaseObject = currentObject;
|
||||
this.lastLastObject = (OsuHitObject)lastLastObject;
|
||||
this.lastObject = (OsuHitObject)lastObject;
|
||||
|
||||
setDistances();
|
||||
setTimingValues();
|
||||
// Calculate angle here
|
||||
|
||||
// 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 setDistances()
|
||||
@ -102,14 +88,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
|
||||
}
|
||||
}
|
||||
|
||||
private void setTimingValues()
|
||||
{
|
||||
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)
|
||||
{
|
||||
if (slider.LazyEndPosition != null)
|
||||
|
@ -2,7 +2,10 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using osu.Game.Rulesets.Difficulty.Preprocessing;
|
||||
using osu.Game.Rulesets.Difficulty.Skills;
|
||||
using osu.Game.Rulesets.Osu.Difficulty.Preprocessing;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Difficulty.Skills
|
||||
{
|
||||
@ -17,33 +20,40 @@ 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)
|
||||
protected override double StrainValueOf(DifficultyHitObject current)
|
||||
{
|
||||
if (current.BaseObject is Spinner)
|
||||
return 0;
|
||||
|
||||
var osuCurrent = (OsuDifficultyHitObject)current;
|
||||
|
||||
double result = 0;
|
||||
|
||||
const double scale = 90;
|
||||
|
||||
double applyDiminishingExp(double val) => Math.Pow(val, 0.99);
|
||||
|
||||
if (Previous.Count > 0)
|
||||
{
|
||||
if (current.Angle != null && current.Angle.Value > angle_bonus_begin)
|
||||
var osuPrevious = (OsuDifficultyHitObject)Previous[0];
|
||||
|
||||
if (osuCurrent.Angle != null && osuCurrent.Angle.Value > angle_bonus_begin)
|
||||
{
|
||||
const double scale = 90;
|
||||
|
||||
var angleBonus = Math.Sqrt(
|
||||
Math.Max(Previous[0].JumpDistance - scale, 0)
|
||||
* Math.Pow(Math.Sin(current.Angle.Value - angle_bonus_begin), 2)
|
||||
* Math.Max(current.JumpDistance - scale, 0));
|
||||
result = 1.5 * applyDiminishingExp(Math.Max(0, angleBonus)) / Math.Max(timing_threshold, Previous[0].StrainTime);
|
||||
Math.Max(osuPrevious.JumpDistance - scale, 0)
|
||||
* Math.Pow(Math.Sin(osuCurrent.Angle.Value - angle_bonus_begin), 2)
|
||||
* Math.Max(osuCurrent.JumpDistance - scale, 0));
|
||||
result = 1.5 * applyDiminishingExp(Math.Max(0, angleBonus)) / Math.Max(timing_threshold, osuPrevious.StrainTime);
|
||||
}
|
||||
}
|
||||
|
||||
double jumpDistanceExp = applyDiminishingExp(current.JumpDistance);
|
||||
double travelDistanceExp = applyDiminishingExp(current.TravelDistance);
|
||||
double jumpDistanceExp = applyDiminishingExp(osuCurrent.JumpDistance);
|
||||
double travelDistanceExp = applyDiminishingExp(osuCurrent.TravelDistance);
|
||||
|
||||
return Math.Max(
|
||||
result + (jumpDistanceExp + travelDistanceExp + Math.Sqrt(travelDistanceExp * jumpDistanceExp)) / Math.Max(current.StrainTime, timing_threshold),
|
||||
(Math.Sqrt(travelDistanceExp * jumpDistanceExp) + jumpDistanceExp + travelDistanceExp) / current.StrainTime
|
||||
result + (jumpDistanceExp + travelDistanceExp + Math.Sqrt(travelDistanceExp * jumpDistanceExp)) / Math.Max(osuCurrent.StrainTime, timing_threshold),
|
||||
(Math.Sqrt(travelDistanceExp * jumpDistanceExp) + jumpDistanceExp + travelDistanceExp) / osuCurrent.StrainTime
|
||||
);
|
||||
}
|
||||
|
||||
private double applyDiminishingExp(double val) => Math.Pow(val, 0.99);
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,10 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using osu.Game.Rulesets.Difficulty.Preprocessing;
|
||||
using osu.Game.Rulesets.Difficulty.Skills;
|
||||
using osu.Game.Rulesets.Osu.Difficulty.Preprocessing;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Difficulty.Skills
|
||||
{
|
||||
@ -11,6 +14,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
|
||||
/// </summary>
|
||||
public class Speed : Skill
|
||||
{
|
||||
private const double single_spacing_threshold = 125;
|
||||
|
||||
private const double angle_bonus_begin = 5 * Math.PI / 6;
|
||||
private const double pi_over_4 = Math.PI / 4;
|
||||
private const double pi_over_2 = Math.PI / 2;
|
||||
@ -22,9 +27,14 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
|
||||
private const double max_speed_bonus = 45; // ~330BPM
|
||||
private const double speed_balancing_factor = 40;
|
||||
|
||||
protected override double StrainValueOf(OsuDifficultyHitObject current)
|
||||
protected override double StrainValueOf(DifficultyHitObject current)
|
||||
{
|
||||
double distance = Math.Min(SINGLE_SPACING_THRESHOLD, current.TravelDistance + current.JumpDistance);
|
||||
if (current.BaseObject is Spinner)
|
||||
return 0;
|
||||
|
||||
var osuCurrent = (OsuDifficultyHitObject)current;
|
||||
|
||||
double distance = Math.Min(single_spacing_threshold, osuCurrent.TravelDistance + osuCurrent.JumpDistance);
|
||||
double deltaTime = Math.Max(max_speed_bonus, current.DeltaTime);
|
||||
|
||||
double speedBonus = 1.0;
|
||||
@ -32,20 +42,20 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
|
||||
speedBonus = 1 + Math.Pow((min_speed_bonus - deltaTime) / speed_balancing_factor, 2);
|
||||
|
||||
double angleBonus = 1.0;
|
||||
if (current.Angle != null && current.Angle.Value < angle_bonus_begin)
|
||||
if (osuCurrent.Angle != null && osuCurrent.Angle.Value < angle_bonus_begin)
|
||||
{
|
||||
angleBonus = 1 + Math.Pow(Math.Sin(1.5 * (angle_bonus_begin - current.Angle.Value)), 2) / 3.57;
|
||||
if (current.Angle.Value < pi_over_2)
|
||||
angleBonus = 1 + Math.Pow(Math.Sin(1.5 * (angle_bonus_begin - osuCurrent.Angle.Value)), 2) / 3.57;
|
||||
if (osuCurrent.Angle.Value < pi_over_2)
|
||||
{
|
||||
angleBonus = 1.28;
|
||||
if (distance < 90 && current.Angle.Value < pi_over_4)
|
||||
if (distance < 90 && osuCurrent.Angle.Value < pi_over_4)
|
||||
angleBonus += (1 - angleBonus) * Math.Min((90 - distance) / 10, 1);
|
||||
else if (distance < 90)
|
||||
angleBonus += (1 - angleBonus) * Math.Min((90 - distance) / 10, 1) * Math.Sin((pi_over_2 - current.Angle.Value) / pi_over_4);
|
||||
angleBonus += (1 - angleBonus) * Math.Min((90 - distance) / 10, 1) * Math.Sin((pi_over_2 - osuCurrent.Angle.Value) / pi_over_4);
|
||||
}
|
||||
}
|
||||
|
||||
return (1 + (speedBonus - 1) * 0.75) * angleBonus * (0.95 + speedBonus * Math.Pow(distance / SINGLE_SPACING_THRESHOLD, 3.5)) / current.StrainTime;
|
||||
return (1 + (speedBonus - 1) * 0.75) * angleBonus * (0.95 + speedBonus * Math.Pow(distance / single_spacing_threshold, 3.5)) / osuCurrent.StrainTime;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -34,7 +34,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components
|
||||
|
||||
PositionBindable.BindValueChanged(_ => UpdatePosition(), true);
|
||||
StackHeightBindable.BindValueChanged(_ => UpdatePosition());
|
||||
ScaleBindable.BindValueChanged(v => Scale = new Vector2(v), true);
|
||||
ScaleBindable.BindValueChanged(scale => Scale = new Vector2(scale.NewValue), true);
|
||||
}
|
||||
|
||||
protected virtual void UpdatePosition() => Position = hitCircle.StackedPosition;
|
||||
|
@ -2,7 +2,7 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Configuration;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osuTK;
|
||||
|
@ -2,7 +2,7 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Configuration;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
|
||||
|
@ -34,7 +34,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
||||
body.BorderColour = colours.Yellow;
|
||||
|
||||
PositionBindable.BindValueChanged(_ => updatePosition(), true);
|
||||
ScaleBindable.BindValueChanged(v => body.PathWidth = v * 64, true);
|
||||
ScaleBindable.BindValueChanged(scale => body.PathWidth = scale.NewValue * 64, true);
|
||||
}
|
||||
|
||||
private void updatePosition() => Position = slider.StackedPosition;
|
||||
|
@ -2,7 +2,7 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Configuration;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
|
@ -55,7 +55,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners.Components
|
||||
|
||||
PositionBindable.BindValueChanged(_ => updatePosition(), true);
|
||||
StackHeightBindable.BindValueChanged(_ => updatePosition());
|
||||
ScaleBindable.BindValueChanged(v => ring.Scale = new Vector2(v), true);
|
||||
ScaleBindable.BindValueChanged(scale => ring.Scale = new Vector2(scale.NewValue), true);
|
||||
}
|
||||
|
||||
private void updatePosition() => Position = spinner.Position;
|
||||
|
@ -39,7 +39,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
|
||||
public void ApplyToScoreProcessor(ScoreProcessor scoreProcessor)
|
||||
{
|
||||
scoreProcessor.Health.ValueChanged += val => { blinds.AnimateClosedness((float)val); };
|
||||
scoreProcessor.Health.ValueChanged += health => { blinds.AnimateClosedness((float)health.NewValue); };
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -1,6 +1,7 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Input;
|
||||
using osu.Framework.Input.Events;
|
||||
@ -41,9 +42,9 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
return default_flashlight_size;
|
||||
}
|
||||
|
||||
protected override void OnComboChange(int newCombo)
|
||||
protected override void OnComboChange(ValueChangedEvent<int> e)
|
||||
{
|
||||
this.TransformTo(nameof(FlashlightSize), new Vector2(0, getSizeFor(newCombo)), FLASHLIGHT_FADE_DURATION);
|
||||
this.TransformTo(nameof(FlashlightSize), new Vector2(0, getSizeFor(e.NewValue)), FLASHLIGHT_FADE_DURATION);
|
||||
}
|
||||
|
||||
protected override string FragmentShader => "CircularFlashlight";
|
||||
|
74
osu.Game.Rulesets.Osu/Mods/OsuModGrow.cs
Normal file
74
osu.Game.Rulesets.Osu/Mods/OsuModGrow.cs
Normal file
@ -0,0 +1,74 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
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 osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Mods
|
||||
{
|
||||
internal class OsuModGrow : Mod, IApplicableToDrawableHitObjects
|
||||
{
|
||||
public override string Name => "Grow";
|
||||
|
||||
public override string Acronym => "GR";
|
||||
|
||||
public override FontAwesome Icon => FontAwesome.fa_arrows_v;
|
||||
|
||||
public override ModType Type => ModType.Fun;
|
||||
|
||||
public override string Description => "Hit them at the right size!";
|
||||
|
||||
public override double ScoreMultiplier => 1;
|
||||
|
||||
public void ApplyToDrawableHitObjects(IEnumerable<DrawableHitObject> drawables)
|
||||
{
|
||||
foreach (var drawable in drawables)
|
||||
{
|
||||
switch (drawable)
|
||||
{
|
||||
case DrawableSpinner _:
|
||||
continue;
|
||||
default:
|
||||
drawable.ApplyCustomUpdateState += ApplyCustomState;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void ApplyCustomState(DrawableHitObject drawable, ArmedState state)
|
||||
{
|
||||
var h = (OsuHitObject)drawable.HitObject;
|
||||
|
||||
// apply grow effect
|
||||
switch (drawable)
|
||||
{
|
||||
case DrawableSliderHead _:
|
||||
case DrawableSliderTail _:
|
||||
// special cases we should *not* be scaling.
|
||||
break;
|
||||
case DrawableSlider _:
|
||||
case DrawableHitCircle _:
|
||||
{
|
||||
using (drawable.BeginAbsoluteSequence(h.StartTime - h.TimePreempt, true))
|
||||
drawable.ScaleTo(0.5f).Then().ScaleTo(1, h.TimePreempt, Easing.OutSine);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// remove approach circles
|
||||
switch (drawable)
|
||||
{
|
||||
case DrawableHitCircle circle:
|
||||
// we don't want to see the approach circle
|
||||
using (circle.BeginAbsoluteSequence(h.StartTime - h.TimePreempt, true))
|
||||
circle.ApproachCircle.Hide();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -3,8 +3,9 @@
|
||||
|
||||
using System;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Configuration;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces;
|
||||
using osuTK;
|
||||
@ -27,15 +28,30 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
private readonly IBindable<int> stackHeightBindable = new Bindable<int>();
|
||||
private readonly IBindable<float> scaleBindable = new Bindable<float>();
|
||||
|
||||
private readonly Container explodeContainer;
|
||||
|
||||
private readonly Container scaleContainer;
|
||||
|
||||
public DrawableHitCircle(HitCircle h)
|
||||
: base(h)
|
||||
{
|
||||
Origin = Anchor.Centre;
|
||||
|
||||
Position = HitObject.StackedPosition;
|
||||
Scale = new Vector2(h.Scale);
|
||||
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
scaleContainer = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Origin = Anchor.Centre,
|
||||
Anchor = Anchor.Centre,
|
||||
Child = explodeContainer = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Origin = Anchor.Centre,
|
||||
Anchor = Anchor.Centre,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
glow = new GlowPiece(),
|
||||
circle = new CirclePiece
|
||||
@ -61,6 +77,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
Alpha = 0,
|
||||
Scale = new Vector2(4),
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
//may not be so correct
|
||||
@ -72,7 +91,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
{
|
||||
positionBindable.BindValueChanged(_ => Position = HitObject.StackedPosition);
|
||||
stackHeightBindable.BindValueChanged(_ => Position = HitObject.StackedPosition);
|
||||
scaleBindable.BindValueChanged(v => Scale = new Vector2(v));
|
||||
scaleBindable.BindValueChanged(scale => scaleContainer.Scale = new Vector2(scale.NewValue), true);
|
||||
|
||||
positionBindable.BindTo(HitObject.PositionBindable);
|
||||
stackHeightBindable.BindTo(HitObject.StackHeightBindable);
|
||||
@ -156,8 +175,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
circle.FadeOut();
|
||||
number.FadeOut();
|
||||
|
||||
this.FadeOut(800)
|
||||
.ScaleTo(Scale * 1.5f, 400, Easing.OutQuad);
|
||||
this.FadeOut(800);
|
||||
explodeContainer.ScaleTo(1.5f, 400, Easing.OutQuad);
|
||||
}
|
||||
|
||||
Expire();
|
||||
|
@ -8,7 +8,7 @@ using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Configuration;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
@ -99,10 +99,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
config.BindWith(OsuSetting.SnakingOutSliders, Body.SnakingOut);
|
||||
|
||||
positionBindable.BindValueChanged(_ => Position = HitObject.StackedPosition);
|
||||
scaleBindable.BindValueChanged(v =>
|
||||
scaleBindable.BindValueChanged(scale =>
|
||||
{
|
||||
Body.PathWidth = HitObject.Scale * 64;
|
||||
Ball.Scale = new Vector2(HitObject.Scale);
|
||||
Body.PathWidth = scale.NewValue * 64;
|
||||
Ball.Scale = new Vector2(scale.NewValue);
|
||||
});
|
||||
|
||||
positionBindable.BindTo(HitObject.PositionBindable);
|
||||
|
@ -3,7 +3,7 @@
|
||||
|
||||
using System;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Configuration;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
using osuTK;
|
||||
|
@ -1,7 +1,7 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Configuration;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
|
@ -11,7 +11,7 @@ using osuTK.Graphics;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Configuration;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Game.Screens.Ranking;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
|
||||
@ -130,7 +130,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
circle.Colour = colours.BlueDark;
|
||||
glow.Colour = colours.BlueDark;
|
||||
|
||||
positionBindable.BindValueChanged(v => Position = v);
|
||||
positionBindable.BindValueChanged(pos => Position = pos.NewValue);
|
||||
positionBindable.BindTo(HitObject.PositionBindable);
|
||||
}
|
||||
|
||||
|
@ -7,6 +7,7 @@ using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osuTK.Graphics;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Skinning;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
|
||||
@ -42,9 +43,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
|
||||
}, s => s.GetTexture("Play/osu/hitcircle") == null),
|
||||
number = new SkinnableSpriteText("Play/osu/number-text", _ => new OsuSpriteText
|
||||
{
|
||||
Font = @"Venera",
|
||||
Font = OsuFont.Numeric.With(size: 40),
|
||||
UseFullGlyphHeight = false,
|
||||
TextSize = 40,
|
||||
}, restrictSize: false)
|
||||
{
|
||||
Text = @"1"
|
||||
|
@ -4,7 +4,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Configuration;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
using osuTK;
|
||||
|
||||
@ -54,18 +54,18 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
|
||||
var spanProgress = slider.ProgressAt(completionProgress);
|
||||
|
||||
double start = 0;
|
||||
double end = SnakingIn ? MathHelper.Clamp((Time.Current - (slider.StartTime - slider.TimePreempt)) / slider.TimeFadeIn, 0, 1) : 1;
|
||||
double end = SnakingIn.Value ? 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;
|
||||
end = SnakingOut.Value ? spanProgress : 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
start = SnakingOut ? spanProgress : 0;
|
||||
start = SnakingOut.Value ? spanProgress : 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -6,6 +6,7 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
|
||||
@ -23,16 +24,14 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre,
|
||||
Text = @"0",
|
||||
Font = @"Venera",
|
||||
TextSize = 24
|
||||
Font = OsuFont.Numeric.With(size: 24)
|
||||
},
|
||||
new OsuSpriteText
|
||||
{
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre,
|
||||
Text = @"SPINS PER MINUTE",
|
||||
Font = @"Venera",
|
||||
TextSize = 12,
|
||||
Font = OsuFont.Numeric.With(size: 12),
|
||||
Y = 30
|
||||
}
|
||||
};
|
||||
|
@ -1,7 +1,7 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Configuration;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osuTK;
|
||||
@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Osu.Objects
|
||||
|
||||
public virtual Vector2 Position
|
||||
{
|
||||
get => PositionBindable;
|
||||
get => PositionBindable.Value;
|
||||
set => PositionBindable.Value = value;
|
||||
}
|
||||
|
||||
@ -38,7 +38,7 @@ namespace osu.Game.Rulesets.Osu.Objects
|
||||
|
||||
public int StackHeight
|
||||
{
|
||||
get => StackHeightBindable;
|
||||
get => StackHeightBindable.Value;
|
||||
set => StackHeightBindable.Value = value;
|
||||
}
|
||||
|
||||
@ -50,7 +50,7 @@ namespace osu.Game.Rulesets.Osu.Objects
|
||||
|
||||
public float Scale
|
||||
{
|
||||
get => ScaleBindable;
|
||||
get => ScaleBindable.Value;
|
||||
set => ScaleBindable.Value = value;
|
||||
}
|
||||
|
||||
|
@ -7,8 +7,8 @@ using osu.Game.Rulesets.Objects.Types;
|
||||
using System.Collections.Generic;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using System.Linq;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Caching;
|
||||
using osu.Framework.Configuration;
|
||||
using osu.Game.Audio;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
|
@ -1,7 +1,7 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Configuration;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Game.Rulesets.Judgements;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Osu.Judgements;
|
||||
|
@ -124,6 +124,7 @@ namespace osu.Game.Rulesets.Osu
|
||||
return new Mod[] {
|
||||
new OsuModTransform(),
|
||||
new OsuModWiggle(),
|
||||
new OsuModGrow()
|
||||
};
|
||||
default:
|
||||
return new Mod[] { };
|
||||
|
@ -11,9 +11,9 @@ using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Replays
|
||||
{
|
||||
public class OsuReplayInputHandler : FramedReplayInputHandler<OsuReplayFrame>
|
||||
public class OsuFramedReplayInputHandler : FramedReplayInputHandler<OsuReplayFrame>
|
||||
{
|
||||
public OsuReplayInputHandler(Replay replay)
|
||||
public OsuFramedReplayInputHandler(Replay replay)
|
||||
: base(replay)
|
||||
{
|
||||
}
|
@ -0,0 +1,179 @@
|
||||
osu file format v14
|
||||
|
||||
[General]
|
||||
StackLeniency: 0.3
|
||||
Mode: 0
|
||||
|
||||
[Difficulty]
|
||||
CircleSize:4
|
||||
OverallDifficulty:7
|
||||
ApproachRate:8.3
|
||||
SliderMultiplier:1.6
|
||||
SliderTickRate:1
|
||||
|
||||
[TimingPoints]
|
||||
500,500,4,2,1,50,1,0
|
||||
62500,-500,4,2,1,50,0,0
|
||||
71000,-100,4,2,1,50,0,0
|
||||
|
||||
[HitObjects]
|
||||
// Circles spaced 1 beat apart, with increasing jump distance
|
||||
126,112,500,5,0,0:0:0:0:
|
||||
130,155,1000,1,0,0:0:0:0:
|
||||
131,269,1500,1,0,0:0:0:0:
|
||||
341,269,2000,1,0,0:0:0:0:
|
||||
113,95,2500,1,0,0:0:0:0:
|
||||
|
||||
// Circles spaced 1/2 beat apart, with increasing jump distance
|
||||
108,104,3500,5,0,0:0:0:0:
|
||||
110,145,3750,1,0,0:0:0:0:
|
||||
115,262,4000,1,0,0:0:0:0:
|
||||
285,265,4250,1,0,0:0:0:0:
|
||||
458,48,4500,1,0,0:0:0:0:
|
||||
35,199,4750,1,0,0:0:0:0:
|
||||
251,340,5000,1,0,0:0:0:0:
|
||||
20,352,5250,1,0,0:0:0:0:
|
||||
426,62,5500,1,0,0:0:0:0:
|
||||
|
||||
// Circles spaced 1/4 beat apart, with increasing jump distances
|
||||
211,138,6500,5,0,0:0:0:0:
|
||||
99,256,6625,1,0,0:0:0:0:
|
||||
68,129,6750,1,0,0:0:0:0:
|
||||
371,340,6875,1,0,0:0:0:0:
|
||||
241,219,7000,1,0,0:0:0:0:
|
||||
252,148,7125,1,0,0:0:0:0:
|
||||
434,97,7250,1,0,0:0:0:0:
|
||||
40,38,7375,1,0,0:0:0:0:
|
||||
114,334,7500,1,0,0:0:0:0:
|
||||
301,19,7625,1,0,0:0:0:0:
|
||||
441,241,7750,1,0,0:0:0:0:
|
||||
121,91,7875,1,0,0:0:0:0:
|
||||
270,384,8000,1,0,0:0:0:0:
|
||||
488,92,8125,1,0,0:0:0:0:
|
||||
332,82,8250,1,0,0:0:0:0:
|
||||
108,240,8375,1,0,0:0:0:0:
|
||||
281,268,8500,1,0,0:0:0:0:
|
||||
|
||||
// Constant spaced circles spaced 1/2 beat apart, small jump distances, changing angles
|
||||
252,191,9500,5,0,0:0:0:0:
|
||||
356,191,9750,1,0,0:0:0:0:
|
||||
311,268,10000,1,0,0:0:0:0:
|
||||
190,270,10250,1,0,0:0:0:0:
|
||||
107,199,10500,1,0,0:0:0:0:
|
||||
172,105,10750,1,0,0:0:0:0:
|
||||
297,102,11000,1,0,0:0:0:0:
|
||||
373,178,11250,1,0,0:0:0:0:
|
||||
252,195,11500,1,0,0:0:0:0:
|
||||
|
||||
// Constant spaced circles spaced 1/2 beat apart, large jump distances, changing angles
|
||||
140,187,12500,5,0,0:0:0:0:
|
||||
451,331,12750,1,0,0:0:0:0:
|
||||
46,338,13000,1,0,0:0:0:0:
|
||||
204,50,13250,1,0,0:0:0:0:
|
||||
464,162,13500,1,0,0:0:0:0:
|
||||
252,346,13750,1,0,0:0:0:0:
|
||||
13,175,14000,1,0,0:0:0:0:
|
||||
488,181,14250,1,0,0:0:0:0:
|
||||
251,187,14500,1,0,0:0:0:0:
|
||||
|
||||
// Constant spaced circles spaced 1/4 beat apart, small jump distances, changing angles
|
||||
188,192,15500,5,0,0:0:0:0:
|
||||
298,194,15625,1,0,0:0:0:0:
|
||||
317,84,15750,1,0,0:0:0:0:
|
||||
185,85,15875,1,0,0:0:0:0:
|
||||
77,200,16000,1,0,0:0:0:0:
|
||||
184,303,16125,1,0,0:0:0:0:
|
||||
295,225,16250,1,0,0:0:0:0:
|
||||
300,84,16375,1,0,0:0:0:0:
|
||||
144,82,16500,1,0,0:0:0:0:
|
||||
141,215,16625,1,0,0:0:0:0:
|
||||
314,184,16750,1,0,0:0:0:0:
|
||||
188,192,16875,1,0,0:0:0:0:
|
||||
188,192,17000,1,0,0:0:0:0:
|
||||
|
||||
// Constant spaced circles spaced 1/4 beat apart, large jump distances, changing angles
|
||||
97,192,18000,5,0,0:0:0:0:
|
||||
336,38,18125,1,0,0:0:0:0:
|
||||
440,322,18250,1,0,0:0:0:0:
|
||||
39,331,18375,1,0,0:0:0:0:
|
||||
98,39,18500,1,0,0:0:0:0:
|
||||
460,179,18625,1,0,0:0:0:0:
|
||||
245,338,18750,1,0,0:0:0:0:
|
||||
12,184,18875,1,0,0:0:0:0:
|
||||
250,41,19000,1,0,0:0:0:0:
|
||||
265,193,19125,1,0,0:0:0:0:
|
||||
486,22,19250,1,0,0:0:0:0:
|
||||
411,205,19375,1,0,0:0:0:0:
|
||||
107,198,19500,1,0,0:0:0:0:
|
||||
|
||||
// Short sliders spaced 1 beat apart
|
||||
28,108,20500,2,0,L|196:107,1,160
|
||||
25,177,21500,2,0,L|193:176,1,160
|
||||
26,308,22500,2,0,L|194:307,1,160
|
||||
320,89,23500,2,0,L|488:88,1,160
|
||||
|
||||
// Short sliders spaced 1/2 beat apart
|
||||
28,108,25000,6,0,L|196:107,1,160
|
||||
27,173,25750,2,0,L|195:172,1,160
|
||||
25,292,26500,2,0,L|193:291,1,160
|
||||
340,213,27250,2,0,L|508:212,1,160
|
||||
21,44,28000,2,0,L|189:43,1,160
|
||||
|
||||
// Short sliders spaced 1/4 beat apart
|
||||
28,108,29500,6,0,L|196:107,1,160
|
||||
30,169,30125,2,0,L|198:168,1,160
|
||||
35,282,30750,2,0,L|203:281,1,160
|
||||
327,286,31375,2,0,L|495:285,1,160
|
||||
51,61,32000,2,0,L|219:60,1,160
|
||||
|
||||
// Large, medium-paced slider shapes
|
||||
// PerfectCurve
|
||||
66,86,33500,6,0,P|246:348|427:44,1,800
|
||||
66,86,36500,2,0,P|246:348|427:44,1,800
|
||||
66,86,39500,2,0,P|246:348|427:44,1,800
|
||||
// Linear
|
||||
66,72,42500,2,0,B|419:65|419:65|66:316|66:316|426:318,1,1120
|
||||
66,72,46500,2,0,B|419:65|419:65|66:316|66:316|426:318,1,1120
|
||||
66,72,50500,2,0,B|419:65|419:65|66:316|66:316|426:318,1,1120
|
||||
// Bezier
|
||||
76,287,54500,2,0,B|440:325|138:128|470:302|500:30|130:85|66:82,1,640
|
||||
76,287,57000,2,0,B|440:325|138:128|470:302|500:30|130:85|66:82,1,640
|
||||
76,287,59500,2,0,B|440:325|138:128|470:302|500:30|130:85|66:82,1,640
|
||||
|
||||
// Large slow slider with many ticks
|
||||
81,170,62500,6,0,P|263:78|168:268,1,480
|
||||
|
||||
// Fast slider with many repeats
|
||||
102,152,71000,6,0,L|175:153,18,64
|
||||
|
||||
// Slider-circle combos, spaced 1/2 beat apart
|
||||
106,204,75500,6,0,P|275:33|171:304,1,800
|
||||
255,179,78250,1,0,0:0:0:0:
|
||||
106,204,78500,2,0,P|275:33|171:304,1,800
|
||||
255,179,81250,1,0,0:0:0:0:
|
||||
106,204,81500,2,0,P|275:33|171:304,1,800
|
||||
|
||||
// Circle-spinner combos, spaced 1/2 beat apart
|
||||
82,69,85000,5,0,0:0:0:0:
|
||||
256,192,85250,8,0,86000,0:0:0:0:
|
||||
384,189,86250,5,0,0:0:0:0:
|
||||
256,192,86500,12,0,87000,0:0:0:0:
|
||||
|
||||
// Spinner-spinner combos, spaced 1/2 beat apart
|
||||
256,192,88000,12,0,89000,0:0:0:0:
|
||||
256,192,89250,12,0,90250,0:0:0:0:
|
||||
256,192,90500,12,0,91500,0:0:0:0:
|
||||
256,192,91750,12,0,92750,0:0:0:0:
|
||||
256,192,93000,12,0,94000,0:0:0:0:
|
||||
|
||||
// Slider-spinner combos, spaced 1/2 beat apart
|
||||
49,89,95000,6,0,L|214:87,1,160
|
||||
256,192,95625,12,0,96500,0:0:0:0:
|
||||
12,299,96625,6,0,L|177:297,1,160
|
||||
256,192,97250,12,0,98125,0:0:0:0:
|
||||
295,107,98250,6,0,L|460:105,1,160
|
||||
256,192,98875,12,0,99750,0:0:0:0:
|
||||
279,325,99875,6,0,L|444:323,1,160
|
||||
256,192,100500,12,0,101375,0:0:0:0:
|
||||
197,197,101500,6,0,L|362:195,1,160
|
||||
256,192,102125,12,0,103000,0:0:0:0:
|
@ -2,7 +2,7 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Configuration;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
@ -112,7 +112,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuConfigManager config, IBindableBeatmap beatmap)
|
||||
private void load(OsuConfigManager config, IBindable<WorkingBeatmap> beatmap)
|
||||
{
|
||||
InternalChild = expandTarget = new Container
|
||||
{
|
||||
@ -183,13 +183,13 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
|
||||
};
|
||||
|
||||
this.beatmap.BindTo(beatmap);
|
||||
this.beatmap.ValueChanged += v => calculateScale();
|
||||
this.beatmap.ValueChanged += _ => calculateScale();
|
||||
|
||||
cursorScale = config.GetBindable<double>(OsuSetting.GameplayCursorSize);
|
||||
cursorScale.ValueChanged += v => calculateScale();
|
||||
cursorScale.ValueChanged += _ => calculateScale();
|
||||
|
||||
autoCursorScale = config.GetBindable<bool>(OsuSetting.AutoCursorSize);
|
||||
autoCursorScale.ValueChanged += v => calculateScale();
|
||||
autoCursorScale.ValueChanged += _ => calculateScale();
|
||||
|
||||
calculateScale();
|
||||
}
|
||||
@ -198,7 +198,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
|
||||
{
|
||||
float scale = (float)cursorScale.Value;
|
||||
|
||||
if (autoCursorScale && beatmap.Value != null)
|
||||
if (autoCursorScale.Value && beatmap.Value != null)
|
||||
{
|
||||
// if we have a beatmap available, let's get its circle size to figure out an automatic cursor scale modifier.
|
||||
scale *= (float)(1 - 0.7 * (1 + beatmap.Value.BeatmapInfo.BaseDifficulty.CircleSize - BeatmapDifficulty.DEFAULT_DIFFICULTY) / BeatmapDifficulty.DEFAULT_DIFFICULTY);
|
||||
|
@ -72,7 +72,7 @@ namespace osu.Game.Rulesets.Osu.UI
|
||||
|
||||
private void onNewResult(DrawableHitObject judgedObject, JudgementResult result)
|
||||
{
|
||||
if (!judgedObject.DisplayResult || !DisplayJudgements)
|
||||
if (!judgedObject.DisplayResult || !DisplayJudgements.Value)
|
||||
return;
|
||||
|
||||
DrawableOsuJudgement explosion = new DrawableOsuJudgement(result, judgedObject)
|
||||
|
@ -46,7 +46,7 @@ namespace osu.Game.Rulesets.Osu.UI
|
||||
return null;
|
||||
}
|
||||
|
||||
protected override ReplayInputHandler CreateReplayInputHandler(Replay replay) => new OsuReplayInputHandler(replay);
|
||||
protected override ReplayInputHandler CreateReplayInputHandler(Replay replay) => new OsuFramedReplayInputHandler(replay);
|
||||
|
||||
public override double GameplayStartTime
|
||||
{
|
||||
|
@ -0,0 +1,25 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using NUnit.Framework;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Difficulty;
|
||||
using osu.Game.Rulesets.Taiko.Difficulty;
|
||||
using osu.Game.Tests.Beatmaps;
|
||||
|
||||
namespace osu.Game.Rulesets.Taiko.Tests
|
||||
{
|
||||
public class TaikoDifficultyCalculatorTest : DifficultyCalculatorTest
|
||||
{
|
||||
protected override string ResourceAssembly => "osu.Game.Rulesets.Taiko";
|
||||
|
||||
[TestCase(2.9811338051242915d, "diffcalc-test")]
|
||||
[TestCase(2.9811338051242915d, "diffcalc-test-strong")]
|
||||
public void Test(double expected, string name)
|
||||
=> base.Test(expected, name);
|
||||
|
||||
protected override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new TaikoDifficultyCalculator(new TaikoRuleset(), beatmap);
|
||||
|
||||
protected override Ruleset CreateRuleset() => new TaikoRuleset();
|
||||
}
|
||||
}
|
@ -78,7 +78,7 @@ namespace osu.Game.Rulesets.Taiko.Tests
|
||||
Ruleset = new TaikoRuleset().RulesetInfo
|
||||
},
|
||||
ControlPointInfo = controlPointInfo
|
||||
});
|
||||
}, Clock);
|
||||
|
||||
Add(playfieldContainer = new Container
|
||||
{
|
||||
|
@ -0,0 +1,20 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Game.Rulesets.Difficulty.Preprocessing;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Taiko.Objects;
|
||||
|
||||
namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing
|
||||
{
|
||||
public class TaikoDifficultyHitObject : DifficultyHitObject
|
||||
{
|
||||
public readonly bool HasTypeChange;
|
||||
|
||||
public TaikoDifficultyHitObject(HitObject hitObject, HitObject lastObject, double clockRate)
|
||||
: base(hitObject, lastObject, clockRate)
|
||||
{
|
||||
HasTypeChange = lastObject is RimHit != hitObject is RimHit;
|
||||
}
|
||||
}
|
||||
}
|
95
osu.Game.Rulesets.Taiko/Difficulty/Skills/Strain.cs
Normal file
95
osu.Game.Rulesets.Taiko/Difficulty/Skills/Strain.cs
Normal file
@ -0,0 +1,95 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using osu.Game.Rulesets.Difficulty.Preprocessing;
|
||||
using osu.Game.Rulesets.Difficulty.Skills;
|
||||
using osu.Game.Rulesets.Taiko.Difficulty.Preprocessing;
|
||||
using osu.Game.Rulesets.Taiko.Objects;
|
||||
|
||||
namespace osu.Game.Rulesets.Taiko.Difficulty.Skills
|
||||
{
|
||||
public class Strain : Skill
|
||||
{
|
||||
private const double rhythm_change_base_threshold = 0.2;
|
||||
private const double rhythm_change_base = 2.0;
|
||||
|
||||
protected override double SkillMultiplier => 1;
|
||||
protected override double StrainDecayBase => 0.3;
|
||||
|
||||
private ColourSwitch lastColourSwitch = ColourSwitch.None;
|
||||
|
||||
private int sameColourCount = 1;
|
||||
|
||||
protected override double StrainValueOf(DifficultyHitObject current)
|
||||
{
|
||||
double addition = 1;
|
||||
|
||||
// We get an extra addition if we are not a slider or spinner
|
||||
if (current.LastObject is Hit && current.BaseObject is Hit && current.DeltaTime < 1000)
|
||||
{
|
||||
if (hasColourChange(current))
|
||||
addition += 0.75;
|
||||
|
||||
if (hasRhythmChange(current))
|
||||
addition += 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
lastColourSwitch = ColourSwitch.None;
|
||||
sameColourCount = 1;
|
||||
}
|
||||
|
||||
double additionFactor = 1;
|
||||
|
||||
// Scale the addition factor linearly from 0.4 to 1 for DeltaTime from 0 to 50
|
||||
if (current.DeltaTime < 50)
|
||||
additionFactor = 0.4 + 0.6 * current.DeltaTime / 50;
|
||||
|
||||
return additionFactor * addition;
|
||||
}
|
||||
|
||||
private bool hasRhythmChange(DifficultyHitObject current)
|
||||
{
|
||||
// We don't want a division by zero if some random mapper decides to put two HitObjects at the same time.
|
||||
if (current.DeltaTime == 0 || Previous.Count == 0 || Previous[0].DeltaTime == 0)
|
||||
return false;
|
||||
|
||||
double timeElapsedRatio = Math.Max(Previous[0].DeltaTime / current.DeltaTime, current.DeltaTime / Previous[0].DeltaTime);
|
||||
|
||||
if (timeElapsedRatio >= 8)
|
||||
return false;
|
||||
|
||||
double difference = Math.Log(timeElapsedRatio, rhythm_change_base) % 1.0;
|
||||
|
||||
return difference > rhythm_change_base_threshold && difference < 1 - rhythm_change_base_threshold;
|
||||
}
|
||||
|
||||
private bool hasColourChange(DifficultyHitObject current)
|
||||
{
|
||||
var taikoCurrent = (TaikoDifficultyHitObject)current;
|
||||
|
||||
if (!taikoCurrent.HasTypeChange)
|
||||
{
|
||||
sameColourCount++;
|
||||
return false;
|
||||
}
|
||||
|
||||
var oldColourSwitch = lastColourSwitch;
|
||||
var newColourSwitch = sameColourCount % 2 == 0 ? ColourSwitch.Even : ColourSwitch.Odd;
|
||||
|
||||
lastColourSwitch = newColourSwitch;
|
||||
sameColourCount = 1;
|
||||
|
||||
// We only want a bonus if the parity of the color switch changes
|
||||
return oldColourSwitch != ColourSwitch.None && oldColourSwitch != newColourSwitch;
|
||||
}
|
||||
|
||||
private enum ColourSwitch
|
||||
{
|
||||
None,
|
||||
Even,
|
||||
Odd
|
||||
}
|
||||
}
|
||||
}
|
@ -2,7 +2,6 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Game.Rulesets.Difficulty;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
|
||||
namespace osu.Game.Rulesets.Taiko.Difficulty
|
||||
{
|
||||
@ -10,10 +9,5 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
|
||||
{
|
||||
public double GreatHitWindow;
|
||||
public int MaxCombo;
|
||||
|
||||
public TaikoDifficultyAttributes(Mod[] mods, double starRating)
|
||||
: base(mods, starRating)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,137 +1,51 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Difficulty;
|
||||
using osu.Game.Rulesets.Difficulty.Preprocessing;
|
||||
using osu.Game.Rulesets.Difficulty.Skills;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Taiko.Difficulty.Preprocessing;
|
||||
using osu.Game.Rulesets.Taiko.Difficulty.Skills;
|
||||
using osu.Game.Rulesets.Taiko.Mods;
|
||||
using osu.Game.Rulesets.Taiko.Objects;
|
||||
|
||||
namespace osu.Game.Rulesets.Taiko.Difficulty
|
||||
{
|
||||
internal class TaikoDifficultyCalculator : DifficultyCalculator
|
||||
public class TaikoDifficultyCalculator : DifficultyCalculator
|
||||
{
|
||||
private const double star_scaling_factor = 0.04125;
|
||||
|
||||
/// <summary>
|
||||
/// In milliseconds. For difficulty calculation we will only look at the highest strain value in each time interval of size STRAIN_STEP.
|
||||
/// This is to eliminate higher influence of stream over aim by simply having more HitObjects with high strain.
|
||||
/// The higher this value, the less strains there will be, indirectly giving long beatmaps an advantage.
|
||||
/// </summary>
|
||||
private const double strain_step = 400;
|
||||
|
||||
/// <summary>
|
||||
/// The weighting of each strain value decays to this number * it's previous value
|
||||
/// </summary>
|
||||
private const double decay_weight = 0.9;
|
||||
|
||||
public TaikoDifficultyCalculator(Ruleset ruleset, WorkingBeatmap beatmap)
|
||||
: base(ruleset, beatmap)
|
||||
{
|
||||
}
|
||||
|
||||
protected override DifficultyAttributes Calculate(IBeatmap beatmap, Mod[] mods, double timeRate)
|
||||
protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills, double clockRate)
|
||||
{
|
||||
if (!beatmap.HitObjects.Any())
|
||||
return new TaikoDifficultyAttributes(mods, 0);
|
||||
if (beatmap.HitObjects.Count == 0)
|
||||
return new TaikoDifficultyAttributes { Mods = mods };
|
||||
|
||||
var difficultyHitObjects = new List<TaikoHitObjectDifficulty>();
|
||||
|
||||
foreach (var hitObject in beatmap.HitObjects)
|
||||
difficultyHitObjects.Add(new TaikoHitObjectDifficulty((TaikoHitObject)hitObject));
|
||||
|
||||
// Sort DifficultyHitObjects by StartTime of the HitObjects - just to make sure.
|
||||
difficultyHitObjects.Sort((a, b) => a.BaseHitObject.StartTime.CompareTo(b.BaseHitObject.StartTime));
|
||||
|
||||
if (!calculateStrainValues(difficultyHitObjects, timeRate))
|
||||
return new TaikoDifficultyAttributes(mods, 0);
|
||||
|
||||
double starRating = calculateDifficulty(difficultyHitObjects, timeRate) * star_scaling_factor;
|
||||
|
||||
return new TaikoDifficultyAttributes(mods, starRating)
|
||||
return new TaikoDifficultyAttributes
|
||||
{
|
||||
// Todo: This int cast is temporary to achieve 1:1 results with osu!stable, and should be remoevd in the future
|
||||
GreatHitWindow = (int)(beatmap.HitObjects.First().HitWindows.Great / 2) / timeRate,
|
||||
MaxCombo = beatmap.HitObjects.Count(h => h is Hit)
|
||||
StarRating = skills.Single().DifficultyValue() * star_scaling_factor,
|
||||
Mods = mods,
|
||||
// Todo: This int cast is temporary to achieve 1:1 results with osu!stable, and should be removed in the future
|
||||
GreatHitWindow = (int)(beatmap.HitObjects.First().HitWindows.Great / 2) / clockRate,
|
||||
MaxCombo = beatmap.HitObjects.Count(h => h is Hit),
|
||||
};
|
||||
}
|
||||
|
||||
private bool calculateStrainValues(List<TaikoHitObjectDifficulty> objects, double timeRate)
|
||||
protected override IEnumerable<DifficultyHitObject> CreateDifficultyHitObjects(IBeatmap beatmap, double clockRate)
|
||||
{
|
||||
// Traverse hitObjects in pairs to calculate the strain value of NextHitObject from the strain value of CurrentHitObject and environment.
|
||||
using (var hitObjectsEnumerator = objects.GetEnumerator())
|
||||
{
|
||||
if (!hitObjectsEnumerator.MoveNext()) return false;
|
||||
|
||||
TaikoHitObjectDifficulty current = hitObjectsEnumerator.Current;
|
||||
|
||||
// First hitObject starts at strain 1. 1 is the default for strain values, so we don't need to set it here. See DifficultyHitObject.
|
||||
while (hitObjectsEnumerator.MoveNext())
|
||||
{
|
||||
var next = hitObjectsEnumerator.Current;
|
||||
next?.CalculateStrains(current, timeRate);
|
||||
current = next;
|
||||
for (int i = 1; i < beatmap.HitObjects.Count; i++)
|
||||
yield return new TaikoDifficultyHitObject(beatmap.HitObjects[i], beatmap.HitObjects[i - 1], clockRate);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private double calculateDifficulty(List<TaikoHitObjectDifficulty> objects, double timeRate)
|
||||
{
|
||||
double actualStrainStep = strain_step * timeRate;
|
||||
|
||||
// Find the highest strain value within each strain step
|
||||
List<double> highestStrains = new List<double>();
|
||||
double intervalEndTime = actualStrainStep;
|
||||
double maximumStrain = 0; // We need to keep track of the maximum strain in the current interval
|
||||
|
||||
TaikoHitObjectDifficulty previousHitObject = null;
|
||||
foreach (var hitObject in objects)
|
||||
{
|
||||
// While we are beyond the current interval push the currently available maximum to our strain list
|
||||
while (hitObject.BaseHitObject.StartTime > intervalEndTime)
|
||||
{
|
||||
highestStrains.Add(maximumStrain);
|
||||
|
||||
// The maximum strain of the next interval is not zero by default! We need to take the last hitObject we encountered, take its strain and apply the decay
|
||||
// until the beginning of the next interval.
|
||||
if (previousHitObject == null)
|
||||
{
|
||||
maximumStrain = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
double decay = Math.Pow(TaikoHitObjectDifficulty.DECAY_BASE, (intervalEndTime - previousHitObject.BaseHitObject.StartTime) / 1000);
|
||||
maximumStrain = previousHitObject.Strain * decay;
|
||||
}
|
||||
|
||||
// Go to the next time interval
|
||||
intervalEndTime += actualStrainStep;
|
||||
}
|
||||
|
||||
// Obtain maximum strain
|
||||
maximumStrain = Math.Max(hitObject.Strain, maximumStrain);
|
||||
|
||||
previousHitObject = hitObject;
|
||||
}
|
||||
|
||||
// Build the weighted sum over the highest strains for each interval
|
||||
double difficulty = 0;
|
||||
double weight = 1;
|
||||
highestStrains.Sort((a, b) => b.CompareTo(a)); // Sort from highest to lowest strain.
|
||||
|
||||
foreach (double strain in highestStrains)
|
||||
{
|
||||
difficulty += weight * strain;
|
||||
weight *= decay_weight;
|
||||
}
|
||||
|
||||
return difficulty;
|
||||
}
|
||||
protected override Skill[] CreateSkills(IBeatmap beatmap) => new Skill[] { new Strain() };
|
||||
|
||||
protected override Mod[] DifficultyAdjustmentMods => new Mod[]
|
||||
{
|
||||
|
@ -1,6 +1,7 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Caching;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
@ -48,9 +49,9 @@ namespace osu.Game.Rulesets.Taiko.Mods
|
||||
return default_flashlight_size;
|
||||
}
|
||||
|
||||
protected override void OnComboChange(int newCombo)
|
||||
protected override void OnComboChange(ValueChangedEvent<int> e)
|
||||
{
|
||||
this.TransformTo(nameof(FlashlightSize), new Vector2(0, getSizeFor(newCombo)), FLASHLIGHT_FADE_DURATION);
|
||||
this.TransformTo(nameof(FlashlightSize), new Vector2(0, getSizeFor(e.NewValue)), FLASHLIGHT_FADE_DURATION);
|
||||
}
|
||||
|
||||
protected override string FragmentShader => "CircularFlashlight";
|
||||
|
@ -1,127 +0,0 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
|
||||
namespace osu.Game.Rulesets.Taiko.Objects
|
||||
{
|
||||
internal class TaikoHitObjectDifficulty
|
||||
{
|
||||
/// <summary>
|
||||
/// Factor by how much individual / overall strain decays per second.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// These values are results of tweaking a lot and taking into account general feedback.
|
||||
/// </remarks>
|
||||
internal const double DECAY_BASE = 0.30;
|
||||
|
||||
private const double type_change_bonus = 0.75;
|
||||
private const double rhythm_change_bonus = 1.0;
|
||||
private const double rhythm_change_base_threshold = 0.2;
|
||||
private const double rhythm_change_base = 2.0;
|
||||
|
||||
internal TaikoHitObject BaseHitObject;
|
||||
|
||||
/// <summary>
|
||||
/// Measures note density in a way
|
||||
/// </summary>
|
||||
internal double Strain = 1;
|
||||
|
||||
private double timeElapsed;
|
||||
private int sameTypeSince = 1;
|
||||
|
||||
private bool isRim => BaseHitObject is RimHit;
|
||||
|
||||
public TaikoHitObjectDifficulty(TaikoHitObject baseHitObject)
|
||||
{
|
||||
BaseHitObject = baseHitObject;
|
||||
}
|
||||
|
||||
internal void CalculateStrains(TaikoHitObjectDifficulty previousHitObject, double timeRate)
|
||||
{
|
||||
// Rather simple, but more specialized things are inherently inaccurate due to the big difference playstyles and opinions make.
|
||||
// See Taiko feedback thread.
|
||||
timeElapsed = (BaseHitObject.StartTime - previousHitObject.BaseHitObject.StartTime) / timeRate;
|
||||
double decay = Math.Pow(DECAY_BASE, timeElapsed / 1000);
|
||||
|
||||
double addition = 1;
|
||||
|
||||
// Only if we are no slider or spinner we get an extra addition
|
||||
if (previousHitObject.BaseHitObject is Hit && BaseHitObject is Hit
|
||||
&& BaseHitObject.StartTime - previousHitObject.BaseHitObject.StartTime < 1000) // And we only want to check out hitobjects which aren't so far in the past
|
||||
{
|
||||
addition += typeChangeAddition(previousHitObject);
|
||||
addition += rhythmChangeAddition(previousHitObject);
|
||||
}
|
||||
|
||||
double additionFactor = 1.0;
|
||||
// Scale AdditionFactor linearly from 0.4 to 1 for TimeElapsed from 0 to 50
|
||||
if (timeElapsed < 50.0)
|
||||
additionFactor = 0.4 + 0.6 * timeElapsed / 50.0;
|
||||
|
||||
Strain = previousHitObject.Strain * decay + addition * additionFactor;
|
||||
}
|
||||
|
||||
private TypeSwitch lastTypeSwitchEven = TypeSwitch.None;
|
||||
private double typeChangeAddition(TaikoHitObjectDifficulty previousHitObject)
|
||||
{
|
||||
// If we don't have the same hit type, trigger a type change!
|
||||
if (previousHitObject.isRim != isRim)
|
||||
{
|
||||
lastTypeSwitchEven = previousHitObject.sameTypeSince % 2 == 0 ? TypeSwitch.Even : TypeSwitch.Odd;
|
||||
|
||||
// We only want a bonus if the parity of the type switch changes!
|
||||
switch (previousHitObject.lastTypeSwitchEven)
|
||||
{
|
||||
case TypeSwitch.Even:
|
||||
if (lastTypeSwitchEven == TypeSwitch.Odd)
|
||||
return type_change_bonus;
|
||||
break;
|
||||
case TypeSwitch.Odd:
|
||||
if (lastTypeSwitchEven == TypeSwitch.Even)
|
||||
return type_change_bonus;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// No type change? Increment counter and keep track of last type switch
|
||||
else
|
||||
{
|
||||
lastTypeSwitchEven = previousHitObject.lastTypeSwitchEven;
|
||||
sameTypeSince = previousHitObject.sameTypeSince + 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private double rhythmChangeAddition(TaikoHitObjectDifficulty previousHitObject)
|
||||
{
|
||||
// We don't want a division by zero if some random mapper decides to put 2 HitObjects at the same time.
|
||||
if (timeElapsed == 0 || previousHitObject.timeElapsed == 0)
|
||||
return 0;
|
||||
|
||||
double timeElapsedRatio = Math.Max(previousHitObject.timeElapsed / timeElapsed, timeElapsed / previousHitObject.timeElapsed);
|
||||
|
||||
if (timeElapsedRatio >= 8)
|
||||
return 0;
|
||||
|
||||
double difference = Math.Log(timeElapsedRatio, rhythm_change_base) % 1.0;
|
||||
|
||||
if (isWithinChangeThreshold(difference))
|
||||
return rhythm_change_bonus;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private bool isWithinChangeThreshold(double value)
|
||||
{
|
||||
return value > rhythm_change_base_threshold && value < 1 - rhythm_change_base_threshold;
|
||||
}
|
||||
|
||||
private enum TypeSwitch
|
||||
{
|
||||
None,
|
||||
Even,
|
||||
Odd
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,257 @@
|
||||
osu file format v14
|
||||
|
||||
[General]
|
||||
Mode: 1
|
||||
|
||||
[Difficulty]
|
||||
CircleSize:4
|
||||
OverallDifficulty:7
|
||||
ApproachRate:8.3
|
||||
SliderMultiplier:1.6
|
||||
SliderTickRate:1
|
||||
|
||||
[TimingPoints]
|
||||
500,500,4,2,1,50,1,0
|
||||
62500,-500,4,2,1,50,0,0
|
||||
71000,-100,4,2,1,50,0,0
|
||||
|
||||
[HitObjects]
|
||||
// Same as diffcalc-test with finishers on every note
|
||||
142,122,0,5,4,0:0:0:0:
|
||||
142,122,125,1,4,0:0:0:0:
|
||||
142,122,250,1,4,0:0:0:0:
|
||||
142,122,375,1,4,0:0:0:0:
|
||||
142,122,500,1,4,0:0:0:0:
|
||||
142,122,625,1,4,0:0:0:0:
|
||||
142,122,750,1,4,0:0:0:0:
|
||||
142,122,875,1,4,0:0:0:0:
|
||||
142,122,1000,1,4,0:0:0:0:
|
||||
142,122,1125,1,4,0:0:0:0:
|
||||
142,122,1250,1,4,0:0:0:0:
|
||||
142,122,1375,1,4,0:0:0:0:
|
||||
142,122,1500,1,4,0:0:0:0:
|
||||
119,106,2500,1,6,0:0:0:0:
|
||||
119,106,2625,1,6,0:0:0:0:
|
||||
119,106,2750,1,6,0:0:0:0:
|
||||
119,106,2875,1,6,0:0:0:0:
|
||||
119,106,3000,1,6,0:0:0:0:
|
||||
119,106,3125,1,6,0:0:0:0:
|
||||
119,106,3250,1,6,0:0:0:0:
|
||||
119,106,3375,1,6,0:0:0:0:
|
||||
119,106,3500,1,6,0:0:0:0:
|
||||
119,106,3625,1,6,0:0:0:0:
|
||||
119,106,3750,1,6,0:0:0:0:
|
||||
119,106,3875,1,6,0:0:0:0:
|
||||
119,106,4000,1,6,0:0:0:0:
|
||||
136,90,5000,1,4,0:0:0:0:
|
||||
136,90,5125,1,6,0:0:0:0:
|
||||
136,90,5250,1,4,0:0:0:0:
|
||||
136,90,5375,1,6,0:0:0:0:
|
||||
136,90,5500,1,4,0:0:0:0:
|
||||
136,90,5625,1,6,0:0:0:0:
|
||||
136,90,5750,1,4,0:0:0:0:
|
||||
136,90,5875,1,6,0:0:0:0:
|
||||
136,90,6000,1,4,0:0:0:0:
|
||||
136,90,6125,1,6,0:0:0:0:
|
||||
136,90,6250,1,4,0:0:0:0:
|
||||
136,90,6375,1,6,0:0:0:0:
|
||||
136,90,6500,1,4,0:0:0:0:
|
||||
86,113,7500,1,4,0:0:0:0:
|
||||
86,113,7625,1,4,0:0:0:0:
|
||||
86,113,7750,1,6,0:0:0:0:
|
||||
86,113,7875,1,6,0:0:0:0:
|
||||
86,113,8000,1,4,0:0:0:0:
|
||||
86,113,8125,1,4,0:0:0:0:
|
||||
86,113,8250,1,6,0:0:0:0:
|
||||
86,113,8375,1,6,0:0:0:0:
|
||||
86,113,8500,1,4,0:0:0:0:
|
||||
86,113,8625,1,4,0:0:0:0:
|
||||
86,113,8750,1,6,0:0:0:0:
|
||||
86,113,8875,1,6,0:0:0:0:
|
||||
86,113,9000,1,4,0:0:0:0:
|
||||
146,90,10000,1,4,0:0:0:0:
|
||||
146,90,10125,1,4,0:0:0:0:
|
||||
146,90,10250,1,4,0:0:0:0:
|
||||
146,90,10375,1,6,0:0:0:0:
|
||||
146,90,10500,1,6,0:0:0:0:
|
||||
146,90,10625,1,6,0:0:0:0:
|
||||
146,90,10750,1,4,0:0:0:0:
|
||||
146,90,10875,1,4,0:0:0:0:
|
||||
146,90,11000,1,4,0:0:0:0:
|
||||
146,90,11125,1,6,0:0:0:0:
|
||||
146,90,11250,1,6,0:0:0:0:
|
||||
146,90,11375,1,6,0:0:0:0:
|
||||
146,90,11500,1,4,0:0:0:0:
|
||||
146,90,11625,1,4,0:0:0:0:
|
||||
146,90,11750,1,4,0:0:0:0:
|
||||
146,90,11875,1,6,0:0:0:0:
|
||||
146,90,12000,1,6,0:0:0:0:
|
||||
146,90,12125,1,6,0:0:0:0:
|
||||
146,90,12250,1,4,0:0:0:0:
|
||||
146,90,12375,1,4,0:0:0:0:
|
||||
146,90,12500,1,4,0:0:0:0:
|
||||
69,99,13500,1,4,0:0:0:0:
|
||||
69,99,13625,1,4,0:0:0:0:
|
||||
69,99,13750,1,4,0:0:0:0:
|
||||
69,99,13875,1,6,0:0:0:0:
|
||||
69,99,14000,1,4,0:0:0:0:
|
||||
69,99,14125,1,4,0:0:0:0:
|
||||
69,99,14250,1,4,0:0:0:0:
|
||||
69,99,14375,1,6,0:0:0:0:
|
||||
69,99,14500,1,4,0:0:0:0:
|
||||
69,99,14625,1,4,0:0:0:0:
|
||||
69,99,14750,1,4,0:0:0:0:
|
||||
69,99,14875,1,6,0:0:0:0:
|
||||
69,99,15000,1,4,0:0:0:0:
|
||||
69,99,15125,1,4,0:0:0:0:
|
||||
69,99,15250,1,4,0:0:0:0:
|
||||
69,99,15375,1,6,0:0:0:0:
|
||||
69,99,15500,1,4,0:0:0:0:
|
||||
83,89,16500,1,4,0:0:0:0:
|
||||
83,89,16625,1,6,0:0:0:0:
|
||||
83,89,16750,1,6,0:0:0:0:
|
||||
83,89,16875,1,4,0:0:0:0:
|
||||
83,89,17000,1,4,0:0:0:0:
|
||||
83,89,17125,1,4,0:0:0:0:
|
||||
83,89,17250,1,6,0:0:0:0:
|
||||
83,89,17375,1,6,0:0:0:0:
|
||||
83,89,17500,1,6,0:0:0:0:
|
||||
83,89,17625,1,6,0:0:0:0:
|
||||
83,89,17750,1,4,0:0:0:0:
|
||||
83,89,17875,1,4,0:0:0:0:
|
||||
83,89,18000,1,4,0:0:0:0:
|
||||
83,89,18125,1,4,0:0:0:0:
|
||||
83,89,18250,1,4,0:0:0:0:
|
||||
83,89,18375,1,6,0:0:0:0:
|
||||
83,89,18500,1,6,0:0:0:0:
|
||||
83,89,18625,1,6,0:0:0:0:
|
||||
83,89,18750,1,6,0:0:0:0:
|
||||
83,89,18875,1,4,0:0:0:0:
|
||||
83,89,19000,1,4,0:0:0:0:
|
||||
83,89,19125,1,4,0:0:0:0:
|
||||
83,89,19250,1,4,0:0:0:0:
|
||||
83,89,19375,1,6,0:0:0:0:
|
||||
83,89,19500,1,6,0:0:0:0:
|
||||
83,89,19625,1,4,0:0:0:0:
|
||||
84,122,20500,1,4,0:0:0:0:
|
||||
84,122,20625,2,4,L|217:123,1,120
|
||||
84,122,21125,1,4,0:0:0:0:
|
||||
84,122,21250,2,4,L|217:123,1,120
|
||||
84,122,21750,1,4,0:0:0:0:
|
||||
84,122,21875,2,4,L|217:123,1,120
|
||||
84,122,22375,1,4,0:0:0:0:
|
||||
84,122,22500,2,4,L|217:123,1,120
|
||||
84,122,23000,1,4,0:0:0:0:
|
||||
84,122,23125,2,4,L|217:123,1,120
|
||||
99,106,24500,1,4,0:0:0:0:
|
||||
99,106,24625,1,4,0:0:0:0:
|
||||
99,106,24750,2,4,L|194:107,1,80
|
||||
99,106,25125,1,4,0:0:0:0:
|
||||
99,106,25250,1,4,0:0:0:0:
|
||||
99,106,25375,2,4,L|194:107,1,80
|
||||
99,106,25750,1,4,0:0:0:0:
|
||||
99,106,25875,1,4,0:0:0:0:
|
||||
99,106,26000,2,4,L|194:107,1,80
|
||||
99,106,26375,1,4,0:0:0:0:
|
||||
99,106,26500,1,4,0:0:0:0:
|
||||
99,106,26625,2,4,L|194:107,1,80
|
||||
99,106,27000,1,4,0:0:0:0:
|
||||
99,106,27125,1,4,0:0:0:0:
|
||||
99,106,27250,2,4,L|194:107,1,80
|
||||
121,103,28500,1,4,0:0:0:0:
|
||||
121,103,28625,1,4,0:0:0:0:
|
||||
121,103,28750,1,4,0:0:0:0:
|
||||
121,103,28875,2,4,L|190:103,1,40
|
||||
121,103,29125,1,4,0:0:0:0:
|
||||
121,103,29250,1,4,0:0:0:0:
|
||||
121,103,29375,1,4,0:0:0:0:
|
||||
121,103,29500,2,4,L|190:103,1,40
|
||||
121,103,29750,1,4,0:0:0:0:
|
||||
121,103,29875,1,4,0:0:0:0:
|
||||
121,103,30000,1,4,0:0:0:0:
|
||||
121,103,30125,2,4,L|190:103,1,40
|
||||
121,103,30375,1,4,0:0:0:0:
|
||||
121,103,30500,1,4,0:0:0:0:
|
||||
121,103,30625,1,4,0:0:0:0:
|
||||
121,103,30750,2,4,L|190:103,1,40
|
||||
121,103,31000,1,4,0:0:0:0:
|
||||
121,103,31125,1,4,0:0:0:0:
|
||||
121,103,31250,1,4,0:0:0:0:
|
||||
121,103,31375,2,4,L|190:103,1,40
|
||||
121,103,32500,1,4,0:0:0:0:
|
||||
121,103,32625,1,6,0:0:0:0:
|
||||
121,103,32750,1,4,0:0:0:0:
|
||||
121,103,32875,2,4,L|190:103,1,40
|
||||
121,103,33125,1,4,0:0:0:0:
|
||||
121,103,33250,1,6,0:0:0:0:
|
||||
121,103,33375,1,4,0:0:0:0:
|
||||
121,103,33500,2,4,L|190:103,1,40
|
||||
121,103,33750,1,4,0:0:0:0:
|
||||
121,103,33875,1,6,0:0:0:0:
|
||||
121,103,34000,1,4,0:0:0:0:
|
||||
121,103,34125,2,4,L|190:103,1,40
|
||||
121,103,34375,1,4,0:0:0:0:
|
||||
121,103,34500,1,6,0:0:0:0:
|
||||
121,103,34625,1,4,0:0:0:0:
|
||||
121,103,34750,2,4,L|190:103,1,40
|
||||
121,103,35000,1,4,0:0:0:0:
|
||||
121,103,35125,1,6,0:0:0:0:
|
||||
121,103,35250,1,4,0:0:0:0:
|
||||
121,103,35375,2,4,L|190:103,1,40
|
||||
121,103,36500,1,4,0:0:0:0:
|
||||
121,103,36625,1,4,0:0:0:0:
|
||||
121,103,36750,1,6,0:0:0:0:
|
||||
121,103,36875,2,4,L|190:103,1,40
|
||||
121,103,37125,1,4,0:0:0:0:
|
||||
121,103,37250,1,4,0:0:0:0:
|
||||
121,103,37375,1,6,0:0:0:0:
|
||||
121,103,37500,2,4,L|190:103,1,40
|
||||
121,103,37750,1,4,0:0:0:0:
|
||||
121,103,37875,1,4,0:0:0:0:
|
||||
121,103,38000,1,6,0:0:0:0:
|
||||
121,103,38125,2,4,L|190:103,1,40
|
||||
121,103,38375,1,4,0:0:0:0:
|
||||
121,103,38500,1,4,0:0:0:0:
|
||||
121,103,38625,1,6,0:0:0:0:
|
||||
121,103,38750,2,4,L|190:103,1,40
|
||||
121,103,39000,1,4,0:0:0:0:
|
||||
121,103,39125,1,4,0:0:0:0:
|
||||
121,103,39250,1,6,0:0:0:0:
|
||||
121,103,39375,2,4,L|190:103,1,40
|
||||
107,106,40500,1,4,0:0:0:0:
|
||||
107,106,40625,1,4,0:0:0:0:
|
||||
107,106,40750,1,6,0:0:0:0:
|
||||
107,106,40875,1,6,0:0:0:0:
|
||||
46,112,41000,2,4,L|214:112,1,160
|
||||
107,106,41625,1,4,0:0:0:0:
|
||||
107,106,41750,1,4,0:0:0:0:
|
||||
107,106,41875,1,6,0:0:0:0:
|
||||
107,106,42000,1,6,0:0:0:0:
|
||||
46,112,42125,2,4,L|214:112,1,160
|
||||
107,106,42750,1,4,0:0:0:0:
|
||||
107,106,42875,1,4,0:0:0:0:
|
||||
107,106,43000,1,6,0:0:0:0:
|
||||
107,106,43125,1,6,0:0:0:0:
|
||||
46,112,43250,2,4,L|214:112,1,160
|
||||
107,106,43875,1,4,0:0:0:0:
|
||||
107,106,44000,1,4,0:0:0:0:
|
||||
107,106,44125,1,6,0:0:0:0:
|
||||
107,106,44250,1,6,0:0:0:0:
|
||||
46,112,44375,2,4,L|214:112,1,160
|
||||
107,106,45000,1,4,0:0:0:0:
|
||||
107,106,45125,1,4,0:0:0:0:
|
||||
107,106,45250,1,6,0:0:0:0:
|
||||
107,106,45375,1,6,0:0:0:0:
|
||||
46,112,45500,2,4,L|214:112,1,160
|
||||
256,192,47000,12,4,47500,0:0:0:0:
|
||||
256,192,47625,12,4,48000,0:0:0:0:
|
||||
256,192,48125,12,4,48500,0:0:0:0:
|
||||
256,192,48625,12,4,49000,0:0:0:0:
|
||||
256,192,50000,12,4,50500,0:0:0:0:
|
||||
183,143,50625,5,4,0:0:0:0:
|
||||
256,192,50750,12,4,51250,0:0:0:0:
|
||||
114,106,51375,5,4,0:0:0:0:
|
||||
256,192,51625,12,4,52125,0:0:0:0:
|
||||
154,143,52250,5,4,0:0:0:0:
|
||||
256,192,52375,12,4,52875,0:0:0:0:
|
||||
116,111,53000,5,4,0:0:0:0:
|
@ -0,0 +1,285 @@
|
||||
osu file format v14
|
||||
|
||||
[General]
|
||||
Mode: 1
|
||||
|
||||
[Difficulty]
|
||||
CircleSize:4
|
||||
OverallDifficulty:7
|
||||
ApproachRate:8.3
|
||||
SliderMultiplier:1.6
|
||||
SliderTickRate:1
|
||||
|
||||
[TimingPoints]
|
||||
500,500,4,2,1,50,1,0
|
||||
62500,-500,4,2,1,50,0,0
|
||||
71000,-100,4,2,1,50,0,0
|
||||
|
||||
[HitObjects]
|
||||
// dd, spaced 1/4 beat apart
|
||||
142,122,0,5,0,0:0:0:0:
|
||||
142,122,125,1,0,0:0:0:0:
|
||||
142,122,250,1,0,0:0:0:0:
|
||||
142,122,375,1,0,0:0:0:0:
|
||||
142,122,500,1,0,0:0:0:0:
|
||||
142,122,625,1,0,0:0:0:0:
|
||||
142,122,750,1,0,0:0:0:0:
|
||||
142,122,875,1,0,0:0:0:0:
|
||||
142,122,1000,1,0,0:0:0:0:
|
||||
142,122,1125,1,0,0:0:0:0:
|
||||
142,122,1250,1,0,0:0:0:0:
|
||||
142,122,1375,1,0,0:0:0:0:
|
||||
142,122,1500,1,0,0:0:0:0:
|
||||
|
||||
// kk, spaced 1/4 beat apart
|
||||
119,106,2500,1,2,0:0:0:0:
|
||||
119,106,2625,1,2,0:0:0:0:
|
||||
119,106,2750,1,2,0:0:0:0:
|
||||
119,106,2875,1,2,0:0:0:0:
|
||||
119,106,3000,1,2,0:0:0:0:
|
||||
119,106,3125,1,2,0:0:0:0:
|
||||
119,106,3250,1,2,0:0:0:0:
|
||||
119,106,3375,1,2,0:0:0:0:
|
||||
119,106,3500,1,2,0:0:0:0:
|
||||
119,106,3625,1,2,0:0:0:0:
|
||||
119,106,3750,1,2,0:0:0:0:
|
||||
119,106,3875,1,2,0:0:0:0:
|
||||
119,106,4000,1,2,0:0:0:0:
|
||||
|
||||
// dk, spaced 1/4 beat apart
|
||||
136,90,5000,1,0,0:0:0:0:
|
||||
136,90,5125,1,2,0:0:0:0:
|
||||
136,90,5250,1,0,0:0:0:0:
|
||||
136,90,5375,1,2,0:0:0:0:
|
||||
136,90,5500,1,0,0:0:0:0:
|
||||
136,90,5625,1,2,0:0:0:0:
|
||||
136,90,5750,1,0,0:0:0:0:
|
||||
136,90,5875,1,2,0:0:0:0:
|
||||
136,90,6000,1,0,0:0:0:0:
|
||||
136,90,6125,1,2,0:0:0:0:
|
||||
136,90,6250,1,0,0:0:0:0:
|
||||
136,90,6375,1,2,0:0:0:0:
|
||||
136,90,6500,1,0,0:0:0:0:
|
||||
|
||||
// ddkk, spaced 1/4 beat apart
|
||||
86,113,7500,1,0,0:0:0:0:
|
||||
86,113,7625,1,0,0:0:0:0:
|
||||
86,113,7750,1,2,0:0:0:0:
|
||||
86,113,7875,1,2,0:0:0:0:
|
||||
86,113,8000,1,0,0:0:0:0:
|
||||
86,113,8125,1,0,0:0:0:0:
|
||||
86,113,8250,1,2,0:0:0:0:
|
||||
86,113,8375,1,2,0:0:0:0:
|
||||
86,113,8500,1,0,0:0:0:0:
|
||||
86,113,8625,1,0,0:0:0:0:
|
||||
86,113,8750,1,2,0:0:0:0:
|
||||
86,113,8875,1,2,0:0:0:0:
|
||||
86,113,9000,1,0,0:0:0:0:
|
||||
|
||||
// dddkkk, spaced 1/4 beat apart
|
||||
146,90,10000,1,0,0:0:0:0:
|
||||
146,90,10125,1,0,0:0:0:0:
|
||||
146,90,10250,1,0,0:0:0:0:
|
||||
146,90,10375,1,2,0:0:0:0:
|
||||
146,90,10500,1,2,0:0:0:0:
|
||||
146,90,10625,1,2,0:0:0:0:
|
||||
146,90,10750,1,0,0:0:0:0:
|
||||
146,90,10875,1,0,0:0:0:0:
|
||||
146,90,11000,1,0,0:0:0:0:
|
||||
146,90,11125,1,2,0:0:0:0:
|
||||
146,90,11250,1,2,0:0:0:0:
|
||||
146,90,11375,1,2,0:0:0:0:
|
||||
146,90,11500,1,0,0:0:0:0:
|
||||
146,90,11625,1,0,0:0:0:0:
|
||||
146,90,11750,1,0,0:0:0:0:
|
||||
146,90,11875,1,2,0:0:0:0:
|
||||
146,90,12000,1,2,0:0:0:0:
|
||||
146,90,12125,1,2,0:0:0:0:
|
||||
146,90,12250,1,0,0:0:0:0:
|
||||
146,90,12375,1,0,0:0:0:0:
|
||||
146,90,12500,1,0,0:0:0:0:
|
||||
|
||||
// dddk, spaced 1/4 beat apart
|
||||
69,99,13500,1,0,0:0:0:0:
|
||||
69,99,13625,1,0,0:0:0:0:
|
||||
69,99,13750,1,0,0:0:0:0:
|
||||
69,99,13875,1,2,0:0:0:0:
|
||||
69,99,14000,1,0,0:0:0:0:
|
||||
69,99,14125,1,0,0:0:0:0:
|
||||
69,99,14250,1,0,0:0:0:0:
|
||||
69,99,14375,1,2,0:0:0:0:
|
||||
69,99,14500,1,0,0:0:0:0:
|
||||
69,99,14625,1,0,0:0:0:0:
|
||||
69,99,14750,1,0,0:0:0:0:
|
||||
69,99,14875,1,2,0:0:0:0:
|
||||
69,99,15000,1,0,0:0:0:0:
|
||||
69,99,15125,1,0,0:0:0:0:
|
||||
69,99,15250,1,0,0:0:0:0:
|
||||
69,99,15375,1,2,0:0:0:0:
|
||||
69,99,15500,1,0,0:0:0:0:
|
||||
|
||||
// arbitrary pattern, spaced 1/4 beat apart
|
||||
83,89,16500,1,0,0:0:0:0:
|
||||
83,89,16625,1,2,0:0:0:0:
|
||||
83,89,16750,1,2,0:0:0:0:
|
||||
83,89,16875,1,0,0:0:0:0:
|
||||
83,89,17000,1,0,0:0:0:0:
|
||||
83,89,17125,1,0,0:0:0:0:
|
||||
83,89,17250,1,2,0:0:0:0:
|
||||
83,89,17375,1,2,0:0:0:0:
|
||||
83,89,17500,1,2,0:0:0:0:
|
||||
83,89,17625,1,2,0:0:0:0:
|
||||
83,89,17750,1,0,0:0:0:0:
|
||||
83,89,17875,1,0,0:0:0:0:
|
||||
83,89,18000,1,0,0:0:0:0:
|
||||
83,89,18125,1,0,0:0:0:0:
|
||||
83,89,18250,1,0,0:0:0:0:
|
||||
83,89,18375,1,2,0:0:0:0:
|
||||
83,89,18500,1,2,0:0:0:0:
|
||||
83,89,18625,1,2,0:0:0:0:
|
||||
83,89,18750,1,2,0:0:0:0:
|
||||
83,89,18875,1,0,0:0:0:0:
|
||||
83,89,19000,1,0,0:0:0:0:
|
||||
83,89,19125,1,0,0:0:0:0:
|
||||
83,89,19250,1,0,0:0:0:0:
|
||||
83,89,19375,1,2,0:0:0:0:
|
||||
83,89,19500,1,2,0:0:0:0:
|
||||
83,89,19625,1,0,0:0:0:0:
|
||||
|
||||
// d-slider pattern, spaced 1/4 beat apart
|
||||
84,122,20500,1,0,0:0:0:0:
|
||||
84,122,20625,2,0,L|217:123,1,120
|
||||
84,122,21125,1,0,0:0:0:0:
|
||||
84,122,21250,2,0,L|217:123,1,120
|
||||
84,122,21750,1,0,0:0:0:0:
|
||||
84,122,21875,2,0,L|217:123,1,120
|
||||
84,122,22375,1,0,0:0:0:0:
|
||||
84,122,22500,2,0,L|217:123,1,120
|
||||
84,122,23000,1,0,0:0:0:0:
|
||||
84,122,23125,2,0,L|217:123,1,120
|
||||
|
||||
// dd-slider pattern, spaced 1/4 beat apart
|
||||
99,106,24500,1,0,0:0:0:0:
|
||||
99,106,24625,1,0,0:0:0:0:
|
||||
99,106,24750,2,0,L|194:107,1,80
|
||||
99,106,25125,1,0,0:0:0:0:
|
||||
99,106,25250,1,0,0:0:0:0:
|
||||
99,106,25375,2,0,L|194:107,1,80
|
||||
99,106,25750,1,0,0:0:0:0:
|
||||
99,106,25875,1,0,0:0:0:0:
|
||||
99,106,26000,2,0,L|194:107,1,80
|
||||
99,106,26375,1,0,0:0:0:0:
|
||||
99,106,26500,1,0,0:0:0:0:
|
||||
99,106,26625,2,0,L|194:107,1,80
|
||||
99,106,27000,1,0,0:0:0:0:
|
||||
99,106,27125,1,0,0:0:0:0:
|
||||
99,106,27250,2,0,L|194:107,1,80
|
||||
|
||||
// ddd-slider pattern, spaced 1/4 beat apart
|
||||
121,103,28500,1,0,0:0:0:0:
|
||||
121,103,28625,1,0,0:0:0:0:
|
||||
121,103,28750,1,0,0:0:0:0:
|
||||
121,103,28875,2,0,L|190:103,1,40
|
||||
121,103,29125,1,0,0:0:0:0:
|
||||
121,103,29250,1,0,0:0:0:0:
|
||||
121,103,29375,1,0,0:0:0:0:
|
||||
121,103,29500,2,0,L|190:103,1,40
|
||||
121,103,29750,1,0,0:0:0:0:
|
||||
121,103,29875,1,0,0:0:0:0:
|
||||
121,103,30000,1,0,0:0:0:0:
|
||||
121,103,30125,2,0,L|190:103,1,40
|
||||
121,103,30375,1,0,0:0:0:0:
|
||||
121,103,30500,1,0,0:0:0:0:
|
||||
121,103,30625,1,0,0:0:0:0:
|
||||
121,103,30750,2,0,L|190:103,1,40
|
||||
121,103,31000,1,0,0:0:0:0:
|
||||
121,103,31125,1,0,0:0:0:0:
|
||||
121,103,31250,1,0,0:0:0:0:
|
||||
121,103,31375,2,0,L|190:103,1,40
|
||||
|
||||
// dkd-slider pattern, spaced 1/4 beat apart
|
||||
121,103,32500,1,0,0:0:0:0:
|
||||
121,103,32625,1,2,0:0:0:0:
|
||||
121,103,32750,1,0,0:0:0:0:
|
||||
121,103,32875,2,0,L|190:103,1,40
|
||||
121,103,33125,1,0,0:0:0:0:
|
||||
121,103,33250,1,2,0:0:0:0:
|
||||
121,103,33375,1,0,0:0:0:0:
|
||||
121,103,33500,2,0,L|190:103,1,40
|
||||
121,103,33750,1,0,0:0:0:0:
|
||||
121,103,33875,1,2,0:0:0:0:
|
||||
121,103,34000,1,0,0:0:0:0:
|
||||
121,103,34125,2,0,L|190:103,1,40
|
||||
121,103,34375,1,0,0:0:0:0:
|
||||
121,103,34500,1,2,0:0:0:0:
|
||||
121,103,34625,1,0,0:0:0:0:
|
||||
121,103,34750,2,0,L|190:103,1,40
|
||||
121,103,35000,1,0,0:0:0:0:
|
||||
121,103,35125,1,2,0:0:0:0:
|
||||
121,103,35250,1,0,0:0:0:0:
|
||||
121,103,35375,2,0,L|190:103,1,40
|
||||
|
||||
//ddk-slider pattern, spaced 1/4 beat apart
|
||||
121,103,36500,1,0,0:0:0:0:
|
||||
121,103,36625,1,0,0:0:0:0:
|
||||
121,103,36750,1,2,0:0:0:0:
|
||||
121,103,36875,2,0,L|190:103,1,40
|
||||
121,103,37125,1,0,0:0:0:0:
|
||||
121,103,37250,1,0,0:0:0:0:
|
||||
121,103,37375,1,2,0:0:0:0:
|
||||
121,103,37500,2,0,L|190:103,1,40
|
||||
121,103,37750,1,0,0:0:0:0:
|
||||
121,103,37875,1,0,0:0:0:0:
|
||||
121,103,38000,1,2,0:0:0:0:
|
||||
121,103,38125,2,0,L|190:103,1,40
|
||||
121,103,38375,1,0,0:0:0:0:
|
||||
121,103,38500,1,0,0:0:0:0:
|
||||
121,103,38625,1,2,0:0:0:0:
|
||||
121,103,38750,2,0,L|190:103,1,40
|
||||
121,103,39000,1,0,0:0:0:0:
|
||||
121,103,39125,1,0,0:0:0:0:
|
||||
121,103,39250,1,2,0:0:0:0:
|
||||
121,103,39375,2,0,L|190:103,1,40
|
||||
|
||||
//ddkk-slider pattern, spaced 1/4 beat apart
|
||||
107,106,40500,1,0,0:0:0:0:
|
||||
107,106,40625,1,0,0:0:0:0:
|
||||
107,106,40750,1,2,0:0:0:0:
|
||||
107,106,40875,1,2,0:0:0:0:
|
||||
46,112,41000,2,0,L|214:112,1,160
|
||||
107,106,41625,1,0,0:0:0:0:
|
||||
107,106,41750,1,0,0:0:0:0:
|
||||
107,106,41875,1,2,0:0:0:0:
|
||||
107,106,42000,1,2,0:0:0:0:
|
||||
46,112,42125,2,0,L|214:112,1,160
|
||||
107,106,42750,1,0,0:0:0:0:
|
||||
107,106,42875,1,0,0:0:0:0:
|
||||
107,106,43000,1,2,0:0:0:0:
|
||||
107,106,43125,1,2,0:0:0:0:
|
||||
46,112,43250,2,0,L|214:112,1,160
|
||||
107,106,43875,1,0,0:0:0:0:
|
||||
107,106,44000,1,0,0:0:0:0:
|
||||
107,106,44125,1,2,0:0:0:0:
|
||||
107,106,44250,1,2,0:0:0:0:
|
||||
46,112,44375,2,0,L|214:112,1,160
|
||||
107,106,45000,1,0,0:0:0:0:
|
||||
107,106,45125,1,0,0:0:0:0:
|
||||
107,106,45250,1,2,0:0:0:0:
|
||||
107,106,45375,1,2,0:0:0:0:
|
||||
46,112,45500,2,0,L|214:112,1,160
|
||||
|
||||
// spinner-spinner pattern, spaced 1/4 beat apart
|
||||
256,192,47000,12,0,47500,0:0:0:0:
|
||||
256,192,47625,12,0,48000,0:0:0:0:
|
||||
256,192,48125,12,0,48500,0:0:0:0:
|
||||
256,192,48625,12,0,49000,0:0:0:0:
|
||||
|
||||
// spinner-d pattern, spaced 1/4 beat apart
|
||||
256,192,50000,12,0,50500,0:0:0:0:
|
||||
183,143,50625,5,0,0:0:0:0:
|
||||
256,192,50750,12,0,51250,0:0:0:0:
|
||||
114,106,51375,5,0,0:0:0:0:
|
||||
256,192,51625,12,0,52125,0:0:0:0:
|
||||
154,143,52250,5,0,0:0:0:0:
|
||||
256,192,52375,12,0,52875,0:0:0:0:
|
||||
116,111,53000,5,0,0:0:0:0:
|
@ -225,7 +225,7 @@ namespace osu.Game.Rulesets.Taiko.UI
|
||||
|
||||
internal void OnNewResult(DrawableHitObject judgedObject, JudgementResult result)
|
||||
{
|
||||
if (!DisplayJudgements)
|
||||
if (!DisplayJudgements.Value)
|
||||
return;
|
||||
|
||||
if (!judgedObject.DisplayResult)
|
||||
|
@ -2,9 +2,12 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using NUnit.Framework;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Difficulty;
|
||||
using osu.Game.Rulesets.Difficulty.Preprocessing;
|
||||
using osu.Game.Rulesets.Difficulty.Skills;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
|
||||
namespace osu.Game.Tests.NonVisual
|
||||
@ -15,7 +18,7 @@ namespace osu.Game.Tests.NonVisual
|
||||
[Test]
|
||||
public void TestNoMods()
|
||||
{
|
||||
var combinations = new TestDifficultyCalculator().CreateDifficultyAdjustmentModCombinations();
|
||||
var combinations = new TestLegacyDifficultyCalculator().CreateDifficultyAdjustmentModCombinations();
|
||||
|
||||
Assert.AreEqual(1, combinations.Length);
|
||||
Assert.IsTrue(combinations[0] is ModNoMod);
|
||||
@ -24,7 +27,7 @@ namespace osu.Game.Tests.NonVisual
|
||||
[Test]
|
||||
public void TestSingleMod()
|
||||
{
|
||||
var combinations = new TestDifficultyCalculator(new ModA()).CreateDifficultyAdjustmentModCombinations();
|
||||
var combinations = new TestLegacyDifficultyCalculator(new ModA()).CreateDifficultyAdjustmentModCombinations();
|
||||
|
||||
Assert.AreEqual(2, combinations.Length);
|
||||
Assert.IsTrue(combinations[0] is ModNoMod);
|
||||
@ -34,7 +37,7 @@ namespace osu.Game.Tests.NonVisual
|
||||
[Test]
|
||||
public void TestDoubleMod()
|
||||
{
|
||||
var combinations = new TestDifficultyCalculator(new ModA(), new ModB()).CreateDifficultyAdjustmentModCombinations();
|
||||
var combinations = new TestLegacyDifficultyCalculator(new ModA(), new ModB()).CreateDifficultyAdjustmentModCombinations();
|
||||
|
||||
Assert.AreEqual(4, combinations.Length);
|
||||
Assert.IsTrue(combinations[0] is ModNoMod);
|
||||
@ -49,7 +52,7 @@ namespace osu.Game.Tests.NonVisual
|
||||
[Test]
|
||||
public void TestIncompatibleMods()
|
||||
{
|
||||
var combinations = new TestDifficultyCalculator(new ModA(), new ModIncompatibleWithA()).CreateDifficultyAdjustmentModCombinations();
|
||||
var combinations = new TestLegacyDifficultyCalculator(new ModA(), new ModIncompatibleWithA()).CreateDifficultyAdjustmentModCombinations();
|
||||
|
||||
Assert.AreEqual(3, combinations.Length);
|
||||
Assert.IsTrue(combinations[0] is ModNoMod);
|
||||
@ -60,7 +63,7 @@ namespace osu.Game.Tests.NonVisual
|
||||
[Test]
|
||||
public void TestDoubleIncompatibleMods()
|
||||
{
|
||||
var combinations = new TestDifficultyCalculator(new ModA(), new ModB(), new ModIncompatibleWithA(), new ModIncompatibleWithAAndB()).CreateDifficultyAdjustmentModCombinations();
|
||||
var combinations = new TestLegacyDifficultyCalculator(new ModA(), new ModB(), new ModIncompatibleWithA(), new ModIncompatibleWithAAndB()).CreateDifficultyAdjustmentModCombinations();
|
||||
|
||||
Assert.AreEqual(8, combinations.Length);
|
||||
Assert.IsTrue(combinations[0] is ModNoMod);
|
||||
@ -83,7 +86,7 @@ namespace osu.Game.Tests.NonVisual
|
||||
[Test]
|
||||
public void TestIncompatibleThroughBaseType()
|
||||
{
|
||||
var combinations = new TestDifficultyCalculator(new ModAofA(), new ModIncompatibleWithAofA()).CreateDifficultyAdjustmentModCombinations();
|
||||
var combinations = new TestLegacyDifficultyCalculator(new ModAofA(), new ModIncompatibleWithAofA()).CreateDifficultyAdjustmentModCombinations();
|
||||
|
||||
Assert.AreEqual(3, combinations.Length);
|
||||
Assert.IsTrue(combinations[0] is ModNoMod);
|
||||
@ -136,9 +139,9 @@ namespace osu.Game.Tests.NonVisual
|
||||
public override Type[] IncompatibleMods => new[] { typeof(ModA), typeof(ModB) };
|
||||
}
|
||||
|
||||
private class TestDifficultyCalculator : DifficultyCalculator
|
||||
private class TestLegacyDifficultyCalculator : DifficultyCalculator
|
||||
{
|
||||
public TestDifficultyCalculator(params Mod[] mods)
|
||||
public TestLegacyDifficultyCalculator(params Mod[] mods)
|
||||
: base(null, null)
|
||||
{
|
||||
DifficultyAdjustmentMods = mods;
|
||||
@ -146,7 +149,20 @@ namespace osu.Game.Tests.NonVisual
|
||||
|
||||
protected override Mod[] DifficultyAdjustmentMods { get; }
|
||||
|
||||
protected override DifficultyAttributes Calculate(IBeatmap beatmap, Mod[] mods, double timeRate) => throw new NotImplementedException();
|
||||
protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills, double clockRate)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
protected override IEnumerable<DifficultyHitObject> CreateDifficultyHitObjects(IBeatmap beatmap, double clockRate)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
protected override Skill[] CreateSkills(IBeatmap beatmap)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
115
osu.Game.Tests/NonVisual/LimitedCapacityStackTest.cs
Normal file
115
osu.Game.Tests/NonVisual/LimitedCapacityStackTest.cs
Normal file
@ -0,0 +1,115 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using NUnit.Framework;
|
||||
using osu.Game.Rulesets.Difficulty.Utils;
|
||||
|
||||
namespace osu.Game.Tests.NonVisual
|
||||
{
|
||||
[TestFixture]
|
||||
public class LimitedCapacityStackTest
|
||||
{
|
||||
private const int capacity = 3;
|
||||
|
||||
private LimitedCapacityStack<int> stack;
|
||||
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
stack = new LimitedCapacityStack<int>(capacity);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestEmptyStack()
|
||||
{
|
||||
Assert.AreEqual(0, stack.Count);
|
||||
|
||||
Assert.Throws<IndexOutOfRangeException>(() =>
|
||||
{
|
||||
int unused = stack[0];
|
||||
});
|
||||
|
||||
int count = 0;
|
||||
foreach (var unused in stack)
|
||||
count++;
|
||||
|
||||
Assert.AreEqual(0, count);
|
||||
}
|
||||
|
||||
[TestCase(1)]
|
||||
[TestCase(2)]
|
||||
[TestCase(3)]
|
||||
public void TestInRangeElements(int count)
|
||||
{
|
||||
// e.g. 0 -> 1 -> 2
|
||||
for (int i = 0; i < count; i++)
|
||||
stack.Push(i);
|
||||
|
||||
Assert.AreEqual(count, stack.Count);
|
||||
|
||||
// e.g. 2 -> 1 -> 0 (reverse order)
|
||||
for (int i = 0; i < stack.Count; i++)
|
||||
Assert.AreEqual(count - 1 - i, stack[i]);
|
||||
|
||||
// e.g. indices 3, 4, 5, 6 (out of range)
|
||||
for (int i = stack.Count; i < stack.Count + capacity; i++)
|
||||
{
|
||||
Assert.Throws<IndexOutOfRangeException>(() =>
|
||||
{
|
||||
int unused = stack[i];
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
[TestCase(4)]
|
||||
[TestCase(5)]
|
||||
[TestCase(6)]
|
||||
public void TestOverflowElements(int count)
|
||||
{
|
||||
// e.g. 0 -> 1 -> 2 -> 3
|
||||
for (int i = 0; i < count; i++)
|
||||
stack.Push(i);
|
||||
|
||||
Assert.AreEqual(capacity, stack.Count);
|
||||
|
||||
// e.g. 3 -> 2 -> 1 (reverse order)
|
||||
for (int i = 0; i < stack.Count; i++)
|
||||
Assert.AreEqual(count - 1 - i, stack[i]);
|
||||
|
||||
// e.g. indices 3, 4, 5, 6 (out of range)
|
||||
for (int i = stack.Count; i < stack.Count + capacity; i++)
|
||||
{
|
||||
Assert.Throws<IndexOutOfRangeException>(() =>
|
||||
{
|
||||
int unused = stack[i];
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
[TestCase(1)]
|
||||
[TestCase(2)]
|
||||
[TestCase(3)]
|
||||
[TestCase(4)]
|
||||
[TestCase(5)]
|
||||
[TestCase(6)]
|
||||
public void TestEnumerator(int count)
|
||||
{
|
||||
// e.g. 0 -> 1 -> 2 -> 3
|
||||
for (int i = 0; i < count; i++)
|
||||
stack.Push(i);
|
||||
|
||||
int enumeratorCount = 0;
|
||||
int expectedValue = count - 1;
|
||||
|
||||
foreach (var item in stack)
|
||||
{
|
||||
Assert.AreEqual(expectedValue, item);
|
||||
enumeratorCount++;
|
||||
expectedValue--;
|
||||
}
|
||||
|
||||
Assert.AreEqual(stack.Count, enumeratorCount);
|
||||
}
|
||||
}
|
||||
}
|
@ -27,7 +27,7 @@ namespace osu.Game.Tests.Visual
|
||||
protected override void AddCheckSteps(Func<Player> player)
|
||||
{
|
||||
base.AddCheckSteps(player);
|
||||
AddUntilStep(() => ((ScoreAccessiblePlayer)player()).ScoreProcessor.TotalScore > 0, "score above zero");
|
||||
AddUntilStep(() => ((ScoreAccessiblePlayer)player()).ScoreProcessor.TotalScore.Value > 0, "score above zero");
|
||||
AddUntilStep(() => ((ScoreAccessiblePlayer)player()).HUDOverlay.KeyCounter.Children.Any(kc => kc.CountPresses > 0), "key counter counted keys");
|
||||
}
|
||||
|
||||
|
@ -15,6 +15,7 @@ using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Overlays;
|
||||
using osuTK.Graphics;
|
||||
using osu.Framework.Lists;
|
||||
using osu.Game.Graphics;
|
||||
|
||||
namespace osu.Game.Tests.Visual
|
||||
{
|
||||
@ -196,8 +197,8 @@ namespace osu.Game.Tests.Visual
|
||||
{
|
||||
AutoSizeAxes = Axes.Both;
|
||||
Direction = FillDirection.Horizontal;
|
||||
Add(new OsuSpriteText { Text = header + @": ", TextSize = text_size });
|
||||
Add(valueText = new OsuSpriteText { TextSize = text_size });
|
||||
Add(new OsuSpriteText { Text = header + @": ", Font = OsuFont.GetFont(size: text_size) });
|
||||
Add(valueText = new OsuSpriteText { Font = OsuFont.GetFont(size: text_size) });
|
||||
Margin = new MarginPadding(margin);
|
||||
}
|
||||
}
|
||||
|
@ -147,7 +147,7 @@ namespace osu.Game.Tests.Visual
|
||||
|
||||
private bool selectedBeatmapVisible()
|
||||
{
|
||||
var currentlySelected = carousel.Items.Find(s => s.Item is CarouselBeatmap && s.Item.State == CarouselItemState.Selected);
|
||||
var currentlySelected = carousel.Items.Find(s => s.Item is CarouselBeatmap && s.Item.State.Value == CarouselItemState.Selected);
|
||||
if (currentlySelected == null)
|
||||
return true;
|
||||
return currentlySelected.Item.Visible;
|
||||
|
@ -50,7 +50,7 @@ namespace osu.Game.Tests.Visual
|
||||
AddStep("show", () =>
|
||||
{
|
||||
infoWedge.State = Visibility.Visible;
|
||||
infoWedge.Beatmap = Beatmap;
|
||||
infoWedge.Beatmap = Beatmap.Value;
|
||||
});
|
||||
|
||||
// select part is redundant, but wait for load isn't
|
||||
|
@ -69,7 +69,7 @@ namespace osu.Game.Tests.Visual
|
||||
});
|
||||
|
||||
channelTabControl.OnRequestLeave += channel => channelTabControl.RemoveChannel(channel);
|
||||
channelTabControl.Current.ValueChanged += channel => currentText.Text = "Currently selected channel: " + channel.ToString();
|
||||
channelTabControl.Current.ValueChanged += channel => currentText.Text = "Currently selected channel: " + channel.NewValue.ToString();
|
||||
|
||||
AddStep("Add random private channel", addRandomPrivateChannel);
|
||||
AddAssert("There is only one channels", () => channelTabControl.Items.Count() == 2);
|
||||
|
@ -13,10 +13,10 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Framework.Configuration;
|
||||
|
||||
namespace osu.Game.Tests.Visual
|
||||
{
|
||||
@ -96,7 +96,7 @@ namespace osu.Game.Tests.Visual
|
||||
return true;
|
||||
}
|
||||
|
||||
bool isItalic() => newLine.ContentFlow.Where(d => d is OsuSpriteText).Cast<OsuSpriteText>().All(sprite => sprite.Font == "Exo2.0-MediumItalic");
|
||||
bool isItalic() => newLine.ContentFlow.Where(d => d is OsuSpriteText).Cast<OsuSpriteText>().All(sprite => sprite.Font.Italics);
|
||||
|
||||
bool isShowingLinks()
|
||||
{
|
||||
|
@ -2,27 +2,16 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Game.Screens.Menu;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Tests.Visual
|
||||
{
|
||||
public class TestCaseDisclaimer : OsuTestCase
|
||||
public class TestCaseDisclaimer : ScreenTestCase
|
||||
{
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = Color4.Black,
|
||||
},
|
||||
new Disclaimer()
|
||||
};
|
||||
LoadScreen(new Disclaimer());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -60,7 +60,7 @@ namespace osu.Game.Tests.Visual
|
||||
}
|
||||
};
|
||||
|
||||
drawableDate.Current.ValueChanged += v => flash.FadeOutFromOne(500);
|
||||
drawableDate.Current.ValueChanged += _ => flash.FadeOutFromOne(500);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -9,11 +9,11 @@ using osu.Game.Screens.Tournament.Teams;
|
||||
namespace osu.Game.Tests.Visual
|
||||
{
|
||||
[Description("for tournament use")]
|
||||
public class TestCaseDrawings : OsuTestCase
|
||||
public class TestCaseDrawings : ScreenTestCase
|
||||
{
|
||||
public TestCaseDrawings()
|
||||
{
|
||||
Add(new Drawings
|
||||
LoadScreen(new Drawings
|
||||
{
|
||||
TeamList = new TestTeamList(),
|
||||
});
|
||||
|
@ -19,7 +19,7 @@ namespace osu.Game.Tests.Visual
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
Beatmap.Value = new TestWorkingBeatmap(new OsuRuleset().RulesetInfo);
|
||||
Beatmap.Value = new TestWorkingBeatmap(new OsuRuleset().RulesetInfo, Clock);
|
||||
Child = new ComposeScreen();
|
||||
}
|
||||
}
|
||||
|
@ -5,7 +5,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Configuration;
|
||||
using osu.Framework.Bindables;
|
||||
using osuTK;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
@ -85,7 +85,7 @@ namespace osu.Game.Tests.Visual
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(IAdjustableClock adjustableClock, IBindableBeatmap beatmap)
|
||||
private void load(IAdjustableClock adjustableClock, IBindable<WorkingBeatmap> beatmap)
|
||||
{
|
||||
this.adjustableClock = adjustableClock;
|
||||
this.beatmap.BindTo(beatmap);
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user