1
0
mirror of https://github.com/ppy/osu.git synced 2026-05-15 05:02:33 +08:00

Compare commits

...

432 Commits

227 changed files with 6655 additions and 3272 deletions
+23 -7
View File
@@ -11,7 +11,11 @@
],
"cwd": "${workspaceRoot}",
"preLaunchTask": "Build tests (Debug)",
"env": {},
"linux": {
"env": {
"LD_LIBRARY_PATH": "${workspaceRoot}/osu.Game.Tests/bin/Debug/netcoreapp2.1:${env:LD_LIBRARY_PATH}"
}
},
"console": "internalConsole"
},
{
@@ -24,7 +28,11 @@
],
"cwd": "${workspaceRoot}",
"preLaunchTask": "Build tests (Release)",
"env": {},
"linux": {
"env": {
"LD_LIBRARY_PATH": "${workspaceRoot}/osu.Game.Tests/bin/Release/netcoreapp2.1:${env:LD_LIBRARY_PATH}"
}
},
"console": "internalConsole"
},
{
@@ -33,11 +41,15 @@
"request": "launch",
"program": "dotnet",
"args": [
"${workspaceRoot}/osu.Desktop/bin/Debug/netcoreapp2.1/osu!.dll",
"${workspaceRoot}/osu.Desktop/bin/Debug/netcoreapp2.1/osu!.dll"
],
"cwd": "${workspaceRoot}",
"preLaunchTask": "Build osu! (Debug)",
"env": {},
"linux": {
"env": {
"LD_LIBRARY_PATH": "${workspaceRoot}/osu.Desktop/bin/Debug/netcoreapp2.1:${env:LD_LIBRARY_PATH}"
}
},
"console": "internalConsole"
},
{
@@ -46,12 +58,16 @@
"request": "launch",
"program": "dotnet",
"args": [
"${workspaceRoot}/osu.Desktop/bin/Release/netcoreapp2.1/osu!.dll",
"${workspaceRoot}/osu.Desktop/bin/Release/netcoreapp2.1/osu!.dll"
],
"cwd": "${workspaceRoot}",
"preLaunchTask": "Build osu! (Release)",
"env": {},
"linux": {
"env": {
"LD_LIBRARY_PATH": "${workspaceRoot}/osu.Desktop/bin/Release/netcoreapp2.1:${env:LD_LIBRARY_PATH}"
}
},
"console": "internalConsole"
}
]
}
}
+20 -6
View File
@@ -1,18 +1,32 @@
# osu! [![Build status](https://ci.appveyor.com/api/projects/status/u2p01nx7l6og8buh?svg=true)](https://ci.appveyor.com/project/peppy/osu) [![CodeFactor](https://www.codefactor.io/repository/github/ppy/osu/badge)](https://www.codefactor.io/repository/github/ppy/osu) [![dev chat](https://discordapp.com/api/guilds/188630481301012481/widget.png?style=shield)](https://discord.gg/ppy)
Rhythm is just a *click* away. The future of [osu!](https://osu.ppy.sh) and the beginning of an open era!
Rhythm is just a *click* away. The future of [osu!](https://osu.ppy.sh) and the beginning of an open era! Commonly known by the codename "osu!lazer". Pew pew.
# Status
This is still heavily under development and is not intended for end-user use. This repository is intended for developer collaboration. You're welcome to try and use it but please do not submit bug reports without a patch. Please do not ask for help building or using this software.
This project is still heavily under development, but is in a state where users are encouraged to try it out and keep it installed alongside the stable osu! client. It will continue to evolve over the coming months and hopefully bring some new unique features to the table.
We are accepting bug reports (please report with as much detail as possible). Feature requests are welcome as long as you read and understand the contribution guidelines listed below.
# Requirements
- A desktop platform that can compile .NET 4.7.1. We recommend using [Visual Studio Community Edition](https://www.visualstudio.com/) (Windows), [Visual Studio for Mac](https://www.visualstudio.com/vs/visual-studio-mac/) (macOS) or [MonoDevelop](http://www.monodevelop.com/download/) (Linux), all of which are free. [Visual Studio Code](https://code.visualstudio.com/) may also be used but requires further setup steps which are not covered here.
- A desktop platform with the [.NET Core SDK 2.1](https://www.microsoft.com/net/learn/get-started) or higher installed.
- When working with the codebase, we recommend using an IDE with intellisense and syntax highlighting, such as [Visual Studio Community Edition](https://www.visualstudio.com/) (Windows), [Visual Studio Code](https://code.visualstudio.com/) (with the C# plugin installed) or [Jetbrains Rider](https://www.jetbrains.com/rider/) (commercial).
# Getting Started
- Clone the repository including submodules (`git clone --recurse-submodules https://github.com/ppy/osu`)
- Build in your IDE of choice (recommended IDEs automatically restore nuget packages; if you are using an alternative make sure to `nuget restore`)
# Building and running
If you are not interested in developing the game, please head over to the [releases](https://github.com/ppy/osu/releases) to download a precompiled build with automatic updating enabled (download and run the install executable for your platform).
Clone the repository including submodules
`git clone --recurse-submodules https://github.com/ppy/osu`
Build and run
- Using Visual Studio 2017, Rider or Visual Studio Code (configurations are included)
- From command line using `dotnet run --project osu.Desktop --framework netcoreapp2.1`
The above methods should automatically do so, but if you run into issues building you may need to restore nuget packages (commonly via `dotnet restore`).
# Contributing
+47 -40
View File
@@ -12,6 +12,7 @@ using osu.Framework.Platform;
using osu.Game;
using OpenTK.Input;
using Microsoft.Win32;
using osu.Desktop.Updater;
using osu.Framework.Platform.Windows;
namespace osu.Desktop
@@ -38,6 +39,52 @@ namespace osu.Desktop
}
}
protected override void LoadComplete()
{
base.LoadComplete();
if (!noVersionOverlay)
{
LoadComponentAsync(new VersionManager { Depth = int.MinValue }, v =>
{
Add(v);
v.State = Visibility.Visible;
});
#if NET_FRAMEWORK
Add(new SquirrelUpdateManager());
#else
Add(new SimpleUpdateManager());
#endif
}
}
public override void SetHost(GameHost host)
{
base.SetHost(host);
var desktopWindow = host.Window as DesktopGameWindow;
if (desktopWindow != null)
{
desktopWindow.CursorState |= CursorState.Hidden;
desktopWindow.SetIconFromStream(Assembly.GetExecutingAssembly().GetManifestResourceStream(GetType(), "lazer.ico"));
desktopWindow.Title = Name;
desktopWindow.FileDrop += fileDrop;
}
}
private void fileDrop(object sender, FileDropEventArgs e)
{
var filePaths = new[] { e.FileName };
var firstExtension = Path.GetExtension(filePaths.First());
if (filePaths.Any(f => Path.GetExtension(f) != firstExtension)) return;
Task.Factory.StartNew(() => Import(filePaths), TaskCreationOptions.LongRunning);
}
/// <summary>
/// A method of accessing an osu-stable install in a controlled fashion.
/// </summary>
@@ -77,45 +124,5 @@ namespace osu.Desktop
{
}
}
protected override void LoadComplete()
{
base.LoadComplete();
if (!noVersionOverlay)
{
LoadComponentAsync(new VersionManager { Depth = int.MinValue }, v =>
{
Add(v);
v.State = Visibility.Visible;
});
}
}
public override void SetHost(GameHost host)
{
base.SetHost(host);
var desktopWindow = host.Window as DesktopGameWindow;
if (desktopWindow != null)
{
desktopWindow.CursorState |= CursorState.Hidden;
desktopWindow.SetIconFromStream(Assembly.GetExecutingAssembly().GetManifestResourceStream(GetType(), "lazer.ico"));
desktopWindow.Title = Name;
desktopWindow.FileDrop += fileDrop;
}
}
private void fileDrop(object sender, FileDropEventArgs e)
{
var filePaths = new[] { e.FileName };
var firstExtension = Path.GetExtension(filePaths.First());
if (filePaths.Any(f => Path.GetExtension(f) != firstExtension)) return;
Task.Factory.StartNew(() => Import(filePaths), TaskCreationOptions.LongRunning);
}
}
}
-4
View File
@@ -91,10 +91,6 @@ namespace osu.Desktop.Overlays
}
}
};
#if NET_FRAMEWORK
Add(new SquirrelUpdateManager());
#endif
}
protected override void LoadComplete()
+3 -5
View File
@@ -7,6 +7,7 @@ using System.Linq;
using osu.Framework;
using osu.Framework.Platform;
using osu.Game.IPC;
#if NET_FRAMEWORK
using System.Runtime;
#endif
@@ -18,10 +19,7 @@ namespace osu.Desktop
[STAThread]
public static int Main(string[] args)
{
// required to initialise native SQLite libraries on some platforms.
if (!RuntimeInfo.IsMono)
useMulticoreJit();
useMultiCoreJit();
// Back up the cwd before DesktopGameHost changes it
var cwd = Environment.CurrentDirectory;
@@ -54,7 +52,7 @@ namespace osu.Desktop
}
}
private static void useMulticoreJit()
private static void useMultiCoreJit()
{
#if NET_FRAMEWORK
var directory = Directory.CreateDirectory(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Profiles"));
+103
View File
@@ -0,0 +1,103 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Newtonsoft.Json;
using osu.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics.Containers;
using osu.Framework.IO.Network;
using osu.Framework.Platform;
using osu.Game;
using osu.Game.Graphics;
using osu.Game.Overlays;
using osu.Game.Overlays.Notifications;
namespace osu.Desktop.Updater
{
/// <summary>
/// An update manager that shows notifications if a newer release is detected.
/// Installation is left up to the user.
/// </summary>
internal class SimpleUpdateManager : CompositeDrawable
{
private NotificationOverlay notificationOverlay;
private string version;
private GameHost host;
[BackgroundDependencyLoader]
private void load(NotificationOverlay notification, OsuGameBase game, GameHost host)
{
notificationOverlay = notification;
this.host = host;
version = game.Version;
if (game.IsDeployedBuild)
Schedule(() => Task.Run(() => checkForUpdateAsync()));
}
private async void checkForUpdateAsync()
{
var releases = new JsonWebRequest<GitHubRelease>("https://api.github.com/repos/ppy/osu/releases/latest");
await releases.PerformAsync();
var latest = releases.ResponseObject;
if (latest.TagName != version)
{
notificationOverlay.Post(new SimpleNotification
{
Text = $"A newer release of osu! has been found ({version} → {latest.TagName}).\n\n"
+ "Click here to download the new version, which can be installed over the top of your existing installation",
Icon = FontAwesome.fa_upload,
Activated = () =>
{
host.OpenUrlExternally(getBestUrl(latest));
return true;
}
});
}
}
private string getBestUrl(GitHubRelease release)
{
GitHubAsset bestAsset = null;
switch (RuntimeInfo.OS)
{
case RuntimeInfo.Platform.Windows:
bestAsset = release.Assets?.FirstOrDefault(f => f.Name.EndsWith(".exe"));
break;
case RuntimeInfo.Platform.MacOsx:
bestAsset = release.Assets?.FirstOrDefault(f => f.Name.EndsWith(".dmg"));
break;
}
return bestAsset?.BrowserDownloadUrl ?? release.HtmlUrl;
}
public class GitHubRelease
{
[JsonProperty("html_url")]
public string HtmlUrl { get; set; }
[JsonProperty("tag_name")]
public string TagName { get; set; }
[JsonProperty("assets")]
public List<GitHubAsset> Assets { get; set; }
}
public class GitHubAsset
{
[JsonProperty("name")]
public string Name { get; set; }
[JsonProperty("browser_download_url")]
public string BrowserDownloadUrl { get; set; }
}
}
}
@@ -3,6 +3,7 @@
#if NET_FRAMEWORK
using System;
using System.Threading.Tasks;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Colour;
@@ -16,7 +17,7 @@ using OpenTK;
using OpenTK.Graphics;
using Squirrel;
namespace osu.Desktop.Overlays
namespace osu.Desktop.Updater
{
public class SquirrelUpdateManager : Component
{
@@ -35,7 +36,7 @@ namespace osu.Desktop.Overlays
notificationOverlay = notification;
if (game.IsDeployedBuild)
Schedule(() => checkForUpdateAsync());
Schedule(() => Task.Run(() => checkForUpdateAsync()));
}
private async void checkForUpdateAsync(bool useDeltaPatching = true, UpdateProgressNotification notification = null)
+1 -1
View File
@@ -29,7 +29,7 @@
<PackageReference Include="Microsoft.Win32.Registry" Version="4.5.0" />
</ItemGroup>
<ItemGroup Label="Package References">
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="2.1.1" />
<PackageReference Include="squirrel.windows" Version="1.8.0" Condition="'$(TargetFramework)' == 'net471'" />
</ItemGroup>
@@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using NUnit.Framework;
using osu.Framework.MathUtils;
using osu.Game.Rulesets.Catch.Objects;
@@ -12,11 +13,14 @@ using osu.Game.Tests.Beatmaps;
namespace osu.Game.Rulesets.Catch.Tests
{
internal class CatchBeatmapConversionTest : BeatmapConversionTest<ConvertValue>
[TestFixture]
public class CatchBeatmapConversionTest : BeatmapConversionTest<ConvertValue>
{
protected override string ResourceAssembly => "osu.Game.Rulesets.Catch";
[TestCase("basic"), Ignore("See: https://github.com/ppy/osu/issues/2232")]
[TestCase("basic")]
[TestCase("spinner")]
[TestCase("spinner-and-circles")]
public new void Test(string name)
{
base.Test(name);
@@ -27,36 +31,52 @@ namespace osu.Game.Rulesets.Catch.Tests
if (hitObject is JuiceStream stream)
{
foreach (var nested in stream.NestedHitObjects)
{
yield return new ConvertValue
{
StartTime = nested.StartTime,
Position = ((CatchHitObject)nested).X * CatchPlayfield.BASE_WIDTH
};
}
yield return new ConvertValue((CatchHitObject)nested);
}
else if (hitObject is BananaShower shower)
{
foreach (var nested in shower.NestedHitObjects)
yield return new ConvertValue((CatchHitObject)nested);
}
else
{
yield return new ConvertValue
{
StartTime = hitObject.StartTime,
Position = ((CatchHitObject)hitObject).X * CatchPlayfield.BASE_WIDTH
};
}
yield return new ConvertValue((CatchHitObject)hitObject);
}
protected override Ruleset CreateRuleset() => new CatchRuleset();
}
internal struct ConvertValue : IEquatable<ConvertValue>
public struct ConvertValue : IEquatable<ConvertValue>
{
/// <summary>
/// A sane value to account for osu!stable using ints everwhere.
/// </summary>
private const float conversion_lenience = 2;
public double StartTime;
public float Position;
[JsonIgnore]
public readonly CatchHitObject HitObject;
public ConvertValue(CatchHitObject hitObject)
{
HitObject = hitObject;
startTime = 0;
position = 0;
}
private double startTime;
public double StartTime
{
get => HitObject?.StartTime ?? startTime;
set => startTime = value;
}
private float position;
public float Position
{
get => HitObject?.X * CatchPlayfield.BASE_WIDTH ?? position;
set => position = value;
}
public bool Equals(ConvertValue other)
=> Precision.AlmostEquals(StartTime, other.StartTime, conversion_lenience)
@@ -55,7 +55,7 @@ namespace osu.Game.Rulesets.Catch.Tests
{
}
public void ToggleHyperDash(bool status) => MovableCatcher.HyperDashModifier = status ? 2 : 1;
public void ToggleHyperDash(bool status) => MovableCatcher.SetHyperDashState(status ? 2 : 1);
}
}
}
@@ -55,7 +55,7 @@ namespace osu.Game.Rulesets.Catch.Tests
private DrawableFruit createDrawable(int index)
{
Fruit fruit = index == 5
? new BananaShower.Banana
? new Banana
{
StartTime = 1000000000000,
IndexInBeatmap = index,
@@ -8,9 +8,9 @@ using osu.Game.Rulesets.Catch.Objects;
namespace osu.Game.Rulesets.Catch.Tests
{
[TestFixture]
public class TestCaseHyperdash : Game.Tests.Visual.TestCasePlayer
public class TestCaseHyperDash : Game.Tests.Visual.TestCasePlayer
{
public TestCaseHyperdash()
public TestCaseHyperDash()
: base(new CatchRuleset())
{
}
@@ -26,22 +26,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
var positionData = obj as IHasXPosition;
var comboData = obj as IHasCombo;
var endTime = obj as IHasEndTime;
if (positionData == null)
{
if (endTime != null)
{
yield return new BananaShower
{
StartTime = obj.StartTime,
Samples = obj.Samples,
Duration = endTime.Duration,
NewCombo = comboData?.NewCombo ?? false
};
}
yield break;
}
var legacyOffset = obj as IHasLegacyLastTickOffset;
if (curveData != null)
{
@@ -54,20 +39,31 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
Distance = curveData.Distance,
RepeatSamples = curveData.RepeatSamples,
RepeatCount = curveData.RepeatCount,
X = positionData.X / CatchPlayfield.BASE_WIDTH,
X = (positionData?.X ?? 0) / CatchPlayfield.BASE_WIDTH,
NewCombo = comboData?.NewCombo ?? false,
LegacyLastTickOffset = legacyOffset?.LegacyLastTickOffset ?? 0
};
}
else if (endTime != null)
{
yield return new BananaShower
{
StartTime = obj.StartTime,
Samples = obj.Samples,
Duration = endTime.Duration,
NewCombo = comboData?.NewCombo ?? false
};
yield break;
}
yield return new Fruit
else
{
StartTime = obj.StartTime,
Samples = obj.Samples,
NewCombo = comboData?.NewCombo ?? false,
X = positionData.X / CatchPlayfield.BASE_WIDTH
};
yield return new Fruit
{
StartTime = obj.StartTime,
Samples = obj.Samples,
NewCombo = comboData?.NewCombo ?? false,
X = (positionData?.X ?? 0) / CatchPlayfield.BASE_WIDTH
};
}
}
protected override Beatmap<CatchHitObject> CreateBeatmap() => new CatchBeatmap();
@@ -9,11 +9,14 @@ using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Catch.UI;
using osu.Game.Rulesets.Objects.Types;
using OpenTK;
using osu.Game.Rulesets.Catch.MathUtils;
namespace osu.Game.Rulesets.Catch.Beatmaps
{
public class CatchBeatmapProcessor : BeatmapProcessor
{
public const int RNG_SEED = 1337;
public CatchBeatmapProcessor(IBeatmap beatmap)
: base(beatmap)
{
@@ -21,13 +24,52 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
public override void PostProcess()
{
initialiseHyperDash((List<CatchHitObject>)Beatmap.HitObjects);
base.PostProcess();
applyPositionOffsets();
initialiseHyperDash((List<CatchHitObject>)Beatmap.HitObjects);
int index = 0;
foreach (var obj in Beatmap.HitObjects.OfType<CatchHitObject>())
{
obj.IndexInBeatmap = index++;
if (obj.LastInCombo && obj.NestedHitObjects.LastOrDefault() is IHasComboInformation lastNested)
lastNested.LastInCombo = true;
}
}
private void applyPositionOffsets()
{
var rng = new FastRandom(RNG_SEED);
// todo: HardRock displacement should be applied here
foreach (var obj in Beatmap.HitObjects)
{
switch (obj)
{
case BananaShower bananaShower:
foreach (var banana in bananaShower.NestedHitObjects.OfType<Banana>())
{
banana.X = (float)rng.NextDouble();
rng.Next(); // osu!stable retrieved a random banana type
rng.Next(); // osu!stable retrieved a random banana rotation
rng.Next(); // osu!stable retrieved a random banana colour
}
break;
case JuiceStream juiceStream:
foreach (var nested in juiceStream.NestedHitObjects)
{
var hitObject = (CatchHitObject)nested;
if (hitObject is TinyDroplet)
hitObject.X += rng.Next(-20, 20) / CatchPlayfield.BASE_WIDTH;
else if (hitObject is Droplet)
rng.Next(); // osu!stable retrieved a random droplet rotation
hitObject.X = MathHelper.Clamp(hitObject.X, 0, 1);
}
break;
}
}
}
private void initialiseHyperDash(List<CatchHitObject> objects)
@@ -61,12 +61,12 @@ namespace osu.Game.Rulesets.Catch.Difficulty
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 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)
{
ApproachRate = preEmpt > 1200.0 ? -(preEmpt - 1800.0) / 120.0 : -(preEmpt - 1200.0) / 150.0 + 5.0,
ApproachRate = preempt > 1200.0 ? -(preempt - 1800.0) / 120.0 : -(preempt - 1200.0) / 150.0 + 5.0,
MaxCombo = difficultyHitObjects.Count
};
}
@@ -0,0 +1,36 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Catch.Judgements
{
public class CatchBananaJudgement : CatchJudgement
{
public override bool AffectsCombo => false;
public override bool ShouldExplode => true;
protected override int NumericResultFor(HitResult result)
{
switch (result)
{
default:
return 0;
case HitResult.Perfect:
return 1100;
}
}
protected override float HealthIncreaseFor(HitResult result)
{
switch (result)
{
default:
return 0;
case HitResult.Perfect:
return 8;
}
}
}
}
@@ -0,0 +1,32 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Catch.Judgements
{
public class CatchDropletJudgement : CatchJudgement
{
protected override int NumericResultFor(HitResult result)
{
switch (result)
{
default:
return 0;
case HitResult.Perfect:
return 30;
}
}
protected override float HealthIncreaseFor(HitResult result)
{
switch (result)
{
default:
return 0;
case HitResult.Perfect:
return 7;
}
}
}
}
@@ -2,11 +2,51 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Catch.Judgements
{
public class CatchJudgement : Judgement
{
// todo: wangs
public override HitResult MaxResult => HitResult.Perfect;
protected override int NumericResultFor(HitResult result)
{
switch (result)
{
default:
return 0;
case HitResult.Perfect:
return 300;
}
}
/// <summary>
/// The base health increase for the result achieved.
/// </summary>
public float HealthIncrease => HealthIncreaseFor(Result);
/// <summary>
/// Whether fruit on the platter should explode or drop.
/// Note that this is only checked if the owning object is also <see cref="IHasComboInformation.LastInCombo" />
/// </summary>
public virtual bool ShouldExplode => IsHit;
/// <summary>
/// Convert a <see cref="HitResult"/> to a base health increase.
/// </summary>
/// <param name="result">The value to convert.</param>
/// <returns>The base health increase.</returns>
protected virtual float HealthIncreaseFor(HitResult result)
{
switch (result)
{
default:
return 0;
case HitResult.Perfect:
return 10.2f;
}
}
}
}
@@ -0,0 +1,34 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Catch.Judgements
{
public class CatchTinyDropletJudgement : CatchJudgement
{
public override bool AffectsCombo => false;
protected override int NumericResultFor(HitResult result)
{
switch (result)
{
default:
return 0;
case HitResult.Perfect:
return 10;
}
}
protected override float HealthIncreaseFor(HitResult result)
{
switch (result)
{
default:
return 0;
case HitResult.Perfect:
return 4;
}
}
}
}
@@ -0,0 +1,91 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
namespace osu.Game.Rulesets.Catch.MathUtils
{
/// <summary>
/// A PRNG specified in http://heliosphan.org/fastrandom.html.
/// </summary>
public class FastRandom
{
private const double int_to_real = 1.0 / (int.MaxValue + 1.0);
private const uint int_mask = 0x7FFFFFFF;
private const uint y = 842502087;
private const uint z = 3579807591;
private const uint w = 273326509;
private uint _x, _y = y, _z = z, _w = w;
public FastRandom(int seed)
{
_x = (uint)seed;
}
public FastRandom()
: this(Environment.TickCount)
{
}
/// <summary>
/// Generates a random unsigned integer within the range [<see cref="uint.MinValue"/>, <see cref="uint.MaxValue"/>).
/// </summary>
/// <returns>The random value.</returns>
public uint NextUInt()
{
uint t = _x ^ _x << 11;
_x = _y;
_y = _z;
_z = _w;
return _w = _w ^ _w >> 19 ^ t ^ t >> 8;
}
/// <summary>
/// Generates a random integer value within the range [0, <see cref="int.MaxValue"/>).
/// </summary>
/// <returns>The random value.</returns>
public int Next() => (int)(int_mask & NextUInt());
/// <summary>
/// Generates a random integer value within the range [0, <paramref name="upperBound"/>).
/// </summary>
/// <param name="upperBound">The upper bound.</param>
/// <returns>The random value.</returns>
public int Next(int upperBound) => (int)(NextDouble() * upperBound);
/// <summary>
/// Generates a random integer value within the range [<paramref name="lowerBound"/>, <paramref name="upperBound"/>).
/// </summary>
/// <param name="lowerBound">The lower bound of the range.</param>
/// <param name="upperBound">The upper bound of the range.</param>
/// <returns>The random value.</returns>
public int Next(int lowerBound, int upperBound) => (int)(lowerBound + NextDouble() * (upperBound - lowerBound));
/// <summary>
/// Generates a random double value within the range [0, 1).
/// </summary>
/// <returns>The random value.</returns>
public double NextDouble() => int_to_real * Next();
private uint bitBuffer;
private int bitIndex = 32;
/// <summary>
/// Generates a reandom boolean value. Cached such that a random value is only generated once in every 32 calls.
/// </summary>
/// <returns>The random value.</returns>
public bool NextBool()
{
if (bitIndex == 32)
{
bitBuffer = NextUInt();
bitIndex = 1;
return (bitBuffer & 1) == 1;
}
bitIndex++;
return ((bitBuffer >>= 1) & 1) == 1;
}
}
}
+10
View File
@@ -0,0 +1,10 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
namespace osu.Game.Rulesets.Catch.Objects
{
public class Banana : Fruit
{
public override FruitVisualRepresentation VisualRepresentation => FruitVisualRepresentation.Banana;
}
}
@@ -1,7 +1,6 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.MathUtils;
using osu.Game.Rulesets.Objects.Types;
namespace osu.Game.Rulesets.Catch.Objects
@@ -31,18 +30,12 @@ namespace osu.Game.Rulesets.Catch.Objects
AddNested(new Banana
{
Samples = Samples,
StartTime = i,
X = RNG.NextSingle()
StartTime = i
});
}
public double EndTime => StartTime + Duration;
public double Duration { get; set; }
public class Banana : Fruit
{
public override FruitVisualRepresentation VisualRepresentation => FruitVisualRepresentation.Banana;
}
}
}
@@ -0,0 +1,17 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Rulesets.Catch.Judgements;
namespace osu.Game.Rulesets.Catch.Objects.Drawable
{
public class DrawableBanana : DrawableFruit
{
public DrawableBanana(Banana h)
: base(h)
{
}
protected override CatchJudgement CreateJudgement() => new CatchBananaJudgement();
}
}
@@ -5,9 +5,7 @@ using System;
using System.Linq;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Catch.Objects.Drawable
{
@@ -24,15 +22,11 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
InternalChild = bananaContainer = new Container { RelativeSizeAxes = Axes.Both };
foreach (var b in s.NestedHitObjects.Cast<BananaShower.Banana>())
foreach (var b in s.NestedHitObjects.Cast<Banana>())
AddNested(getVisualRepresentation?.Invoke(b));
}
protected override void CheckForJudgements(bool userTriggered, double timeOffset)
{
if (timeOffset >= 0)
AddJudgement(new Judgement { Result = NestedHitObjects.Cast<DrawableCatchHitObject>().Any(n => n.Judgements.Any(j => j.IsHit)) ? HitResult.Perfect : HitResult.Miss });
}
protected override bool ProvidesJudgement => false;
protected override void AddNested(DrawableHitObject h)
{
@@ -2,14 +2,14 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using OpenTK;
using OpenTK.Graphics;
using osu.Framework.Graphics;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Catch.Judgements;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Objects.Types;
using OpenTK;
using osu.Game.Rulesets.Scoring;
using osu.Game.Skinning;
using OpenTK.Graphics;
namespace osu.Game.Rulesets.Catch.Objects.Drawable
{
@@ -58,9 +58,15 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
if (CheckPosition == null) return;
if (timeOffset >= 0)
AddJudgement(new Judgement { Result = CheckPosition.Invoke(HitObject) ? HitResult.Perfect : HitResult.Miss });
{
var judgement = CreateJudgement();
judgement.Result = CheckPosition.Invoke(HitObject) ? HitResult.Perfect : HitResult.Miss;
AddJudgement(judgement);
}
}
protected virtual CatchJudgement CreateJudgement() => new CatchJudgement();
protected override void SkinChanged(ISkinSource skin, bool allowFallback)
{
base.SkinChanged(skin, allowFallback);
@@ -6,6 +6,7 @@ using osu.Framework.Graphics;
using osu.Game.Rulesets.Catch.Objects.Drawable.Pieces;
using OpenTK;
using OpenTK.Graphics;
using osu.Game.Rulesets.Catch.Judgements;
namespace osu.Game.Rulesets.Catch.Objects.Drawable
{
@@ -23,6 +24,8 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
Masking = false;
}
protected override CatchJudgement CreateJudgement() => new CatchDropletJudgement();
[BackgroundDependencyLoader]
private void load()
{
@@ -0,0 +1,19 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using OpenTK;
using osu.Game.Rulesets.Catch.Judgements;
namespace osu.Game.Rulesets.Catch.Objects.Drawable
{
public class DrawableTinyDroplet : DrawableDroplet
{
public DrawableTinyDroplet(Droplet h)
: base(h)
{
Size = new Vector2((float)CatchHitObject.OBJECT_RADIUS) / 8;
}
protected override CatchJudgement CreateJudgement() => new CatchTinyDropletJudgement();
}
}
@@ -42,7 +42,6 @@ namespace osu.Game.Rulesets.Catch.Objects
protected override void CreateNestedHitObjects()
{
base.CreateNestedHitObjects();
createTicks();
}
@@ -78,6 +77,13 @@ namespace osu.Game.Rulesets.Catch.Objects
double time = spanStartTime + timeProgress * spanDuration;
if (LegacyLastTickOffset != null)
{
// If we're the last tick, apply the legacy offset
if (span == this.SpanCount() - 1 && d + tickDistance > length)
time = Math.Max(StartTime + Duration / 2, time - LegacyLastTickOffset.Value);
}
double tinyTickInterval = time - lastDropletTime;
while (tinyTickInterval > 100)
tinyTickInterval /= 2;
@@ -124,9 +130,6 @@ namespace osu.Game.Rulesets.Catch.Objects
X = X + Curve.PositionAt(reversed ? 0 : 1).X / CatchPlayfield.BASE_WIDTH
});
}
if (NestedHitObjects.LastOrDefault() is IHasComboInformation lastNested)
lastNested.LastInCombo = LastInCombo;
}
public double EndTime => StartTime + this.SpanCount() * Curve.Distance / Velocity;
@@ -156,5 +159,7 @@ namespace osu.Game.Rulesets.Catch.Objects
get { return Curve.CurveType; }
set { Curve.CurveType = value; }
}
public double? LegacyLastTickOffset { get; set; }
}
}
@@ -56,7 +56,7 @@ namespace osu.Game.Rulesets.Catch.Replays
return;
}
if (h is BananaShower.Banana)
if (h is Banana)
{
// auto bananas unrealistically warp to catch 100% combo.
Replay.Frames.Add(new CatchReplayFrame(h.StartTime, h.X));
@@ -105,7 +105,7 @@ namespace osu.Game.Rulesets.Catch.Replays
{
switch (nestedObj)
{
case BananaShower.Banana _:
case Banana _:
case TinyDroplet _:
case Droplet _:
case Fruit _:
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,65 @@
{
"Mappings": [{
"StartTime": 2589,
"Objects": [{
"StartTime": 2589,
"Position": 256
}]
},
{
"StartTime": 2915,
"Objects": [{
"StartTime": 2915,
"Position": 65
},
{
"StartTime": 2916,
"Position": 482
}
]
},
{
"StartTime": 3078,
"Objects": [{
"StartTime": 3078,
"Position": 164
},
{
"StartTime": 3079,
"Position": 315
}
]
},
{
"StartTime": 3241,
"Objects": [{
"StartTime": 3241,
"Position": 145
},
{
"StartTime": 3242,
"Position": 159
}
]
},
{
"StartTime": 3404,
"Objects": [{
"StartTime": 3404,
"Position": 310
},
{
"StartTime": 3405,
"Position": 441
}
]
},
{
"StartTime": 5197,
"Objects": [{
"StartTime": 5197,
"Position": 256
}]
}
]
}
@@ -0,0 +1,24 @@
osu file format v14
[General]
StackLeniency: 0.7
Mode: 2
[Difficulty]
HPDrainRate:5
CircleSize:2
OverallDifficulty:5
ApproachRate:8
SliderMultiplier:1.4
SliderTickRate:4
[TimingPoints]
2589,326.086956521739,4,2,1,70,1,0
[HitObjects]
256,192,2589,5,0,0:0:0:0:
256,192,2915,12,0,2916,0:0:0:0:
256,192,3078,12,0,3079,0:0:0:0:
256,192,3241,12,0,3242,0:0:0:0:
256,192,3404,12,0,3405,0:0:0:0:
256,192,5197,5,0,0:0:0:0:
@@ -0,0 +1,74 @@
{
"Mappings": [{
"StartTime": 18500,
"Objects": [{
"StartTime": 18500,
"Position": 65
},
{
"StartTime": 18559,
"Position": 482
},
{
"StartTime": 18618,
"Position": 164
},
{
"StartTime": 18678,
"Position": 315
},
{
"StartTime": 18737,
"Position": 145
},
{
"StartTime": 18796,
"Position": 159
},
{
"StartTime": 18856,
"Position": 310
},
{
"StartTime": 18915,
"Position": 441
},
{
"StartTime": 18975,
"Position": 428
},
{
"StartTime": 19034,
"Position": 243
},
{
"StartTime": 19093,
"Position": 422
},
{
"StartTime": 19153,
"Position": 481
},
{
"StartTime": 19212,
"Position": 104
},
{
"StartTime": 19271,
"Position": 473
},
{
"StartTime": 19331,
"Position": 135
},
{
"StartTime": 19390,
"Position": 360
},
{
"StartTime": 19450,
"Position": 123
}
]
}]
}
@@ -0,0 +1,20 @@
osu file format v14
[General]
Mode: 2
[Difficulty]
HPDrainRate:6
CircleSize:4
OverallDifficulty:7
ApproachRate:8.3
SliderMultiplier:1.6
SliderTickRate:1
[TimingPoints]
500,500,4,2,1,50,1,0
13426,-100,4,3,1,45,0,0
14884,-100,4,2,1,50,0,0
[HitObjects]
256,192,18500,12,0,19450,0:0:0:0:
@@ -1,10 +1,12 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using System.Linq;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Catch.Judgements;
using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.UI;
@@ -17,28 +19,57 @@ namespace osu.Game.Rulesets.Catch.Scoring
{
}
private float hpDrainRate;
protected override void SimulateAutoplay(Beatmap<CatchHitObject> beatmap)
{
hpDrainRate = beatmap.BeatmapInfo.BaseDifficulty.DrainRate;
foreach (var obj in beatmap.HitObjects)
{
switch (obj)
{
case JuiceStream stream:
foreach (var _ in stream.NestedHitObjects.Cast<CatchHitObject>())
AddJudgement(new CatchJudgement { Result = HitResult.Perfect });
foreach (var nestedObject in stream.NestedHitObjects)
switch (nestedObject)
{
case TinyDroplet _:
AddJudgement(new CatchTinyDropletJudgement { Result = HitResult.Perfect });
break;
case Droplet _:
AddJudgement(new CatchDropletJudgement { Result = HitResult.Perfect });
break;
case Fruit _:
AddJudgement(new CatchJudgement { Result = HitResult.Perfect });
break;
}
break;
case BananaShower shower:
foreach (var _ in shower.NestedHitObjects.Cast<CatchHitObject>())
AddJudgement(new CatchJudgement { Result = HitResult.Perfect });
AddJudgement(new CatchJudgement { Result = HitResult.Perfect });
AddJudgement(new CatchBananaJudgement { Result = HitResult.Perfect });
break;
case Fruit _:
AddJudgement(new CatchJudgement { Result = HitResult.Perfect });
break;
}
}
}
base.SimulateAutoplay(beatmap);
private const double harshness = 0.01;
protected override void OnNewJudgement(Judgement judgement)
{
base.OnNewJudgement(judgement);
if (judgement.Result == HitResult.Miss)
{
if (!judgement.IsBonus)
Health.Value -= hpDrainRate * (harshness * 2);
return;
}
if (judgement is CatchJudgement catchJudgement)
Health.Value += Math.Max(catchJudgement.HealthIncrease - hpDrainRate, 0) * harshness;
}
}
}
@@ -38,14 +38,16 @@ namespace osu.Game.Rulesets.Catch.UI
{
switch (h)
{
case Banana banana:
return new DrawableBanana(banana);
case Fruit fruit:
return new DrawableFruit(fruit);
case JuiceStream stream:
return new DrawableJuiceStream(stream, GetVisualRepresentation);
case BananaShower banana:
return new DrawableBananaShower(banana, GetVisualRepresentation);
case BananaShower shower:
return new DrawableBananaShower(shower, GetVisualRepresentation);
case TinyDroplet tiny:
return new DrawableDroplet(tiny) { Scale = new Vector2(0.5f) };
return new DrawableTinyDroplet(tiny);
case Droplet droplet:
return new DrawableDroplet(droplet);
}
+50 -33
View File
@@ -11,6 +11,7 @@ using osu.Framework.Graphics.Textures;
using osu.Framework.Input.Bindings;
using osu.Framework.MathUtils;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Catch.Judgements;
using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Catch.Objects.Drawable;
using osu.Game.Rulesets.Catch.Replays;
@@ -78,12 +79,11 @@ namespace osu.Game.Rulesets.Catch.UI
if (!fruit.StaysOnPlate)
runAfterLoaded(() => MovableCatcher.Explode(caughtFruit));
}
if (fruit.HitObject.LastInCombo)
{
if (judgement.IsHit)
if (((CatchJudgement)judgement).ShouldExplode)
runAfterLoaded(() => MovableCatcher.Explode());
else
MovableCatcher.Drop();
@@ -250,51 +250,62 @@ namespace osu.Game.Rulesets.Catch.UI
if (validCatch && fruit.HyperDash)
{
HyperDashModifier = Math.Abs(fruit.HyperDashTarget.X - fruit.X) / Math.Abs(fruit.HyperDashTarget.StartTime - fruit.StartTime) / BASE_SPEED;
HyperDashDirection = fruit.HyperDashTarget.X - fruit.X;
var target = fruit.HyperDashTarget;
double timeDifference = target.StartTime - fruit.StartTime;
double positionDifference = target.X * CatchPlayfield.BASE_WIDTH - catcherPosition;
double velocity = positionDifference / Math.Max(1.0, timeDifference - 1000.0 / 60.0);
SetHyperDashState(Math.Abs(velocity), target.X);
}
else
HyperDashModifier = 1;
{
SetHyperDashState();
}
return validCatch;
}
private double hyperDashModifier = 1;
private int hyperDashDirection;
private float hyperDashTargetPosition;
/// <summary>
/// Whether we are hypderdashing or not.
/// Whether we are hyper-dashing or not.
/// </summary>
public bool HyperDashing => hyperDashModifier != 1;
private double hyperDashModifier = 1;
/// <summary>
/// The direction in which hyperdash is allowed. 0 allows both directions.
/// Set hyper-dash state.
/// </summary>
public double HyperDashDirection;
/// <summary>
/// The speed modifier resultant from hyperdash. Will trigger hyperdash when not equal to 1.
/// </summary>
public double HyperDashModifier
/// <param name="modifier">The speed multiplier. If this is less or equals to 1, this catcher will be non-hyper-dashing state.</param>
/// <param name="targetPosition">When this catcher crosses this position, this catcher ends hyper-dashing.</param>
public void SetHyperDashState(double modifier = 1, float targetPosition = -1)
{
get { return hyperDashModifier; }
set
const float hyper_dash_transition_length = 180;
bool previouslyHyperDashing = HyperDashing;
if (modifier <= 1 || X == targetPosition)
{
if (value == hyperDashModifier) return;
hyperDashModifier = value;
hyperDashModifier = 1;
hyperDashDirection = 0;
const float transition_length = 180;
if (HyperDashing)
if (previouslyHyperDashing)
{
this.FadeColour(Color4.OrangeRed, transition_length, Easing.OutQuint);
this.FadeTo(0.2f, transition_length, Easing.OutQuint);
Trail = true;
this.FadeColour(Color4.White, hyper_dash_transition_length, Easing.OutQuint);
this.FadeTo(1, hyper_dash_transition_length, Easing.OutQuint);
}
else
}
else
{
hyperDashModifier = modifier;
hyperDashDirection = Math.Sign(targetPosition - X);
hyperDashTargetPosition = targetPosition;
if (!previouslyHyperDashing)
{
HyperDashDirection = 0;
this.FadeColour(Color4.White, transition_length, Easing.OutQuint);
this.FadeTo(1, transition_length, Easing.OutQuint);
this.FadeColour(Color4.OrangeRed, hyper_dash_transition_length, Easing.OutQuint);
this.FadeTo(0.2f, hyper_dash_transition_length, Easing.OutQuint);
Trail = true;
}
}
}
@@ -349,12 +360,18 @@ namespace osu.Game.Rulesets.Catch.UI
var direction = Math.Sign(currentDirection);
double dashModifier = Dashing ? 1 : 0.5;
if (hyperDashModifier != 1 && (HyperDashDirection == 0 || direction == Math.Sign(HyperDashDirection)))
dashModifier = hyperDashModifier;
double speed = BASE_SPEED * dashModifier * hyperDashModifier;
Scale = new Vector2(Math.Abs(Scale.X) * direction, Scale.Y);
X = (float)MathHelper.Clamp(X + direction * Clock.ElapsedFrameTime * BASE_SPEED * dashModifier, 0, 1);
X = (float)MathHelper.Clamp(X + direction * Clock.ElapsedFrameTime * speed, 0, 1);
// Correct overshooting.
if (hyperDashDirection > 0 && hyperDashTargetPosition < X ||
hyperDashDirection < 0 && hyperDashTargetPosition > X)
{
X = hyperDashTargetPosition;
SetHyperDashState();
}
}
/// <summary>
@@ -5,6 +5,8 @@ using System;
using System.Collections.Generic;
using NUnit.Framework;
using osu.Framework.MathUtils;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
@@ -12,11 +14,11 @@ using osu.Game.Tests.Beatmaps;
namespace osu.Game.Rulesets.Mania.Tests
{
internal class ManiaBeatmapConversionTest : BeatmapConversionTest<ConvertValue>
[TestFixture]
public class ManiaBeatmapConversionTest : BeatmapConversionTest<ManiaConvertMapping, ConvertValue>
{
protected override string ResourceAssembly => "osu.Game.Rulesets.Mania";
[NonParallelizable]
[TestCase("basic")]
public new void Test(string name)
{
@@ -33,10 +35,36 @@ namespace osu.Game.Rulesets.Mania.Tests
};
}
protected override ManiaConvertMapping CreateConvertMapping() => new ManiaConvertMapping(Converter);
protected override Ruleset CreateRuleset() => new ManiaRuleset();
}
internal struct ConvertValue : IEquatable<ConvertValue>
public class ManiaConvertMapping : ConvertMapping<ConvertValue>, IEquatable<ManiaConvertMapping>
{
public uint RandomW;
public uint RandomX;
public uint RandomY;
public uint RandomZ;
public ManiaConvertMapping()
{
}
public ManiaConvertMapping(IBeatmapConverter converter)
{
var maniaConverter = (ManiaBeatmapConverter)converter;
RandomW = maniaConverter.Random.W;
RandomX = maniaConverter.Random.X;
RandomY = maniaConverter.Random.Y;
RandomZ = maniaConverter.Random.Z;
}
public bool Equals(ManiaConvertMapping other) => other != null && RandomW == other.RandomW && RandomX == other.RandomX && RandomY == other.RandomY && RandomZ == other.RandomZ;
public override bool Equals(ConvertMapping<ConvertValue> other) => base.Equals(other) && Equals(other as ManiaConvertMapping);
}
public struct ConvertValue : IEquatable<ConvertValue>
{
/// <summary>
/// A sane value to account for osu!stable using ints everwhere.
@@ -0,0 +1,45 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Input.Bindings;
using osu.Game.Tests.Visual;
namespace osu.Game.Rulesets.Mania.Tests
{
public abstract class ManiaInputTestCase : OsuTestCase
{
private readonly Container<Drawable> content;
protected override Container<Drawable> Content => content ?? base.Content;
protected ManiaInputTestCase(int keys)
{
base.Content.Add(content = new LocalInputManager(keys));
}
private class LocalInputManager : ManiaInputManager
{
public LocalInputManager(int variant)
: base(new ManiaRuleset().RulesetInfo, variant)
{
}
protected override RulesetKeyBindingContainer CreateKeyBindingContainer(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique)
=> new LocalKeyBindingContainer(ruleset, variant, unique);
private class LocalKeyBindingContainer : RulesetKeyBindingContainer
{
public LocalKeyBindingContainer(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique)
: base(ruleset, variant, unique)
{
}
protected override void ReloadMappings()
{
KeyBindings = DefaultKeyBindings;
}
}
}
}
}
@@ -0,0 +1,37 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.Allocation;
using osu.Framework.Configuration;
using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Mania.UI;
using osu.Game.Rulesets.UI.Scrolling;
namespace osu.Game.Rulesets.Mania.Tests
{
/// <summary>
/// A container which provides a <see cref="IScrollingInfo"/> to children.
/// </summary>
public class ScrollingTestContainer : Container
{
private readonly ScrollingDirection direction;
public ScrollingTestContainer(ScrollingDirection direction)
{
this.direction = direction;
}
protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent)
{
var dependencies = new DependencyContainer(base.CreateLocalDependencies(parent));
dependencies.CacheAs<IScrollingInfo>(new ScrollingInfo { Direction = { Value = direction }});
return dependencies;
}
private class ScrollingInfo : IScrollingInfo
{
public readonly Bindable<ScrollingDirection> Direction = new Bindable<ScrollingDirection>();
IBindable<ScrollingDirection> IScrollingInfo.Direction => Direction;
}
}
}
@@ -0,0 +1,111 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using System.Collections.Generic;
using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Framework.Allocation;
using osu.Framework.Graphics.Containers;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.Objects.Drawables;
using osu.Game.Rulesets.Mania.UI;
using osu.Game.Rulesets.Mania.UI.Components;
using osu.Game.Rulesets.UI.Scrolling;
using OpenTK;
using OpenTK.Graphics;
namespace osu.Game.Rulesets.Mania.Tests
{
[TestFixture]
public class TestCaseColumn : ManiaInputTestCase
{
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(Column),
typeof(ColumnBackground),
typeof(ColumnKeyArea),
typeof(ColumnHitObjectArea)
};
private readonly List<Column> columns = new List<Column>();
public TestCaseColumn()
: base(2)
{
}
[BackgroundDependencyLoader]
private void load()
{
Child = new FillFlowContainer
{
RelativeSizeAxes = Axes.Both,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Spacing = new Vector2(20, 0),
Children = new[]
{
createColumn(ScrollingDirection.Up, ManiaAction.Key1),
createColumn(ScrollingDirection.Down, ManiaAction.Key2)
}
};
}
protected override void LoadComplete()
{
base.LoadComplete();
AddStep("note", createNote);
AddStep("hold note", createHoldNote);
}
private void createNote()
{
for (int i = 0; i < columns.Count; i++)
{
var obj = new Note { Column = i, StartTime = Time.Current + 2000 };
obj.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
columns[i].Add(new DrawableNote(obj));
}
}
private void createHoldNote()
{
for (int i = 0; i < columns.Count; i++)
{
var obj = new HoldNote { Column = i, StartTime = Time.Current + 2000, Duration = 500 };
obj.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
columns[i].Add(new DrawableHoldNote(obj));
}
}
private Drawable createColumn(ScrollingDirection direction, ManiaAction action)
{
var column = new Column(direction)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Height = 0.85f,
AccentColour = Color4.OrangeRed,
Action = { Value = action },
VisibleTimeRange = { Value = 2000 }
};
columns.Add(column);
return new ScrollingTestContainer(direction)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
AutoSizeAxes = Axes.X,
RelativeSizeAxes = Axes.Y,
Child = column
};
}
}
}
@@ -1,106 +0,0 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.Objects.Drawables;
using osu.Game.Tests.Visual;
using OpenTK;
using OpenTK.Graphics;
namespace osu.Game.Rulesets.Mania.Tests
{
[TestFixture]
public class TestCaseManiaHitObjects : OsuTestCase
{
public TestCaseManiaHitObjects()
{
Note note1 = new Note();
Note note2 = new Note();
HoldNote holdNote = new HoldNote { StartTime = 1000 };
note1.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
note2.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
holdNote.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
Add(new FillFlowContainer
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Y,
Direction = FillDirection.Horizontal,
Spacing = new Vector2(10, 0),
// Imagine that the containers containing the drawable notes are the "columns"
Children = new Drawable[]
{
new Container
{
Name = "Normal note column",
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Y,
Width = 50,
Children = new[]
{
new Container
{
Name = "Timing section",
RelativeSizeAxes = Axes.Both,
RelativeChildSize = new Vector2(1, 10000),
Children = new[]
{
new DrawableNote(note1, ManiaAction.Key1)
{
Y = 5000,
LifetimeStart = double.MinValue,
LifetimeEnd = double.MaxValue,
AccentColour = Color4.Red
},
new DrawableNote(note2, ManiaAction.Key1)
{
Y = 6000,
LifetimeStart = double.MinValue,
LifetimeEnd = double.MaxValue,
AccentColour = Color4.Red
}
}
}
}
},
new Container
{
Name = "Hold note column",
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Y,
Width = 50,
Children = new[]
{
new Container
{
Name = "Timing section",
RelativeSizeAxes = Axes.Both,
RelativeChildSize = new Vector2(1, 10000),
Children = new[]
{
new DrawableHoldNote(holdNote, ManiaAction.Key1)
{
Y = 5000,
Height = 1000,
LifetimeStart = double.MinValue,
LifetimeEnd = double.MaxValue,
AccentColour = Color4.Red,
}
}
}
}
}
}
});
}
}
}
@@ -1,185 +0,0 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Timing;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Configuration;
using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Mania.Configuration;
using osu.Game.Rulesets.Mania.Judgements;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.Objects.Drawables;
using osu.Game.Rulesets.Mania.UI;
using osu.Game.Rulesets.Scoring;
using osu.Game.Tests.Visual;
namespace osu.Game.Rulesets.Mania.Tests
{
[TestFixture]
public class TestCaseManiaPlayfield : OsuTestCase
{
private const double start_time = 500;
private const double duration = 500;
protected override double TimePerAction => 200;
private RulesetInfo maniaRuleset;
public TestCaseManiaPlayfield()
{
var rng = new Random(1337);
AddStep("1 column", () => createPlayfield(1));
AddStep("4 columns", () => createPlayfield(4));
AddStep("5 columns", () => createPlayfield(5));
AddStep("8 columns", () => createPlayfield(8));
AddStep("4 + 4 columns", () =>
{
var stages = new List<StageDefinition>
{
new StageDefinition { Columns = 4 },
new StageDefinition { Columns = 4 },
};
createPlayfield(stages);
});
AddStep("2 + 4 + 2 columns", () =>
{
var stages = new List<StageDefinition>
{
new StageDefinition { Columns = 2 },
new StageDefinition { Columns = 4 },
new StageDefinition { Columns = 2 },
};
createPlayfield(stages);
});
AddStep("1 + 8 + 1 columns", () =>
{
var stages = new List<StageDefinition>
{
new StageDefinition { Columns = 1 },
new StageDefinition { Columns = 8 },
new StageDefinition { Columns = 1 },
};
createPlayfield(stages);
});
AddStep("Reversed", () => createPlayfield(4, true));
AddStep("Notes with input", () => createPlayfieldWithNotes());
AddStep("Notes with input (reversed)", () => createPlayfieldWithNotes(true));
AddStep("Notes with gravity", () => createPlayfieldWithNotes());
AddStep("Notes with gravity (reversed)", () => createPlayfieldWithNotes(true));
AddStep("Hit explosion", () =>
{
var playfield = createPlayfield(4);
int col = rng.Next(0, 4);
var note = new Note { Column = col };
note.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
var drawableNote = new DrawableNote(note, ManiaAction.Key1)
{
AccentColour = playfield.Columns.ElementAt(col).AccentColour
};
playfield.OnJudgement(drawableNote, new ManiaJudgement { Result = HitResult.Perfect });
playfield.Columns[col].OnJudgement(drawableNote, new ManiaJudgement { Result = HitResult.Perfect });
});
}
[BackgroundDependencyLoader]
private void load(RulesetStore rulesets, SettingsStore settings)
{
maniaRuleset = rulesets.GetRuleset(3);
Dependencies.Cache(new ManiaConfigManager(settings, maniaRuleset, 4));
}
private ManiaPlayfield createPlayfield(int cols, bool inverted = false)
{
var stages = new List<StageDefinition>
{
new StageDefinition { Columns = cols },
};
return createPlayfield(stages, inverted);
}
private ManiaPlayfield createPlayfield(List<StageDefinition> stages, bool inverted = false)
{
Clear();
var inputManager = new ManiaInputManager(maniaRuleset, stages.Sum(g => g.Columns)) { RelativeSizeAxes = Axes.Both };
Add(inputManager);
ManiaPlayfield playfield;
inputManager.Add(playfield = new ManiaPlayfield(stages)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
});
playfield.Inverted.Value = inverted;
return playfield;
}
private void createPlayfieldWithNotes(bool inverted = false)
{
Clear();
var rateAdjustClock = new StopwatchClock(true) { Rate = 1 };
var inputManager = new ManiaInputManager(maniaRuleset, 4) { RelativeSizeAxes = Axes.Both };
Add(inputManager);
ManiaPlayfield playfield;
var stages = new List<StageDefinition>
{
new StageDefinition { Columns = 4 },
};
inputManager.Add(playfield = new ManiaPlayfield(stages)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Clock = new FramedClock(rateAdjustClock)
});
playfield.Inverted.Value = inverted;
for (double t = start_time; t <= start_time + duration; t += 100)
{
var note1 = new Note { StartTime = t, Column = 0 };
var note2 = new Note { StartTime = t, Column = 3 };
note1.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
note2.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
playfield.Add(new DrawableNote(note1, ManiaAction.Key1));
playfield.Add(new DrawableNote(note2, ManiaAction.Key4));
}
var holdNote1 = new HoldNote { StartTime = start_time, Duration = duration, Column = 1 };
var holdNote2 = new HoldNote { StartTime = start_time, Duration = duration, Column = 2 };
holdNote1.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
holdNote2.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
playfield.Add(new DrawableHoldNote(holdNote1, ManiaAction.Key2));
playfield.Add(new DrawableHoldNote(holdNote2, ManiaAction.Key3));
}
}
}
@@ -0,0 +1,173 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Configuration;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.Objects.Drawables;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.UI.Scrolling;
using osu.Game.Tests.Visual;
using OpenTK;
using OpenTK.Graphics;
namespace osu.Game.Rulesets.Mania.Tests
{
[TestFixture]
public class TestCaseNotes : OsuTestCase
{
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(DrawableNote),
typeof(DrawableHoldNote)
};
[BackgroundDependencyLoader]
private void load()
{
Child = new FillFlowContainer
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal,
Spacing = new Vector2(20),
Children = new[]
{
createNoteDisplay(ScrollingDirection.Down),
createNoteDisplay(ScrollingDirection.Up),
createHoldNoteDisplay(ScrollingDirection.Down),
createHoldNoteDisplay(ScrollingDirection.Up),
}
};
}
private Drawable createNoteDisplay(ScrollingDirection direction)
{
var note = new Note { StartTime = 999999999 };
note.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
return new ScrollingTestContainer(direction)
{
AutoSizeAxes = Axes.Both,
Child = new NoteContainer(direction, $"note, scrolling {direction.ToString().ToLower()}")
{
Child = new DrawableNote(note) { AccentColour = Color4.OrangeRed }
}
};
}
private Drawable createHoldNoteDisplay(ScrollingDirection direction)
{
var note = new HoldNote { StartTime = 999999999, Duration = 1000 };
note.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
return new ScrollingTestContainer(direction)
{
AutoSizeAxes = Axes.Both,
Child = new NoteContainer(direction, $"hold note, scrolling {direction.ToString().ToLower()}")
{
Child = new DrawableHoldNote(note)
{
RelativeSizeAxes = Axes.Both,
AccentColour = Color4.OrangeRed,
}
}
};
}
private class NoteContainer : Container
{
private readonly Container content;
protected override Container<Drawable> Content => content;
private readonly ScrollingDirection direction;
public NoteContainer(ScrollingDirection direction, string description)
{
this.direction = direction;
AutoSizeAxes = Axes.Both;
InternalChild = new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Spacing = new Vector2(0, 10),
Direction = FillDirection.Vertical,
Children = new Drawable[]
{
new Container
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
Width = 45,
Height = 100,
Children = new Drawable[]
{
new Box
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
RelativeSizeAxes = Axes.Both,
Width = 1.25f,
Colour = Color4.Black.Opacity(0.5f)
},
content = new Container { RelativeSizeAxes = Axes.Both }
}
},
new SpriteText
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
TextSize = 14,
Text = description
}
}
};
}
protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent)
{
var dependencies = new DependencyContainer(base.CreateLocalDependencies(parent));
dependencies.CacheAs<IBindable<ManiaAction>>(new Bindable<ManiaAction>());
return dependencies;
}
protected override void Update()
{
base.Update();
foreach (var obj in content.OfType<DrawableHitObject>())
{
if (!(obj.HitObject is IHasEndTime endTime))
continue;
foreach (var nested in obj.NestedHitObjects)
{
double finalPosition = (nested.HitObject.StartTime - obj.HitObject.StartTime) / endTime.Duration;
switch (direction)
{
case ScrollingDirection.Up:
nested.Y = (float)(finalPosition * content.DrawHeight);
break;
case ScrollingDirection.Down:
nested.Y = (float)(-finalPosition * content.DrawHeight);
break;
}
}
}
}
}
}
}
@@ -0,0 +1,121 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Collections.Generic;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.Objects.Drawables;
using osu.Game.Rulesets.Mania.UI;
using osu.Game.Rulesets.UI.Scrolling;
using OpenTK;
namespace osu.Game.Rulesets.Mania.Tests
{
[TestFixture]
public class TestCaseStage : ManiaInputTestCase
{
private const int columns = 4;
private readonly List<ManiaStage> stages = new List<ManiaStage>();
public TestCaseStage()
: base(columns)
{
}
[BackgroundDependencyLoader]
private void load()
{
Child = new FillFlowContainer
{
RelativeSizeAxes = Axes.Both,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Spacing = new Vector2(20, 0),
Children = new[]
{
createStage(ScrollingDirection.Up, ManiaAction.Key1),
createStage(ScrollingDirection.Down, ManiaAction.Key3)
}
};
}
protected override void LoadComplete()
{
base.LoadComplete();
AddStep("note", createNote);
AddStep("hold note", createHoldNote);
AddStep("minor bar line", () => createBarLine(false));
AddStep("major bar line", () => createBarLine(true));
}
private void createNote()
{
foreach (var stage in stages)
{
for (int i = 0; i < stage.Columns.Count; i++)
{
var obj = new Note { Column = i, StartTime = Time.Current + 2000 };
obj.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
stage.Add(new DrawableNote(obj));
}
}
}
private void createHoldNote()
{
foreach (var stage in stages)
{
for (int i = 0; i < stage.Columns.Count; i++)
{
var obj = new HoldNote { Column = i, StartTime = Time.Current + 2000, Duration = 500 };
obj.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
stage.Add(new DrawableHoldNote(obj));
}
}
}
private void createBarLine(bool major)
{
foreach (var stage in stages)
{
var obj = new BarLine
{
StartTime = Time.Current + 2000,
ControlPoint = new TimingControlPoint(),
BeatIndex = major ? 0 : 1
};
obj.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
stage.Add(obj);
}
}
private Drawable createStage(ScrollingDirection direction, ManiaAction action)
{
var specialAction = ManiaAction.Special1;
var stage = new ManiaStage(direction, 0, new StageDefinition { Columns = 2 }, ref action, ref specialAction) { VisibleTimeRange = { Value = 2000 } };
stages.Add(stage);
return new ScrollingTestContainer(direction)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Y,
AutoSizeAxes = Axes.X,
Child = stage
};
}
}
}
@@ -5,6 +5,7 @@ using osu.Game.Rulesets.Mania.Objects;
using System;
using System.Linq;
using System.Collections.Generic;
using osu.Framework.MathUtils;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
@@ -28,8 +29,10 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
public int TargetColumns;
public readonly bool IsForCurrentRuleset;
// Internal for testing purposes
internal FastRandom Random { get; private set; }
private Pattern lastPattern = new Pattern();
private FastRandom random;
private ManiaBeatmap beatmap;
@@ -62,7 +65,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
BeatmapDifficulty difficulty = original.BeatmapInfo.BaseDifficulty;
int seed = (int)Math.Round(difficulty.DrainRate + difficulty.CircleSize) * 20 + (int)(difficulty.OverallDifficulty * 41.2) + (int)Math.Round(difficulty.ApproachRate);
random = new FastRandom(seed);
Random = new FastRandom(seed);
return base.ConvertBeatmap(original);
}
@@ -100,7 +103,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
private double lastTime;
private Vector2 lastPosition;
private PatternType lastStair;
private PatternType lastStair = PatternType.Stair;
private void recordNote(double time, Vector2 position)
{
lastTime = time;
@@ -115,12 +118,15 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
/// <returns>The hit objects generated.</returns>
private IEnumerable<ManiaHitObject> generateSpecific(HitObject original, IBeatmap originalBeatmap)
{
var generator = new SpecificBeatmapPatternGenerator(random, original, beatmap, lastPattern, originalBeatmap);
var generator = new SpecificBeatmapPatternGenerator(Random, original, beatmap, lastPattern, originalBeatmap);
Pattern newPattern = generator.Generate();
lastPattern = newPattern;
foreach (var newPattern in generator.Generate())
{
lastPattern = newPattern;
return newPattern.HitObjects;
foreach (var obj in newPattern.HitObjects)
yield return obj;
}
}
/// <summary>
@@ -138,27 +144,44 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
Patterns.PatternGenerator conversion = null;
if (distanceData != null)
conversion = new DistanceObjectPatternGenerator(random, original, beatmap, lastPattern, originalBeatmap);
{
var generator = new DistanceObjectPatternGenerator(Random, original, beatmap, lastPattern, originalBeatmap);
conversion = generator;
for (double time = original.StartTime; !Precision.DefinitelyBigger(time, generator.EndTime); time += generator.SegmentDuration)
{
recordNote(time, positionData?.Position ?? Vector2.Zero);
computeDensity(time);
}
}
else if (endTimeData != null)
conversion = new EndTimeObjectPatternGenerator(random, original, beatmap, originalBeatmap);
{
conversion = new EndTimeObjectPatternGenerator(Random, original, beatmap, originalBeatmap);
recordNote(endTimeData.EndTime, new Vector2(256, 192));
computeDensity(endTimeData.EndTime);
}
else if (positionData != null)
{
computeDensity(original.StartTime);
conversion = new HitObjectPatternGenerator(random, original, beatmap, lastPattern, lastTime, lastPosition, density, lastStair, originalBeatmap);
conversion = new HitObjectPatternGenerator(Random, original, beatmap, lastPattern, lastTime, lastPosition, density, lastStair, originalBeatmap);
recordNote(original.StartTime, positionData.Position);
}
if (conversion == null)
return null;
yield break;
Pattern newPattern = conversion.Generate();
foreach (var newPattern in conversion.Generate())
{
lastPattern = conversion is EndTimeObjectPatternGenerator ? lastPattern : newPattern;
lastStair = (conversion as HitObjectPatternGenerator)?.StairType ?? lastStair;
lastPattern = conversion is EndTimeObjectPatternGenerator ? lastPattern : newPattern;
lastStair = (conversion as HitObjectPatternGenerator)?.StairType ?? lastStair;
foreach (var obj in newPattern.HitObjects)
yield return obj;
return newPattern.HitObjects;
}
}
/// <summary>
@@ -171,7 +194,12 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
{
}
public override Pattern Generate()
public override IEnumerable<Pattern> Generate()
{
yield return generate();
}
private Pattern generate()
{
var endTimeData = HitObject as IHasEndTime;
var positionData = HitObject as IHasXPosition;
@@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using osu.Framework.MathUtils;
using osu.Game.Audio;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Mania.MathUtils;
@@ -24,8 +25,9 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
/// </summary>
private const float osu_base_scoring_distance = 100;
private readonly double endTime;
private readonly double segmentDuration;
public readonly double EndTime;
public readonly double SegmentDuration;
private readonly int spanCount;
private PatternType convertType;
@@ -52,53 +54,81 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
// The duration of the osu! hit object
double osuDuration = distance / osuVelocity;
endTime = hitObject.StartTime + osuDuration;
segmentDuration = (endTime - HitObject.StartTime) / spanCount;
EndTime = hitObject.StartTime + osuDuration;
SegmentDuration = (EndTime - HitObject.StartTime) / spanCount;
}
public override Pattern Generate()
public override IEnumerable<Pattern> Generate()
{
var originalPattern = generate();
if (originalPattern.HitObjects.Count() == 1)
{
yield return originalPattern;
yield break;
}
// We need to split the intermediate pattern into two new patterns:
// 1. A pattern containing all objects that do not end at our EndTime.
// 2. A pattern containing all objects that end at our EndTime. This will be used for further pattern generation.
var intermediatePattern = new Pattern();
var endTimePattern = new Pattern();
foreach (var obj in originalPattern.HitObjects)
{
if (!Precision.AlmostEquals(EndTime, (obj as IHasEndTime)?.EndTime ?? obj.StartTime))
intermediatePattern.Add(obj);
else
endTimePattern.Add(obj);
}
yield return intermediatePattern;
yield return endTimePattern;
}
private Pattern generate()
{
if (TotalColumns == 1)
{
var pattern = new Pattern();
addToPattern(pattern, 0, HitObject.StartTime, endTime);
addToPattern(pattern, 0, HitObject.StartTime, EndTime);
return pattern;
}
if (spanCount > 1)
{
if (segmentDuration <= 90)
if (SegmentDuration <= 90)
return generateRandomHoldNotes(HitObject.StartTime, 1);
if (segmentDuration <= 120)
if (SegmentDuration <= 120)
{
convertType |= PatternType.ForceNotStack;
return generateRandomNotes(HitObject.StartTime, spanCount + 1);
}
if (segmentDuration <= 160)
if (SegmentDuration <= 160)
return generateStair(HitObject.StartTime);
if (segmentDuration <= 200 && ConversionDifficulty > 3)
if (SegmentDuration <= 200 && ConversionDifficulty > 3)
return generateRandomMultipleNotes(HitObject.StartTime);
double duration = endTime - HitObject.StartTime;
double duration = EndTime - HitObject.StartTime;
if (duration >= 4000)
return generateNRandomNotes(HitObject.StartTime, 0.23, 0, 0);
if (segmentDuration > 400 && spanCount < TotalColumns - 1 - RandomStart)
if (SegmentDuration > 400 && spanCount < TotalColumns - 1 - RandomStart)
return generateTiledHoldNotes(HitObject.StartTime);
return generateHoldAndNormalNotes(HitObject.StartTime);
}
if (segmentDuration <= 110)
if (SegmentDuration <= 110)
{
if (PreviousPattern.ColumnWithObjects < TotalColumns)
convertType |= PatternType.ForceNotStack;
else
convertType &= ~PatternType.ForceNotStack;
return generateRandomNotes(HitObject.StartTime, segmentDuration < 80 ? 1 : 2);
return generateRandomNotes(HitObject.StartTime, SegmentDuration < 80 ? 1 : 2);
}
if (ConversionDifficulty > 6.5)
@@ -148,7 +178,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
{
while (pattern.ColumnHasObject(nextColumn) || PreviousPattern.ColumnHasObject(nextColumn)) //find available column
nextColumn = Random.Next(RandomStart, TotalColumns);
addToPattern(pattern, nextColumn, startTime, endTime);
addToPattern(pattern, nextColumn, startTime, EndTime);
}
// This is can't be combined with the above loop due to RNG
@@ -156,7 +186,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
{
while (pattern.ColumnHasObject(nextColumn))
nextColumn = Random.Next(RandomStart, TotalColumns);
addToPattern(pattern, nextColumn, startTime, endTime);
addToPattern(pattern, nextColumn, startTime, EndTime);
}
return pattern;
@@ -193,7 +223,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
nextColumn = Random.Next(RandomStart, TotalColumns);
lastColumn = nextColumn;
startTime += segmentDuration;
startTime += SegmentDuration;
}
return pattern;
@@ -223,7 +253,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
for (int i = 0; i <= spanCount; i++)
{
addToPattern(pattern, column, startTime, startTime);
startTime += segmentDuration;
startTime += SegmentDuration;
// Check if we're at the borders of the stage, and invert the pattern if so
if (increasing)
@@ -284,7 +314,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
addToPattern(pattern, nextColumn, startTime, startTime);
nextColumn = Random.Next(RandomStart, TotalColumns);
startTime += segmentDuration;
startTime += SegmentDuration;
}
return pattern;
@@ -329,7 +359,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
break;
}
bool isDoubleSample(SampleInfo sample) => sample.Name == SampleInfo.HIT_CLAP && sample.Name == SampleInfo.HIT_FINISH;
bool isDoubleSample(SampleInfo sample) => sample.Name == SampleInfo.HIT_CLAP || sample.Name == SampleInfo.HIT_FINISH;
bool canGenerateTwoNotes = (convertType & PatternType.LowProbability) == 0;
canGenerateTwoNotes &= HitObject.Samples.Any(isDoubleSample) || sampleInfoListAt(HitObject.StartTime).Any(isDoubleSample);
@@ -372,8 +402,8 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
while (pattern.ColumnHasObject(nextColumn))
nextColumn = Random.Next(RandomStart, TotalColumns);
addToPattern(pattern, nextColumn, startTime, endTime);
startTime += segmentDuration;
addToPattern(pattern, nextColumn, startTime, EndTime);
startTime += SegmentDuration;
}
return pattern;
@@ -402,7 +432,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
}
// Create the hold note
addToPattern(pattern, holdColumn, startTime, endTime);
addToPattern(pattern, holdColumn, startTime, EndTime);
int nextColumn = Random.Next(RandomStart, TotalColumns);
int noteCount;
@@ -434,7 +464,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
pattern.Add(rowPattern);
rowPattern.Clear();
startTime += segmentDuration;
startTime += SegmentDuration;
}
return pattern;
@@ -452,7 +482,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
if (curveData == null)
return HitObject.Samples;
double segmentTime = (endTime - HitObject.StartTime) / spanCount;
double segmentTime = (EndTime - HitObject.StartTime) / spanCount;
int index = (int)(segmentTime == 0 ? 0 : (time - HitObject.StartTime) / segmentTime);
return curveData.RepeatSamples[index];
@@ -19,12 +19,15 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
public EndTimeObjectPatternGenerator(FastRandom random, HitObject hitObject, ManiaBeatmap beatmap, IBeatmap originalBeatmap)
: base(random, hitObject, beatmap, new Pattern(), originalBeatmap)
{
var endtimeData = HitObject as IHasEndTime;
endTime = endtimeData?.EndTime ?? 0;
endTime = (HitObject as IHasEndTime)?.EndTime ?? 0;
}
public override Pattern Generate()
public override IEnumerable<Pattern> Generate()
{
yield return generate();
}
private Pattern generate()
{
var pattern = new Pattern();
@@ -2,6 +2,7 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using System.Collections.Generic;
using System.Linq;
using OpenTK;
using osu.Game.Audio;
@@ -82,127 +83,133 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
{
if (HitObject.Samples.Any(s => s.Name == SampleInfo.HIT_FINISH) && TotalColumns != 8)
convertType |= PatternType.Mirror;
else
else if (HitObject.Samples.Any(s => s.Name == SampleInfo.HIT_CLAP))
convertType |= PatternType.Gathered;
}
}
public override Pattern Generate()
public override IEnumerable<Pattern> Generate()
{
if (TotalColumns == 1)
yield return generate();
}
private Pattern generate()
{
var pattern = new Pattern();
try
{
var pattern = new Pattern();
addToPattern(pattern, 0);
return pattern;
}
int lastColumn = PreviousPattern.HitObjects.FirstOrDefault()?.Column ?? 0;
if ((convertType & PatternType.Reverse) > 0 && PreviousPattern.HitObjects.Any())
{
// Generate a new pattern by copying the last hit objects in reverse-column order
var pattern = new Pattern();
for (int i = RandomStart; i < TotalColumns; i++)
if (PreviousPattern.ColumnHasObject(i))
addToPattern(pattern, RandomStart + TotalColumns - i - 1);
return pattern;
}
if ((convertType & PatternType.Cycle) > 0 && PreviousPattern.HitObjects.Count() == 1
// If we convert to 7K + 1, let's not overload the special key
&& (TotalColumns != 8 || lastColumn != 0)
// Make sure the last column was not the centre column
&& (TotalColumns % 2 == 0 || lastColumn != TotalColumns / 2))
{
// Generate a new pattern by cycling backwards (similar to Reverse but for only one hit object)
var pattern = new Pattern();
int column = RandomStart + TotalColumns - lastColumn - 1;
addToPattern(pattern, column);
return pattern;
}
if ((convertType & PatternType.ForceStack) > 0 && PreviousPattern.HitObjects.Any())
{
// Generate a new pattern by placing on the already filled columns
var pattern = new Pattern();
for (int i = RandomStart; i < TotalColumns; i++)
if (PreviousPattern.ColumnHasObject(i))
addToPattern(pattern, i);
return pattern;
}
if ((convertType & PatternType.Stair) > 0 && PreviousPattern.HitObjects.Count() == 1)
{
// Generate a new pattern by placing on the next column, cycling back to the start if there is no "next"
var pattern = new Pattern();
int targetColumn = lastColumn + 1;
if (targetColumn == TotalColumns)
if (TotalColumns == 1)
{
targetColumn = RandomStart;
StairType = PatternType.ReverseStair;
addToPattern(pattern, 0);
return pattern;
}
addToPattern(pattern, targetColumn);
return pattern;
}
int lastColumn = PreviousPattern.HitObjects.FirstOrDefault()?.Column ?? 0;
if ((convertType & PatternType.ReverseStair) > 0 && PreviousPattern.HitObjects.Count() == 1)
{
// Generate a new pattern by placing on the previous column, cycling back to the end if there is no "previous"
var pattern = new Pattern();
int targetColumn = lastColumn - 1;
if (targetColumn == RandomStart - 1)
if ((convertType & PatternType.Reverse) > 0 && PreviousPattern.HitObjects.Any())
{
targetColumn = TotalColumns - 1;
StairType = PatternType.Stair;
// Generate a new pattern by copying the last hit objects in reverse-column order
for (int i = RandomStart; i < TotalColumns; i++)
if (PreviousPattern.ColumnHasObject(i))
addToPattern(pattern, RandomStart + TotalColumns - i - 1);
return pattern;
}
addToPattern(pattern, targetColumn);
return pattern;
}
if ((convertType & PatternType.Cycle) > 0 && PreviousPattern.HitObjects.Count() == 1
// If we convert to 7K + 1, let's not overload the special key
&& (TotalColumns != 8 || lastColumn != 0)
// Make sure the last column was not the centre column
&& (TotalColumns % 2 == 0 || lastColumn != TotalColumns / 2))
{
// Generate a new pattern by cycling backwards (similar to Reverse but for only one hit object)
int column = RandomStart + TotalColumns - lastColumn - 1;
addToPattern(pattern, column);
if ((convertType & PatternType.KeepSingle) > 0)
return generateRandomNotes(1);
return pattern;
}
if ((convertType & PatternType.ForceStack) > 0 && PreviousPattern.HitObjects.Any())
{
// Generate a new pattern by placing on the already filled columns
for (int i = RandomStart; i < TotalColumns; i++)
if (PreviousPattern.ColumnHasObject(i))
addToPattern(pattern, i);
return pattern;
}
if (PreviousPattern.HitObjects.Count() == 1)
{
if ((convertType & PatternType.Stair) > 0)
{
// Generate a new pattern by placing on the next column, cycling back to the start if there is no "next"
int targetColumn = lastColumn + 1;
if (targetColumn == TotalColumns)
targetColumn = RandomStart;
addToPattern(pattern, targetColumn);
return pattern;
}
if ((convertType & PatternType.ReverseStair) > 0)
{
// Generate a new pattern by placing on the previous column, cycling back to the end if there is no "previous"
int targetColumn = lastColumn - 1;
if (targetColumn == RandomStart - 1)
targetColumn = TotalColumns - 1;
addToPattern(pattern, targetColumn);
return pattern;
}
}
if ((convertType & PatternType.KeepSingle) > 0)
return pattern = generateRandomNotes(1);
if ((convertType & PatternType.Mirror) > 0)
{
if (ConversionDifficulty > 6.5)
return pattern = generateRandomPatternWithMirrored(0.12, 0.38, 0.12);
if (ConversionDifficulty > 4)
return pattern = generateRandomPatternWithMirrored(0.12, 0.17, 0);
return pattern = generateRandomPatternWithMirrored(0.12, 0, 0);
}
if ((convertType & PatternType.Mirror) > 0)
{
if (ConversionDifficulty > 6.5)
return generateRandomPatternWithMirrored(0.12, 0.38, 0.12);
{
if ((convertType & PatternType.LowProbability) > 0)
return pattern = generateRandomPattern(0.78, 0.42, 0, 0);
return pattern = generateRandomPattern(1, 0.62, 0, 0);
}
if (ConversionDifficulty > 4)
return generateRandomPatternWithMirrored(0.12, 0.17, 0);
return generateRandomPatternWithMirrored(0.12, 0, 0);
}
{
if ((convertType & PatternType.LowProbability) > 0)
return pattern = generateRandomPattern(0.35, 0.08, 0, 0);
return pattern = generateRandomPattern(0.52, 0.15, 0, 0);
}
if (ConversionDifficulty > 6.5)
if (ConversionDifficulty > 2)
{
if ((convertType & PatternType.LowProbability) > 0)
return pattern = generateRandomPattern(0.18, 0, 0, 0);
return pattern = generateRandomPattern(0.45, 0, 0, 0);
}
return pattern = generateRandomPattern(0, 0, 0, 0);
}
finally
{
if ((convertType & PatternType.LowProbability) > 0)
return generateRandomPattern(0.78, 0.42, 0, 0);
return generateRandomPattern(1, 0.62, 0, 0);
foreach (var obj in pattern.HitObjects)
{
if ((convertType & PatternType.Stair) > 0 && obj.Column == TotalColumns - 1)
StairType = PatternType.ReverseStair;
if ((convertType & PatternType.ReverseStair) > 0 && obj.Column == RandomStart)
StairType = PatternType.Stair;
}
}
if (ConversionDifficulty > 4)
{
if ((convertType & PatternType.LowProbability) > 0)
return generateRandomPattern(0.35, 0.08, 0, 0);
return generateRandomPattern(0.52, 0.15, 0, 0);
}
if (ConversionDifficulty > 2)
{
if ((convertType & PatternType.LowProbability) > 0)
return generateRandomPattern(0.18, 0, 0, 0);
return generateRandomPattern(0.45, 0, 0, 0);
}
return generateRandomPattern(0, 0, 0, 0);
}
/// <summary>
@@ -103,17 +103,14 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
HitObject lastObject = OriginalBeatmap.HitObjects.LastOrDefault();
HitObject firstObject = OriginalBeatmap.HitObjects.FirstOrDefault();
double drainTime = (lastObject?.StartTime ?? 0) - (firstObject?.StartTime ?? 0);
drainTime -= OriginalBeatmap.TotalBreakTime;
// Drain time in seconds
int drainTime = (int)(((lastObject?.StartTime ?? 0) - (firstObject?.StartTime ?? 0) - OriginalBeatmap.TotalBreakTime) / 1000);
if (drainTime == 0)
drainTime = 10000000;
// We need this in seconds
drainTime /= 1000;
drainTime = 10000;
BeatmapDifficulty difficulty = OriginalBeatmap.BeatmapInfo.BaseDifficulty;
conversionDifficulty = ((difficulty.DrainRate + MathHelper.Clamp(difficulty.ApproachRate, 4, 7)) / 1.5 + OriginalBeatmap.HitObjects.Count() / drainTime * 9f) / 38f * 5f / 1.15;
conversionDifficulty = ((difficulty.DrainRate + MathHelper.Clamp(difficulty.ApproachRate, 4, 7)) / 1.5 + (double)OriginalBeatmap.HitObjects.Count() / drainTime * 9f) / 38f * 5f / 1.15;
conversionDifficulty = Math.Min(conversionDifficulty.Value, 12);
return conversionDifficulty.Value;
@@ -2,6 +2,7 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using System.Collections.Generic;
using osu.Game.Rulesets.Objects;
namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns
@@ -42,9 +43,9 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns
}
/// <summary>
/// Generates the pattern for <see cref="HitObject"/>, filled with hit objects.
/// Generates the patterns for <see cref="HitObject"/>, each filled with hit objects.
/// </summary>
/// <returns>The <see cref="Pattern"/> containing the hit objects.</returns>
public abstract Pattern Generate();
/// <returns>The <see cref="Pattern"/>s containing the hit objects.</returns>
public abstract IEnumerable<Pattern> Generate();
}
}
@@ -4,6 +4,7 @@
using osu.Framework.Configuration.Tracking;
using osu.Game.Configuration;
using osu.Game.Rulesets.Configuration;
using osu.Game.Rulesets.Mania.UI;
namespace osu.Game.Rulesets.Mania.Configuration
{
@@ -19,6 +20,7 @@ namespace osu.Game.Rulesets.Mania.Configuration
base.InitialiseDefaults();
Set(ManiaSetting.ScrollTime, 1500.0, 50.0, 10000.0, 50.0);
Set(ManiaSetting.ScrollDirection, ManiaScrollingDirection.Down);
}
public override TrackedSettings CreateTrackedSettings() => new TrackedSettings
@@ -29,6 +31,7 @@ namespace osu.Game.Rulesets.Mania.Configuration
public enum ManiaSetting
{
ScrollTime
ScrollTime,
ScrollDirection
}
}
@@ -0,0 +1,18 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Rulesets.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)
{
}
}
}
@@ -39,6 +39,9 @@ namespace osu.Game.Rulesets.Mania.Difficulty
protected override DifficultyAttributes Calculate(IBeatmap beatmap, Mod[] mods, double timeRate)
{
if (!beatmap.HitObjects.Any())
return new ManiaDifficultyAttributes(mods, 0);
var difficultyHitObjects = new List<ManiaHitObjectDifficulty>();
int columnCount = ((ManiaBeatmap)beatmap).TotalColumns;
@@ -50,9 +53,14 @@ namespace osu.Game.Rulesets.Mania.Difficulty
if (!calculateStrainValues(difficultyHitObjects, timeRate))
return new DifficultyAttributes(mods, 0);
double starRating = calculateDifficulty(difficultyHitObjects, timeRate) * star_scaling_factor;
return new DifficultyAttributes(mods, starRating);
return new ManiaDifficultyAttributes(mods, starRating)
{
// 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
};
}
private bool calculateStrainValues(List<ManiaHitObjectDifficulty> objects, double timeRate)
@@ -13,6 +13,8 @@ namespace osu.Game.Rulesets.Mania.Difficulty
{
public class ManiaPerformanceCalculator : PerformanceCalculator
{
protected new ManiaDifficultyAttributes Attributes => (ManiaDifficultyAttributes)base.Attributes;
private Mod[] mods;
// Score after being scaled by non-difficulty-increasing mods
@@ -105,14 +107,12 @@ namespace osu.Game.Rulesets.Mania.Difficulty
private double computeAccuracyValue(double strainValue)
{
// Todo: This int cast is temporary to achieve 1:1 results with osu!stable, and should be remoevd in the future
double hitWindowGreat = (int)(Beatmap.HitObjects.First().HitWindows.Great / 2) / TimeRate;
if (hitWindowGreat <= 0)
if (Attributes.GreatHitWindow <= 0)
return 0;
// Lots of arbitrary values from testing.
// Considering to use derivation from perfect accuracy in a probabilistic manner - assume normal distribution
double accuracyValue = Math.Max(0.0, 0.2 - (hitWindowGreat - 34) * 0.006667)
double accuracyValue = Math.Max(0.0, 0.2 - (Attributes.GreatHitWindow - 34) * 0.006667)
* strainValue
* Math.Pow(Math.Max(0.0, scaledScore - 960000) / 40000, 1.1);
@@ -8,6 +8,7 @@ namespace osu.Game.Rulesets.Mania.Judgements
public class HoldNoteJudgement : ManiaJudgement
{
public override bool AffectsCombo => false;
protected override int NumericResultFor(HitResult result) => 0;
}
}
+3
View File
@@ -16,6 +16,7 @@ using osu.Game.Rulesets.Mania.Replays;
using osu.Game.Rulesets.Replays.Types;
using osu.Game.Beatmaps.Legacy;
using osu.Game.Configuration;
using osu.Game.Overlays.Settings;
using osu.Game.Rulesets.Configuration;
using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Mania.Beatmaps;
@@ -155,6 +156,8 @@ namespace osu.Game.Rulesets.Mania
public override IRulesetConfigManager CreateConfig(SettingsStore settings) => new ManiaConfigManager(settings, RulesetInfo);
public override RulesetSettingsSubsection CreateSettings() => new ManiaSettingsSubsection(this);
public ManiaRuleset(RulesetInfo rulesetInfo = null)
: base(rulesetInfo)
{
@@ -0,0 +1,34 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Game.Overlays.Settings;
using osu.Game.Rulesets.Mania.Configuration;
using osu.Game.Rulesets.Mania.UI;
namespace osu.Game.Rulesets.Mania
{
public class ManiaSettingsSubsection : RulesetSettingsSubsection
{
protected override string Header => "osu!mania";
public ManiaSettingsSubsection(ManiaRuleset ruleset)
: base(ruleset)
{
}
[BackgroundDependencyLoader]
private void load(ManiaConfigManager config)
{
Children = new Drawable[]
{
new SettingsEnumDropdown<ManiaScrollingDirection>
{
LabelText = "Scrolling direction",
Bindable = config.GetBindable<ManiaScrollingDirection>(ManiaSetting.ScrollDirection)
}
};
}
}
}
@@ -15,11 +15,15 @@ namespace osu.Game.Rulesets.Mania.MathUtils
private const uint y = 842502087;
private const uint z = 3579807591;
private const uint w = 273326509;
private uint _x, _y = y, _z = z, _w = w;
internal uint X { get; private set; }
internal uint Y { get; private set; } = y;
internal uint Z { get; private set; } = z;
internal uint W { get; private set; } = w;
public FastRandom(int seed)
{
_x = (uint)seed;
X = (uint)seed;
}
public FastRandom()
@@ -33,11 +37,11 @@ namespace osu.Game.Rulesets.Mania.MathUtils
/// <returns>The random value.</returns>
public uint NextUInt()
{
uint t = _x ^ _x << 11;
_x = _y;
_y = _z;
_z = _w;
return _w = _w ^ _w >> 19 ^ t ^ t >> 8;
uint t = X ^ X << 11;
X = Y;
Y = Z;
Z = W;
return W = W ^ W >> 19 ^ t ^ t >> 8;
}
/// <summary>
@@ -10,6 +10,7 @@ using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Mania.Judgements;
using osu.Framework.Input.Bindings;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.UI.Scrolling;
namespace osu.Game.Rulesets.Mania.Objects.Drawables
{
@@ -37,8 +38,8 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
private readonly Container<DrawableHoldNoteTick> tickContainer;
public DrawableHoldNote(HoldNote hitObject, ManiaAction action)
: base(hitObject, action)
public DrawableHoldNote(HoldNote hitObject)
: base(hitObject)
{
RelativeSizeAxes = Axes.X;
@@ -56,12 +57,12 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
HoldStartTime = () => holdStartTime
})
},
head = new DrawableHeadNote(this, action)
head = new DrawableHeadNote(this)
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre
},
tail = new DrawableTailNote(this, action)
tail = new DrawableTailNote(this)
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre
@@ -75,6 +76,13 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
AddNested(tail);
}
protected override void OnDirectionChanged(ScrollingDirection direction)
{
base.OnDirectionChanged(direction);
bodyPiece.Anchor = bodyPiece.Origin = direction == ScrollingDirection.Up ? Anchor.TopLeft : Anchor.BottomLeft;
}
public override Color4 AccentColour
{
get { return base.AccentColour; }
@@ -100,7 +108,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
base.Update();
// Make the body piece not lie under the head note
bodyPiece.Y = head.Height / 2;
bodyPiece.Y = (Direction.Value == ScrollingDirection.Up ? 1 : -1) * head.Height / 2;
bodyPiece.Height = DrawHeight - head.Height / 2 + tail.Height / 2;
}
@@ -110,7 +118,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
if (Time.Current < HitObject.StartTime || Time.Current > HitObject.EndTime)
return false;
if (action != Action)
if (action != Action.Value)
return false;
// The user has pressed during the body of the hold note, after the head note and its hit windows have passed
@@ -127,7 +135,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
if (!holdStartTime.HasValue)
return false;
if (action != Action)
if (action != Action.Value)
return false;
holdStartTime = null;
@@ -146,8 +154,8 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
{
private readonly DrawableHoldNote holdNote;
public DrawableHeadNote(DrawableHoldNote holdNote, ManiaAction action)
: base(holdNote.HitObject.Head, action)
public DrawableHeadNote(DrawableHoldNote holdNote)
: base(holdNote.HitObject.Head)
{
this.holdNote = holdNote;
}
@@ -183,8 +191,8 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
private readonly DrawableHoldNote holdNote;
public DrawableTailNote(DrawableHoldNote holdNote, ManiaAction action)
: base(holdNote.HitObject.Tail, action)
public DrawableTailNote(DrawableHoldNote holdNote)
: base(holdNote.HitObject.Tail)
{
this.holdNote = holdNote;
}
@@ -227,7 +235,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
if (!holdNote.holdStartTime.HasValue)
return false;
if (action != Action)
if (action != Action.Value)
return false;
UpdateJudgement(true);
@@ -1,31 +1,55 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using JetBrains.Annotations;
using osu.Framework.Allocation;
using osu.Framework.Configuration;
using osu.Framework.Graphics;
using osu.Game.Rulesets.Mania.UI;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.UI.Scrolling;
namespace osu.Game.Rulesets.Mania.Objects.Drawables
{
public abstract class DrawableManiaHitObject<TObject> : DrawableHitObject<ManiaHitObject>
where TObject : ManiaHitObject
public abstract class DrawableManiaHitObject : DrawableHitObject<ManiaHitObject>
{
/// <summary>
/// The key that will trigger input for this hit object.
/// The <see cref="ManiaAction"/> which causes this <see cref="DrawableManiaHitObject{TObject}"/> to be hit.
/// </summary>
protected ManiaAction Action { get; }
protected readonly IBindable<ManiaAction> Action = new Bindable<ManiaAction>();
public new TObject HitObject;
protected readonly IBindable<ScrollingDirection> Direction = new Bindable<ScrollingDirection>();
protected DrawableManiaHitObject(TObject hitObject, ManiaAction? action = null)
protected DrawableManiaHitObject(ManiaHitObject hitObject)
: base(hitObject)
{
Anchor = Anchor.TopCentre;
Origin = Anchor.TopCentre;
HitObject = hitObject;
}
[BackgroundDependencyLoader(true)]
private void load([CanBeNull] IBindable<ManiaAction> action, [NotNull] IScrollingInfo scrollingInfo)
{
if (action != null)
Action = action.Value;
Action.BindTo(action);
Direction.BindTo(scrollingInfo.Direction);
Direction.BindValueChanged(OnDirectionChanged, true);
}
protected virtual void OnDirectionChanged(ScrollingDirection direction)
{
Anchor = Origin = direction == ScrollingDirection.Up ? Anchor.TopCentre : Anchor.BottomCentre;
}
}
public abstract class DrawableManiaHitObject<TObject> : DrawableManiaHitObject
where TObject : ManiaHitObject
{
public new readonly TObject HitObject;
protected DrawableManiaHitObject(TObject hitObject)
: base(hitObject)
{
HitObject = hitObject;
}
protected override void UpdateState(ArmedState state)
@@ -9,6 +9,7 @@ using osu.Framework.Input.Bindings;
using osu.Game.Rulesets.Mania.Judgements;
using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.UI.Scrolling;
namespace osu.Game.Rulesets.Mania.Objects.Drawables
{
@@ -19,8 +20,8 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
{
private readonly NotePiece headPiece;
public DrawableNote(Note hitObject, ManiaAction action)
: base(hitObject, action)
public DrawableNote(Note hitObject)
: base(hitObject)
{
RelativeSizeAxes = Axes.X;
AutoSizeAxes = Axes.Y;
@@ -28,14 +29,14 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
CornerRadius = 5;
Masking = true;
InternalChildren = new Drawable[]
{
headPiece = new NotePiece
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre
}
};
InternalChild = headPiece = new NotePiece();
}
protected override void OnDirectionChanged(ScrollingDirection direction)
{
base.OnDirectionChanged(direction);
headPiece.Anchor = headPiece.Origin = direction == ScrollingDirection.Up ? Anchor.TopCentre : Anchor.BottomCentre;
}
public override Color4 AccentColour
@@ -73,7 +74,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
public virtual bool OnPressed(ManiaAction action)
{
if (action != Action)
if (action != Action.Value)
return false;
return UpdateJudgement(true);
@@ -1,12 +1,16 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.Allocation;
using osu.Framework.Configuration;
using OpenTK.Graphics;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Game.Graphics;
using osu.Game.Rulesets.Mania.UI;
using osu.Game.Rulesets.UI.Scrolling;
namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces
{
@@ -18,6 +22,8 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces
public const float NOTE_HEIGHT = 10;
private const float head_colour_height = 6;
private readonly IBindable<ScrollingDirection> direction = new Bindable<ScrollingDirection>();
private readonly Box colouredBox;
public NotePiece()
@@ -33,8 +39,6 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces
},
colouredBox = new Box
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
RelativeSizeAxes = Axes.X,
Height = head_colour_height,
Alpha = 0.2f
@@ -42,6 +46,16 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces
};
}
[BackgroundDependencyLoader]
private void load(IScrollingInfo scrollingInfo)
{
direction.BindTo(scrollingInfo.Direction);
direction.BindValueChanged(direction =>
{
colouredBox.Anchor = colouredBox.Origin = direction == ScrollingDirection.Up ? Anchor.TopCentre : Anchor.BottomCentre;
}, true);
}
private Color4 accentColour;
public Color4 AccentColour
{
@@ -1,103 +1,132 @@
{
"Mappings": [{
"StartTime": 500,
"Objects": [{
"StartTime": 500,
"EndTime": 2500,
"Column": 0
},
{
"StartTime": 1500,
"EndTime": 2500,
"Column": 1
}
]
},
{
"StartTime": 3000,
"Objects": [{
"StartTime": 3000,
"EndTime": 4000,
"Column": 2
}]
},
{
"StartTime": 4500,
"Objects": [{
"StartTime": 4500,
"EndTime": 5500,
"Column": 4
}]
},
{
"StartTime": 6000,
"Objects": [{
"StartTime": 6000,
"EndTime": 6500,
"Column": 2
}]
},
{
"StartTime": 7000,
"Objects": [{
"StartTime": 7000,
"EndTime": 8000,
"Column": 2
}]
},
{
"StartTime": 8500,
"Objects": [{
"StartTime": 8500,
"EndTime": 11000,
"Column": 0
}]
},
{
"StartTime": 11500,
"Objects": [{
"StartTime": 11500,
"EndTime": 12000,
"Column": 1
}]
},
{
"StartTime": 12500,
"Objects": [{
"StartTime": 12500,
"EndTime": 16500,
"Column": 4
}]
},
{
"StartTime": 17000,
"Objects": [{
"StartTime": 17000,
"EndTime": 18000,
"Column": 2
}]
},
{
"StartTime": 18500,
"Objects": [{
"StartTime": 18500,
"EndTime": 19450,
"Column": 0
}]
},
{
"StartTime": 19875,
"Objects": [{
"StartTime": 19875,
"EndTime": 23875,
"Column": 1
},
{
"StartTime": 19875,
"EndTime": 23875,
"Column": 0
}
]
}
]
"RandomW": 2659373485,
"RandomX": 3579807591,
"RandomY": 273326509,
"RandomZ": 272969173,
"StartTime": 500.0,
"Objects": [{
"StartTime": 500.0,
"EndTime": 2500.0,
"Column": 0
}, {
"StartTime": 1500.0,
"EndTime": 2500.0,
"Column": 1
}]
}, {
"RandomW": 3083803045,
"RandomX": 273326509,
"RandomY": 272969173,
"RandomZ": 2659373485,
"StartTime": 3000.0,
"Objects": [{
"StartTime": 3000.0,
"EndTime": 4000.0,
"Column": 2
}]
}, {
"RandomW": 4073554232,
"RandomX": 272969173,
"RandomY": 2659373485,
"RandomZ": 3083803045,
"StartTime": 4500.0,
"Objects": [{
"StartTime": 4500.0,
"EndTime": 5500.0,
"Column": 4
}]
}, {
"RandomW": 3420401969,
"RandomX": 2659373485,
"RandomY": 3083803045,
"RandomZ": 4073554232,
"StartTime": 6000.0,
"Objects": [{
"StartTime": 6000.0,
"EndTime": 6500.0,
"Column": 2
}]
}, {
"RandomW": 1129881182,
"RandomX": 3083803045,
"RandomY": 4073554232,
"RandomZ": 3420401969,
"StartTime": 7000.0,
"Objects": [{
"StartTime": 7000.0,
"EndTime": 8000.0,
"Column": 2
}]
}, {
"RandomW": 315568458,
"RandomX": 3420401969,
"RandomY": 1129881182,
"RandomZ": 2358617505,
"StartTime": 8500.0,
"Objects": [{
"StartTime": 8500.0,
"EndTime": 11000.0,
"Column": 0
}]
}, {
"RandomW": 548134043,
"RandomX": 1129881182,
"RandomY": 2358617505,
"RandomZ": 315568458,
"StartTime": 11500.0,
"Objects": [{
"StartTime": 11500.0,
"EndTime": 12000.0,
"Column": 1
}]
}, {
"RandomW": 3979422122,
"RandomX": 548134043,
"RandomY": 2810584254,
"RandomZ": 2250186050,
"StartTime": 12500.0,
"Objects": [{
"StartTime": 12500.0,
"EndTime": 16500.0,
"Column": 4
}]
}, {
"RandomW": 2466283411,
"RandomX": 2810584254,
"RandomY": 2250186050,
"RandomZ": 3979422122,
"StartTime": 17000.0,
"Objects": [{
"StartTime": 17000.0,
"EndTime": 18000.0,
"Column": 2
}]
}, {
"RandomW": 83157665,
"RandomX": 2250186050,
"RandomY": 3979422122,
"RandomZ": 2466283411,
"StartTime": 18500.0,
"Objects": [{
"StartTime": 18500.0,
"EndTime": 19450.0,
"Column": 0
}]
}, {
"RandomW": 2383087700,
"RandomX": 83157665,
"RandomY": 2055150192,
"RandomZ": 510071020,
"StartTime": 19875.0,
"Objects": [{
"StartTime": 19875.0,
"EndTime": 23875.0,
"Column": 1
}, {
"StartTime": 19875.0,
"EndTime": 23875.0,
"Column": 0
}]
}]
}
+46 -170
View File
@@ -1,50 +1,39 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using OpenTK;
using OpenTK.Graphics;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Colour;
using osu.Game.Graphics;
using osu.Game.Rulesets.Objects.Drawables;
using System;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Configuration;
using osu.Framework.Input.Bindings;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Mania.UI.Components;
using osu.Game.Rulesets.UI.Scrolling;
namespace osu.Game.Rulesets.Mania.UI
{
public class Column : ScrollingPlayfield, IKeyBindingHandler<ManiaAction>, IHasAccentColour
public class Column : ManiaScrollingPlayfield, IKeyBindingHandler<ManiaAction>, IHasAccentColour
{
private const float key_icon_size = 10;
private const float key_icon_corner_radius = 3;
private const float key_icon_border_radius = 2;
private const float hit_target_height = 10;
private const float hit_target_bar_height = 2;
private const float column_width = 45;
private const float special_column_width = 70;
public ManiaAction Action;
public readonly Bindable<ManiaAction> Action = new Bindable<ManiaAction>();
private readonly Box background;
private readonly Box backgroundOverlay;
private readonly Container hitTargetBar;
private readonly Container keyIcon;
private readonly ColumnBackground background;
private readonly ColumnKeyArea keyArea;
private readonly ColumnHitObjectArea hitObjectArea;
internal readonly Container TopLevelContainer;
private readonly Container explosionContainer;
protected override Container<Drawable> Content => content;
private readonly Container<Drawable> content;
protected override Container<Drawable> Content => hitObjectArea;
public Column()
: base(ScrollingDirection.Up)
public Column(ScrollingDirection direction)
: base(direction)
{
RelativeSizeAxes = Axes.Y;
Width = column_width;
@@ -52,71 +41,21 @@ namespace osu.Game.Rulesets.Mania.UI
Masking = true;
CornerRadius = 5;
InternalChildren = new Drawable[]
background = new ColumnBackground { RelativeSizeAxes = Axes.Both };
Container hitTargetContainer;
InternalChildren = new[]
{
background = new Box
{
Name = "Background",
RelativeSizeAxes = Axes.Both,
Alpha = 0.3f
},
backgroundOverlay = new Box
{
Name = "Background Gradient Overlay",
RelativeSizeAxes = Axes.Both,
Height = 0.5f,
Anchor = Anchor.TopLeft,
Origin = Anchor.TopLeft,
Blending = BlendingMode.Additive,
Alpha = 0
},
new Container
// For input purposes, the background is added at the highest depth, but is then proxied back below all other elements
background.CreateProxy(),
hitTargetContainer = new Container
{
Name = "Hit target + hit objects",
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding { Top = ManiaStage.HIT_TARGET_POSITION },
Children = new Drawable[]
{
new Container
{
Name = "Hit target",
RelativeSizeAxes = Axes.X,
Height = hit_target_height,
Children = new Drawable[]
{
new Box
{
Name = "Background",
RelativeSizeAxes = Axes.Both,
Colour = Color4.Black
},
hitTargetBar = new Container
{
Name = "Bar",
RelativeSizeAxes = Axes.X,
Height = hit_target_bar_height,
Masking = true,
Children = new[]
{
new Box
{
RelativeSizeAxes = Axes.Both
}
}
}
}
},
content = new Container
{
Name = "Hit objects",
RelativeSizeAxes = Axes.Both,
},
// For column lighting, we need to capture input events before the notes
new InputTarget
{
Pressed = onPressed,
Released = onReleased
},
hitObjectArea = new ColumnHitObjectArea { RelativeSizeAxes = Axes.Both },
explosionContainer = new Container
{
Name = "Hit explosions",
@@ -124,46 +63,27 @@ namespace osu.Game.Rulesets.Mania.UI
}
}
},
new Container
keyArea = new ColumnKeyArea
{
Name = "Key",
RelativeSizeAxes = Axes.X,
Height = ManiaStage.HIT_TARGET_POSITION,
Children = new Drawable[]
{
new Box
{
Name = "Key gradient",
RelativeSizeAxes = Axes.Both,
Colour = ColourInfo.GradientVertical(Color4.Black, Color4.Black.Opacity(0)),
Alpha = 0.5f
},
keyIcon = new Container
{
Name = "Key icon",
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = new Vector2(key_icon_size),
Masking = true,
CornerRadius = key_icon_corner_radius,
BorderThickness = 2,
BorderColour = Color4.White, // Not true
Children = new[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Alpha = 0,
AlwaysPresent = true
}
}
}
}
},
background,
TopLevelContainer = new Container { RelativeSizeAxes = Axes.Both }
};
TopLevelContainer.Add(explosionContainer.CreateProxy());
Direction.BindValueChanged(d =>
{
hitTargetContainer.Padding = new MarginPadding
{
Top = d == ScrollingDirection.Up ? ManiaStage.HIT_TARGET_POSITION : 0,
Bottom = d == ScrollingDirection.Down ? ManiaStage.HIT_TARGET_POSITION : 0,
};
keyArea.Anchor = keyArea.Origin= d == ScrollingDirection.Up ? Anchor.TopLeft : Anchor.BottomLeft;
}, true);
}
public override Axes RelativeSizeAxes => Axes.Y;
@@ -192,25 +112,19 @@ namespace osu.Game.Rulesets.Mania.UI
return;
accentColour = value;
background.Colour = accentColour;
backgroundOverlay.Colour = ColourInfo.GradientVertical(accentColour.Opacity(0.6f), accentColour.Opacity(0));
hitTargetBar.EdgeEffect = new EdgeEffectParameters
{
Type = EdgeEffectType.Glow,
Radius = 5,
Colour = accentColour.Opacity(0.5f),
};
keyIcon.EdgeEffect = new EdgeEffectParameters
{
Type = EdgeEffectType.Glow,
Radius = 5,
Colour = accentColour.Opacity(0.5f),
};
background.AccentColour = value;
keyArea.AccentColour = value;
hitObjectArea.AccentColour = value;
}
}
protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent)
{
var dependencies = new DependencyContainer(base.CreateLocalDependencies(parent));
dependencies.CacheAs<IBindable<ManiaAction>>(Action);
return dependencies;
}
/// <summary>
/// Adds a DrawableHitObject to this Playfield.
/// </summary>
@@ -228,48 +142,10 @@ namespace osu.Game.Rulesets.Mania.UI
if (!judgement.IsHit || !judgedObject.DisplayJudgement)
return;
explosionContainer.Add(new HitExplosion(judgedObject));
}
private bool onPressed(ManiaAction action)
{
if (action == Action)
explosionContainer.Add(new HitExplosion(judgedObject)
{
backgroundOverlay.FadeTo(1, 50, Easing.OutQuint).Then().FadeTo(0.5f, 250, Easing.OutQuint);
keyIcon.ScaleTo(1.4f, 50, Easing.OutQuint).Then().ScaleTo(1.3f, 250, Easing.OutQuint);
}
return false;
}
private bool onReleased(ManiaAction action)
{
if (action == Action)
{
backgroundOverlay.FadeTo(0, 250, Easing.OutQuint);
keyIcon.ScaleTo(1f, 125, Easing.OutQuint);
}
return false;
}
/// <summary>
/// This is a simple container which delegates various input events that have to be captured before the notes.
/// </summary>
private class InputTarget : Container, IKeyBindingHandler<ManiaAction>
{
public Func<ManiaAction, bool> Pressed;
public Func<ManiaAction, bool> Released;
public InputTarget()
{
RelativeSizeAxes = Axes.Both;
AlwaysPresent = true;
Alpha = 0;
}
public bool OnPressed(ManiaAction action) => Pressed?.Invoke(action) ?? false;
public bool OnReleased(ManiaAction action) => Released?.Invoke(action) ?? false;
Anchor = Direction == ScrollingDirection.Up ? Anchor.TopCentre : Anchor.BottomCentre
});
}
public bool OnPressed(ManiaAction action)
@@ -0,0 +1,108 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.Allocation;
using osu.Framework.Configuration;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Colour;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Input.Bindings;
using osu.Game.Graphics;
using osu.Game.Rulesets.UI.Scrolling;
using OpenTK.Graphics;
namespace osu.Game.Rulesets.Mania.UI.Components
{
public class ColumnBackground : CompositeDrawable, IKeyBindingHandler<ManiaAction>, IHasAccentColour
{
private readonly IBindable<ManiaAction> action = new Bindable<ManiaAction>();
private Box background;
private Box backgroundOverlay;
private readonly IBindable<ScrollingDirection> direction = new Bindable<ScrollingDirection>();
[BackgroundDependencyLoader]
private void load(IBindable<ManiaAction> action, IScrollingInfo scrollingInfo)
{
this.action.BindTo(action);
InternalChildren = new[]
{
background = new Box
{
Name = "Background",
RelativeSizeAxes = Axes.Both,
Alpha = 0.3f
},
backgroundOverlay = new Box
{
Name = "Background Gradient Overlay",
RelativeSizeAxes = Axes.Both,
Height = 0.5f,
Blending = BlendingMode.Additive,
Alpha = 0
}
};
direction.BindTo(scrollingInfo.Direction);
direction.BindValueChanged(direction =>
{
backgroundOverlay.Anchor = backgroundOverlay.Origin = direction == ScrollingDirection.Up ? Anchor.TopLeft : Anchor.BottomLeft;
updateColours();
}, true);
}
protected override void LoadComplete()
{
base.LoadComplete();
updateColours();
}
private Color4 accentColour;
public Color4 AccentColour
{
get => accentColour;
set
{
if (accentColour == value)
return;
accentColour = value;
updateColours();
}
}
private void updateColours()
{
if (!IsLoaded)
return;
background.Colour = AccentColour;
var brightPoint = AccentColour.Opacity(0.6f);
var dimPoint = AccentColour.Opacity(0);
backgroundOverlay.Colour = ColourInfo.GradientVertical(
direction.Value == ScrollingDirection.Up ? brightPoint : dimPoint,
direction.Value == ScrollingDirection.Up ? dimPoint : brightPoint);
}
public bool OnPressed(ManiaAction action)
{
if (action == this.action.Value)
backgroundOverlay.FadeTo(1, 50, Easing.OutQuint).Then().FadeTo(0.5f, 250, Easing.OutQuint);
return false;
}
public bool OnReleased(ManiaAction action)
{
if (action == this.action.Value)
backgroundOverlay.FadeTo(0, 250, Easing.OutQuint);
return false;
}
}
}
@@ -0,0 +1,99 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.Allocation;
using osu.Framework.Configuration;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Game.Graphics;
using osu.Game.Rulesets.UI.Scrolling;
using OpenTK.Graphics;
namespace osu.Game.Rulesets.Mania.UI.Components
{
public class ColumnHitObjectArea : Container, IHasAccentColour
{
private const float hit_target_height = 10;
private const float hit_target_bar_height = 2;
private Container<Drawable> content;
protected override Container<Drawable> Content => content;
private readonly IBindable<ScrollingDirection> direction = new Bindable<ScrollingDirection>();
private Container hitTargetLine;
[BackgroundDependencyLoader]
private void load(IScrollingInfo scrollingInfo)
{
Drawable hitTargetBar;
InternalChildren = new[]
{
hitTargetBar = new Box
{
RelativeSizeAxes = Axes.X,
Height = hit_target_height,
Colour = Color4.Black
},
hitTargetLine = new Container
{
RelativeSizeAxes = Axes.X,
Height = hit_target_bar_height,
Masking = true,
Child = new Box { RelativeSizeAxes = Axes.Both }
},
content = new Container
{
Name = "Hit objects",
RelativeSizeAxes = Axes.Both,
},
};
direction.BindTo(scrollingInfo.Direction);
direction.BindValueChanged(direction =>
{
Anchor anchor = direction == ScrollingDirection.Up ? Anchor.TopLeft : Anchor.BottomLeft;
hitTargetBar.Anchor = hitTargetBar.Origin = anchor;
hitTargetLine.Anchor = hitTargetLine.Origin = anchor;
}, true);
}
protected override void LoadComplete()
{
base.LoadComplete();
updateColours();
}
private Color4 accentColour;
public Color4 AccentColour
{
get => accentColour;
set
{
if (accentColour == value)
return;
accentColour = value;
updateColours();
}
}
private void updateColours()
{
if (!IsLoaded)
return;
hitTargetLine.EdgeEffect = new EdgeEffectParameters
{
Type = EdgeEffectType.Glow,
Radius = 5,
Colour = accentColour.Opacity(0.5f),
};
}
}
}
@@ -0,0 +1,123 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.Allocation;
using osu.Framework.Configuration;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Colour;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Input.Bindings;
using osu.Game.Graphics;
using osu.Game.Rulesets.UI.Scrolling;
using OpenTK;
using OpenTK.Graphics;
namespace osu.Game.Rulesets.Mania.UI.Components
{
public class ColumnKeyArea : CompositeDrawable, IKeyBindingHandler<ManiaAction>, IHasAccentColour
{
private const float key_icon_size = 10;
private const float key_icon_corner_radius = 3;
private readonly IBindable<ManiaAction> action = new Bindable<ManiaAction>();
private readonly IBindable<ScrollingDirection> direction = new Bindable<ScrollingDirection>();
private Container keyIcon;
[BackgroundDependencyLoader]
private void load(IBindable<ManiaAction> action, IScrollingInfo scrollingInfo)
{
this.action.BindTo(action);
Drawable gradient;
InternalChildren = new[]
{
gradient = new Box
{
Name = "Key gradient",
RelativeSizeAxes = Axes.Both,
Alpha = 0.5f
},
keyIcon = new Container
{
Name = "Key icon",
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = new Vector2(key_icon_size),
Masking = true,
CornerRadius = key_icon_corner_radius,
BorderThickness = 2,
BorderColour = Color4.White, // Not true
Children = new[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Alpha = 0,
AlwaysPresent = true
}
}
}
};
direction.BindTo(scrollingInfo.Direction);
direction.BindValueChanged(direction =>
{
gradient.Colour = ColourInfo.GradientVertical(
direction == ScrollingDirection.Up ? Color4.Black : Color4.Black.Opacity(0),
direction == ScrollingDirection.Up ? Color4.Black.Opacity(0) : Color4.Black);
}, true);
}
protected override void LoadComplete()
{
base.LoadComplete();
updateColours();
}
private Color4 accentColour;
public Color4 AccentColour
{
get => accentColour;
set
{
if (accentColour == value)
return;
accentColour = value;
updateColours();
}
}
private void updateColours()
{
if (!IsLoaded)
return;
keyIcon.EdgeEffect = new EdgeEffectParameters
{
Type = EdgeEffectType.Glow,
Radius = 5,
Colour = accentColour.Opacity(0.5f),
};
}
public bool OnPressed(ManiaAction action)
{
if (action == this.action.Value)
keyIcon.ScaleTo(1.4f, 50, Easing.OutQuint).Then().ScaleTo(1.3f, 250, Easing.OutQuint);
return false;
}
public bool OnReleased(ManiaAction action)
{
if (action == this.action.Value)
keyIcon.ScaleTo(1f, 125, Easing.OutQuint);
return false;
}
}
}
@@ -23,7 +23,6 @@ namespace osu.Game.Rulesets.Mania.UI
{
bool isTick = judgedObject is DrawableHoldNoteTick;
Anchor = Anchor.TopCentre;
Origin = Anchor.Centre;
RelativeSizeAxes = Axes.X;
@@ -0,0 +1,17 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.Configuration;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.UI.Scrolling;
namespace osu.Game.Rulesets.Mania.UI
{
public interface IScrollingInfo
{
/// <summary>
/// The direction <see cref="HitObject"/>s should scroll in.
/// </summary>
IBindable<ScrollingDirection> Direction { get; }
}
}
+4 -13
View File
@@ -8,7 +8,6 @@ using System;
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Configuration;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Objects.Drawables;
@@ -17,18 +16,13 @@ using osu.Game.Rulesets.UI.Scrolling;
namespace osu.Game.Rulesets.Mania.UI
{
public class ManiaPlayfield : ScrollingPlayfield
public class ManiaPlayfield : ManiaScrollingPlayfield
{
/// <summary>
/// Whether this playfield should be inverted. This flips everything inside the playfield.
/// </summary>
public readonly Bindable<bool> Inverted = new Bindable<bool>(true);
public List<Column> Columns => stages.SelectMany(x => x.Columns).ToList();
private readonly List<ManiaStage> stages = new List<ManiaStage>();
public ManiaPlayfield(List<StageDefinition> stageDefinitions)
: base(ScrollingDirection.Up)
public ManiaPlayfield(ScrollingDirection direction, List<StageDefinition> stageDefinitions)
: base(direction)
{
if (stageDefinitions == null)
throw new ArgumentNullException(nameof(stageDefinitions));
@@ -36,8 +30,6 @@ namespace osu.Game.Rulesets.Mania.UI
if (stageDefinitions.Count <= 0)
throw new ArgumentException("Can't have zero or fewer stages.");
Inverted.Value = true;
GridContainer playfieldGrid;
InternalChild = playfieldGrid = new GridContainer
{
@@ -50,9 +42,8 @@ namespace osu.Game.Rulesets.Mania.UI
int firstColumnIndex = 0;
for (int i = 0; i < stageDefinitions.Count; i++)
{
var newStage = new ManiaStage(firstColumnIndex, stageDefinitions[i], ref normalColumnAction, ref specialColumnAction);
var newStage = new ManiaStage(direction, firstColumnIndex, stageDefinitions[i], ref normalColumnAction, ref specialColumnAction);
newStage.VisibleTimeRange.BindTo(VisibleTimeRange);
newStage.Inverted.BindTo(Inverted);
playfieldGrid.Content[0][i] = newStage;
@@ -4,6 +4,7 @@
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Configuration;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics;
using osu.Framework.Input;
@@ -12,6 +13,7 @@ using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Input.Handlers;
using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Mania.Configuration;
using osu.Game.Rulesets.Mania.Mods;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.Objects.Drawables;
@@ -33,6 +35,9 @@ namespace osu.Game.Rulesets.Mania.UI
public IEnumerable<BarLine> BarLines;
private readonly Bindable<ManiaScrollingDirection> configDirection = new Bindable<ManiaScrollingDirection>();
private ScrollingInfo scrollingInfo;
public ManiaRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap)
: base(ruleset, beatmap)
{
@@ -65,12 +70,24 @@ namespace osu.Game.Rulesets.Mania.UI
}
[BackgroundDependencyLoader]
private void load()
private void load(ManiaConfigManager config)
{
BarLines.ForEach(Playfield.Add);
config.BindWith(ManiaSetting.ScrollDirection, configDirection);
configDirection.BindValueChanged(d => scrollingInfo.Direction.Value = (ScrollingDirection)d, true);
}
protected sealed override Playfield CreatePlayfield() => new ManiaPlayfield(Beatmap.Stages)
private DependencyContainer dependencies;
protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent)
{
dependencies = new DependencyContainer(base.CreateLocalDependencies(parent));
dependencies.CacheAs<IScrollingInfo>(scrollingInfo = new ScrollingInfo());
return dependencies;
}
protected sealed override Playfield CreatePlayfield() => new ManiaPlayfield(scrollingInfo.Direction, Beatmap.Stages)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
@@ -84,21 +101,25 @@ namespace osu.Game.Rulesets.Mania.UI
protected override DrawableHitObject<ManiaHitObject> GetVisualRepresentation(ManiaHitObject h)
{
ManiaAction action = Playfield.Columns.ElementAt(h.Column).Action;
var holdNote = h as HoldNote;
if (holdNote != null)
return new DrawableHoldNote(holdNote, action);
var note = h as Note;
if (note != null)
return new DrawableNote(note, action);
return null;
switch (h)
{
case HoldNote holdNote:
return new DrawableHoldNote(holdNote);
case Note note:
return new DrawableNote(note);
default:
return null;
}
}
protected override Vector2 PlayfieldArea => new Vector2(1, 0.8f);
protected override ReplayInputHandler CreateReplayInputHandler(Replay replay) => new ManiaFramedReplayInputHandler(replay);
private class ScrollingInfo : IScrollingInfo
{
public readonly Bindable<ScrollingDirection> Direction = new Bindable<ScrollingDirection>();
IBindable<ScrollingDirection> IScrollingInfo.Direction => Direction;
}
}
}
@@ -0,0 +1,13 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Rulesets.UI.Scrolling;
namespace osu.Game.Rulesets.Mania.UI
{
public enum ManiaScrollingDirection
{
Up = ScrollingDirection.Up,
Down = ScrollingDirection.Down
}
}
@@ -0,0 +1,26 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.Allocation;
using osu.Framework.Configuration;
using osu.Game.Rulesets.UI.Scrolling;
namespace osu.Game.Rulesets.Mania.UI
{
public class ManiaScrollingPlayfield : ScrollingPlayfield
{
private readonly IBindable<ScrollingDirection> direction = new Bindable<ScrollingDirection>();
public ManiaScrollingPlayfield(ScrollingDirection direction)
: base(direction)
{
}
[BackgroundDependencyLoader]
private void load(IScrollingInfo scrollingInfo)
{
direction.BindTo(scrollingInfo.Direction);
direction.BindValueChanged(direction => Direction.Value = direction, true);
}
}
}
+17 -24
View File
@@ -5,7 +5,6 @@ using System;
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Configuration;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
@@ -24,20 +23,15 @@ namespace osu.Game.Rulesets.Mania.UI
/// <summary>
/// A collection of <see cref="Column"/>s.
/// </summary>
internal class ManiaStage : ScrollingPlayfield
internal class ManiaStage : ManiaScrollingPlayfield
{
public const float HIT_TARGET_POSITION = 50;
/// <summary>
/// Whether this playfield should be inverted. This flips everything inside the playfield.
/// </summary>
public readonly Bindable<bool> Inverted = new Bindable<bool>(true);
public IReadOnlyList<Column> Columns => columnFlow.Children;
private readonly FillFlowContainer<Column> columnFlow;
protected override Container<Drawable> Content => content;
private readonly Container<Drawable> content;
protected override Container<Drawable> Content => barLineContainer;
private readonly Container<Drawable> barLineContainer;
public Container<DrawableManiaJudgement> Judgements => judgements;
private readonly JudgementContainer<DrawableManiaJudgement> judgements;
@@ -49,8 +43,8 @@ namespace osu.Game.Rulesets.Mania.UI
private readonly int firstColumnIndex;
public ManiaStage(int firstColumnIndex, StageDefinition definition, ref ManiaAction normalColumnStartAction, ref ManiaAction specialColumnStartAction)
: base(ScrollingDirection.Up)
public ManiaStage(ScrollingDirection direction, int firstColumnIndex, StageDefinition definition, ref ManiaAction normalColumnStartAction, ref ManiaAction specialColumnStartAction)
: base(direction)
{
this.firstColumnIndex = firstColumnIndex;
@@ -106,13 +100,12 @@ namespace osu.Game.Rulesets.Mania.UI
Width = 1366, // Bar lines should only be masked on the vertical axis
BypassAutoSizeAxes = Axes.Both,
Masking = true,
Child = content = new Container
Child = barLineContainer = new Container
{
Name = "Bar lines",
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
RelativeSizeAxes = Axes.Y,
Padding = new MarginPadding { Top = HIT_TARGET_POSITION }
}
},
judgements = new JudgementContainer<DrawableManiaJudgement>
@@ -131,23 +124,23 @@ namespace osu.Game.Rulesets.Mania.UI
for (int i = 0; i < definition.Columns; i++)
{
var isSpecial = definition.IsSpecialColumn(i);
var column = new Column
var column = new Column(direction)
{
IsSpecial = isSpecial,
Action = isSpecial ? specialColumnStartAction++ : normalColumnStartAction++
Action = { Value = isSpecial ? specialColumnStartAction++ : normalColumnStartAction++ }
};
AddColumn(column);
}
Inverted.ValueChanged += invertedChanged;
Inverted.TriggerChange();
}
private void invertedChanged(bool newValue)
{
Scale = new Vector2(1, newValue ? -1 : 1);
Judgements.Scale = Scale;
Direction.BindValueChanged(d =>
{
barLineContainer.Padding = new MarginPadding
{
Top = d == ScrollingDirection.Up ? HIT_TARGET_POSITION : 0,
Bottom = d == ScrollingDirection.Down ? HIT_TARGET_POSITION : 0,
};
}, true);
}
public void AddColumn(Column c)
@@ -218,7 +211,7 @@ namespace osu.Game.Rulesets.Mania.UI
{
// Due to masking differences, it is not possible to get the width of the columns container automatically
// While masking on effectively only the Y-axis, so we need to set the width of the bar line container manually
content.Width = columnFlow.Width;
barLineContainer.Width = columnFlow.Width;
}
}
}
@@ -8,17 +8,19 @@ using osu.Framework.MathUtils;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.UI;
using osu.Game.Tests.Beatmaps;
using OpenTK;
namespace osu.Game.Rulesets.Osu.Tests
{
internal class OsuBeatmapConversionTest : BeatmapConversionTest<ConvertValue>
[TestFixture]
public class OsuBeatmapConversionTest : BeatmapConversionTest<ConvertValue>
{
protected override string ResourceAssembly => "osu.Game.Rulesets.Osu";
[TestCase("basic")]
[TestCase("colinear-perfect-curve")]
[TestCase("slider-ticks")]
public new void Test(string name)
{
base.Test(name);
@@ -26,43 +28,45 @@ namespace osu.Game.Rulesets.Osu.Tests
protected override IEnumerable<ConvertValue> CreateConvertValue(HitObject hitObject)
{
var startPosition = (hitObject as IHasPosition)?.Position ?? new Vector2(256, 192);
var endPosition = (hitObject as Slider)?.EndPosition ?? startPosition;
yield return new ConvertValue
switch (hitObject)
{
StartTime = hitObject.StartTime,
EndTime = (hitObject as IHasEndTime)?.EndTime ?? hitObject.StartTime,
StartX = startPosition.X,
StartY = startPosition.Y,
EndX = endPosition.X,
EndY = endPosition.Y
case Slider slider:
foreach (var nested in slider.NestedHitObjects)
yield return createConvertValue(nested);
break;
default:
yield return createConvertValue(hitObject);
break;
}
ConvertValue createConvertValue(HitObject obj) => new ConvertValue
{
StartTime = obj.StartTime,
EndTime = (obj as IHasEndTime)?.EndTime ?? obj.StartTime,
X = (obj as IHasPosition)?.X ?? OsuPlayfield.BASE_SIZE.X / 2,
Y = (obj as IHasPosition)?.Y ?? OsuPlayfield.BASE_SIZE.Y / 2,
};
}
protected override Ruleset CreateRuleset() => new OsuRuleset();
}
internal struct ConvertValue : IEquatable<ConvertValue>
public struct ConvertValue : IEquatable<ConvertValue>
{
/// <summary>
/// A sane value to account for osu!stable using ints everwhere.
/// A sane value to account for osu!stable using <see cref="int"/>s everywhere.
/// </summary>
private const double conversion_lenience = 2;
public double StartTime;
public double EndTime;
public float StartX;
public float StartY;
public float EndX;
public float EndY;
public float X;
public float Y;
public bool Equals(ConvertValue other)
=> Precision.AlmostEquals(StartTime, other.StartTime)
=> Precision.AlmostEquals(StartTime, other.StartTime, conversion_lenience)
&& Precision.AlmostEquals(EndTime, other.EndTime, conversion_lenience)
&& Precision.AlmostEquals(StartX, other.StartX)
&& Precision.AlmostEquals(StartY, other.StartY, conversion_lenience)
&& Precision.AlmostEquals(EndX, other.EndX, conversion_lenience)
&& Precision.AlmostEquals(EndY, other.EndY, conversion_lenience);
&& Precision.AlmostEquals(X, other.X, conversion_lenience)
&& Precision.AlmostEquals(Y, other.Y, conversion_lenience);
}
}
@@ -27,6 +27,7 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
var endTimeData = original as IHasEndTime;
var positionData = original as IHasPosition;
var comboData = original as IHasCombo;
var legacyOffset = original as IHasLegacyLastTickOffset;
if (curveData != null)
{
@@ -40,7 +41,8 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
RepeatSamples = curveData.RepeatSamples,
RepeatCount = curveData.RepeatCount,
Position = positionData?.Position ?? Vector2.Zero,
NewCombo = comboData?.NewCombo ?? false
NewCombo = comboData?.NewCombo ?? false,
LegacyLastTickOffset = legacyOffset?.LegacyLastTickOffset
};
}
else if (endTimeData != null)
@@ -15,10 +15,10 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
{
}
public override void PostProcess()
public override void PreProcess()
{
base.PreProcess();
applyStacking((Beatmap<OsuHitObject>)Beatmap);
base.PostProcess();
}
private void applyStacking(Beatmap<OsuHitObject> beatmap)
@@ -44,7 +44,7 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
continue;
double endTime = (stackBaseObject as IHasEndTime)?.EndTime ?? stackBaseObject.StartTime;
double stackThreshold = objectN.TimePreempt * beatmap.BeatmapInfo?.StackLeniency ?? 0.7f;
double stackThreshold = objectN.TimePreempt * beatmap.BeatmapInfo.StackLeniency;
if (objectN.StartTime - endTime > stackThreshold)
//We are no longer within stacking range of the next object.
@@ -87,7 +87,7 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
OsuHitObject objectI = beatmap.HitObjects[i];
if (objectI.StackHeight != 0 || objectI is Spinner) continue;
double stackThreshold = objectI.TimePreempt * beatmap.BeatmapInfo?.StackLeniency ?? 0.7f;
double stackThreshold = objectI.TimePreempt * beatmap.BeatmapInfo.StackLeniency;
/* If this object is a hitcircle, then we enter this "special" case.
* It either ends with a stack of hitcircles only, or a stack of hitcircles that are underneath a slider.
@@ -10,6 +10,9 @@ namespace osu.Game.Rulesets.Osu.Difficulty
{
public double AimStrain;
public double SpeedStrain;
public double ApproachRate;
public double OverallDifficulty;
public int MaxCombo;
public OsuDifficultyAttributes(Mod[] mods, double starRating)
: base(mods, starRating)
@@ -25,6 +25,9 @@ namespace osu.Game.Rulesets.Osu.Difficulty
protected override DifficultyAttributes Calculate(IBeatmap beatmap, Mod[] mods, double timeRate)
{
if (!beatmap.HitObjects.Any())
return new OsuDifficultyAttributes(mods, 0);
OsuDifficultyBeatmap difficultyBeatmap = new OsuDifficultyBeatmap(beatmap.HitObjects.Cast<OsuHitObject>().ToList(), timeRate);
Skill[] skills =
{
@@ -58,10 +61,21 @@ namespace osu.Game.Rulesets.Osu.Difficulty
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;
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)
{
AimStrain = aimRating,
SpeedStrain = speedRating
SpeedStrain = speedRating,
ApproachRate = preempt > 1200 ? (1800 - preempt) / 120 : (1200 - preempt) / 150 + 5,
OverallDifficulty = (80 - hitWindowGreat) / 6,
MaxCombo = maxCombo
};
}
@@ -22,16 +22,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty
private Mod[] mods;
/// <summary>
/// Approach rate adjusted by mods.
/// </summary>
private double realApproachRate;
/// <summary>
/// Overall difficulty adjusted by mods.
/// </summary>
private double realOverallDifficulty;
private double accuracy;
private int scoreMaxCombo;
private int countGreat;
@@ -63,13 +53,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty
if (mods.Any(m => !m.Ranked))
return 0;
// Todo: These int casts are temporary to achieve 1:1 results with osu!stable, and should be remoevd 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;
realApproachRate = preEmpt > 1200 ? (1800 - preEmpt) / 120 : (1200 - preEmpt) / 150 + 5;
realOverallDifficulty = (80 - hitWindowGreat) / 6;
// Custom multipliers for NoFail and SpunOut.
double multiplier = 1.12f; // This is being adjusted to keep the final pp value scaled around what it used to be when changing things
@@ -94,8 +77,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty
categoryRatings.Add("Aim", aimValue);
categoryRatings.Add("Speed", speedValue);
categoryRatings.Add("Accuracy", accuracyValue);
categoryRatings.Add("OD", realOverallDifficulty);
categoryRatings.Add("AR", realApproachRate);
categoryRatings.Add("OD", Attributes.OverallDifficulty);
categoryRatings.Add("AR", Attributes.ApproachRate);
categoryRatings.Add("Max Combo", beatmapMaxCombo);
}
@@ -120,22 +103,22 @@ namespace osu.Game.Rulesets.Osu.Difficulty
aimValue *= Math.Min(Math.Pow(scoreMaxCombo, 0.8f) / Math.Pow(beatmapMaxCombo, 0.8f), 1.0f);
double approachRateFactor = 1.0f;
if (realApproachRate > 10.33f)
approachRateFactor += 0.45f * (realApproachRate - 10.33f);
else if (realApproachRate < 8.0f)
if (Attributes.ApproachRate > 10.33f)
approachRateFactor += 0.45f * (Attributes.ApproachRate - 10.33f);
else if (Attributes.ApproachRate < 8.0f)
{
// HD is worth more with lower ar!
if (mods.Any(h => h is OsuModHidden))
approachRateFactor += 0.02f * (8.0f - realApproachRate);
approachRateFactor += 0.02f * (8.0f - Attributes.ApproachRate);
else
approachRateFactor += 0.01f * (8.0f - realApproachRate);
approachRateFactor += 0.01f * (8.0f - Attributes.ApproachRate);
}
aimValue *= approachRateFactor;
// We want to give more reward for lower AR when it comes to aim and HD. This nerfs high AR and buffs lower AR.
if (mods.Any(h => h is OsuModHidden))
aimValue *= 1.02 + (11.0f - realApproachRate) / 50.0; // Gives a 1.04 bonus for AR10, a 1.06 bonus for AR9, a 1.02 bonus for AR11.
aimValue *= 1.02 + (11.0f - Attributes.ApproachRate) / 50.0; // Gives a 1.04 bonus for AR10, a 1.06 bonus for AR9, a 1.02 bonus for AR11.
if (mods.Any(h => h is OsuModFlashlight))
{
@@ -146,7 +129,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
// Scale the aim value with accuracy _slightly_
aimValue *= 0.5f + accuracy / 2.0f;
// It is important to also consider accuracy difficulty when doing that
aimValue *= 0.98f + Math.Pow(realOverallDifficulty, 2) / 2500;
aimValue *= 0.98f + Math.Pow(Attributes.OverallDifficulty, 2) / 2500;
return aimValue;
}
@@ -172,7 +155,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
// Scale the speed value with accuracy _slightly_
speedValue *= 0.5f + accuracy / 2.0f;
// It is important to also consider accuracy difficulty when doing that
speedValue *= 0.98f + Math.Pow(realOverallDifficulty, 2) / 2500;
speedValue *= 0.98f + Math.Pow(Attributes.OverallDifficulty, 2) / 2500;
return speedValue;
}
@@ -194,7 +177,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
// Lots of arbitrary values from testing.
// Considering to use derivation from perfect accuracy in a probabilistic manner - assume normal distribution
double accuracyValue = Math.Pow(1.52163f, realOverallDifficulty) * Math.Pow(betterAccuracyPercentage, 24) * 2.83f;
double accuracyValue = Math.Pow(1.52163f, Attributes.OverallDifficulty) * Math.Pow(betterAccuracyPercentage, 24) * 2.83f;
// Bonus for many hitcircles - it's harder to keep good accuracy up for longer
accuracyValue *= Math.Min(1.15f, Math.Pow(amountHitObjectsWithAccuracy / 1000.0f, 0.3f));
@@ -1,7 +1,6 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using OpenTK;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Osu.Objects.Drawables;
using osu.Game.Rulesets.Scoring;
@@ -12,11 +11,6 @@ namespace osu.Game.Rulesets.Osu.Judgements
{
public override HitResult MaxResult => HitResult.Great;
/// <summary>
/// The positional hit offset.
/// </summary>
public Vector2 PositionOffset;
protected override int NumericResultFor(HitResult result)
{
switch (result)
@@ -8,6 +8,7 @@ namespace osu.Game.Rulesets.Osu.Judgements
public class OsuSliderTailJudgement : OsuJudgement
{
public override bool AffectsCombo => false;
protected override int NumericResultFor(HitResult result) => 0;
}
}
+2 -2
View File
@@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Osu.Mods
public override void ApplyToDrawableHitObjects(IEnumerable<DrawableHitObject> drawables)
{
void adjustFadeIn(OsuHitObject h) => h.TimeFadein = h.TimePreempt * fade_in_duration_multiplier;
void adjustFadeIn(OsuHitObject h) => h.TimeFadeIn = h.TimePreempt * fade_in_duration_multiplier;
foreach (var d in drawables.OfType<DrawableOsuHitObject>())
{
@@ -41,7 +41,7 @@ namespace osu.Game.Rulesets.Osu.Mods
var h = d.HitObject;
var fadeOutStartTime = h.StartTime - h.TimePreempt + h.TimeFadein;
var fadeOutStartTime = h.StartTime - h.TimePreempt + h.TimeFadeIn;
var fadeOutDuration = h.TimePreempt * fade_out_duration_multiplier;
// new duration from completed fade in to end (before fading out)
@@ -96,12 +96,12 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
using (fp.BeginAbsoluteSequence(fadeInTime))
{
fp.FadeIn(currHitObject.TimeFadein);
fp.ScaleTo(1, currHitObject.TimeFadein, Easing.Out);
fp.FadeIn(currHitObject.TimeFadeIn);
fp.ScaleTo(1, currHitObject.TimeFadeIn, Easing.Out);
fp.MoveTo(pointEndPosition, currHitObject.TimeFadein, Easing.Out);
fp.MoveTo(pointEndPosition, currHitObject.TimeFadeIn, Easing.Out);
fp.Delay(fadeOutTime - fadeInTime).FadeOut(currHitObject.TimeFadein);
fp.Delay(fadeOutTime - fadeInTime).FadeOut(currHitObject.TimeFadeIn);
}
fp.Expire(true);
@@ -93,7 +93,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
AddJudgement(new OsuJudgement
{
Result = result,
PositionOffset = Vector2.Zero //todo: set to correct value
});
}
@@ -101,7 +100,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
{
base.UpdatePreemptState();
ApproachCircle.FadeIn(Math.Min(HitObject.TimeFadein * 2, HitObject.TimePreempt));
ApproachCircle.FadeIn(Math.Min(HitObject.TimeFadeIn * 2, HitObject.TimePreempt));
ApproachCircle.ScaleTo(1.1f, HitObject.TimePreempt);
}
@@ -45,7 +45,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
AccentColour = skin.GetValue<SkinConfiguration, Color4>(s => s.ComboColours.Count > 0 ? s.ComboColours[combo.ComboIndex % s.ComboColours.Count] : (Color4?)null) ?? Color4.White;
}
protected virtual void UpdatePreemptState() => this.FadeIn(HitObject.TimeFadein);
protected virtual void UpdatePreemptState() => this.FadeIn(HitObject.TimeFadeIn);
protected virtual void UpdateCurrentState(ArmedState state)
{
@@ -93,6 +93,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
base.AccentColour = value;
Body.AccentColour = AccentColour;
Ball.AccentColour = AccentColour;
foreach (var drawableHitObject in NestedHitObjects)
drawableHitObject.AccentColour = AccentColour;
}
}
@@ -133,7 +136,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
{
if (!userTriggered && Time.Current >= slider.EndTime)
{
var judgementsCount = NestedHitObjects.Count;
var judgementsCount = NestedHitObjects.Count();
var judgementsHit = NestedHitObjects.Count(h => h.IsHit);
var hitFraction = (double)judgementsHit / judgementsCount;
@@ -167,7 +167,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
{
Disc.Tracking = OsuActionInputManager.PressedActions.Any(x => x == OsuAction.LeftButton || x == OsuAction.RightButton);
if (!spmCounter.IsPresent && Disc.Tracking)
spmCounter.FadeIn(HitObject.TimeFadein);
spmCounter.FadeIn(HitObject.TimeFadeIn);
base.Update();
}
@@ -212,7 +212,7 @@ 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 ? MathHelper.Clamp((Time.Current - (slider.StartTime - slider.TimePreempt)) / slider.TimeFadeIn, 0, 1) : 1;
if (span >= slider.SpanCount() - 1)
{
@@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Osu.Objects
public event Action<Vector2> PositionChanged;
public double TimePreempt = 600;
public double TimeFadein = 400;
public double TimeFadeIn = 400;
private Vector2 position;
@@ -54,9 +54,9 @@ namespace osu.Game.Rulesets.Osu.Objects
public virtual bool NewCombo { get; set; }
public int IndexInCurrentCombo { get; set; }
public virtual int IndexInCurrentCombo { get; set; }
public int ComboIndex { get; set; }
public virtual int ComboIndex { get; set; }
public bool LastInCombo { get; set; }
@@ -65,7 +65,7 @@ namespace osu.Game.Rulesets.Osu.Objects
base.ApplyDefaultsToSelf(controlPointInfo, difficulty);
TimePreempt = (float)BeatmapDifficulty.DifficultyRange(difficulty.ApproachRate, 1800, 1200, 450);
TimeFadein = (float)BeatmapDifficulty.DifficultyRange(difficulty.ApproachRate, 1200, 800, 300);
TimeFadeIn = (float)BeatmapDifficulty.DifficultyRange(difficulty.ApproachRate, 1200, 800, 300);
Scale = (1.0f - 0.7f * (difficulty.CircleSize - 5) / 5) / 2;
}
+30 -1
View File
@@ -1,6 +1,7 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using OpenTK;
using osu.Game.Rulesets.Objects.Types;
using System.Collections.Generic;
@@ -25,6 +26,28 @@ namespace osu.Game.Rulesets.Osu.Objects
public Vector2 StackedPositionAt(double t) => StackedPosition + this.CurvePositionAt(t);
public override Vector2 EndPosition => Position + this.CurvePositionAt(1);
public override int ComboIndex
{
get => base.ComboIndex;
set
{
base.ComboIndex = value;
foreach (var n in NestedHitObjects.OfType<IHasComboInformation>())
n.ComboIndex = value;
}
}
public override int IndexInCurrentCombo
{
get => base.IndexInCurrentCombo;
set
{
base.IndexInCurrentCombo = value;
foreach (var n in NestedHitObjects.OfType<IHasComboInformation>())
n.IndexInCurrentCombo = value;
}
}
public SliderCurve Curve { get; } = new SliderCurve();
public List<Vector2> ControlPoints
@@ -45,6 +68,8 @@ namespace osu.Game.Rulesets.Osu.Objects
set { Curve.Distance = value; }
}
public double? LegacyLastTickOffset { get; set; }
/// <summary>
/// The position of the cursor at the point of completion of this <see cref="Slider"/> if it was hit
/// with as few movements as possible. This is set and used by difficulty calculation.
@@ -91,6 +116,9 @@ namespace osu.Game.Rulesets.Osu.Objects
createSliderEnds();
createTicks();
createRepeatPoints();
if (LegacyLastTickOffset != null)
TailCircle.StartTime = Math.Max(StartTime + Duration / 2, TailCircle.StartTime - LegacyLastTickOffset.Value);
}
private void createSliderEnds()
@@ -141,7 +169,8 @@ namespace osu.Game.Rulesets.Osu.Objects
var distanceProgress = d / length;
var timeProgress = reversed ? 1 - distanceProgress : distanceProgress;
var firstSample = Samples.FirstOrDefault(s => s.Name == SampleInfo.HIT_NORMAL) ?? Samples.FirstOrDefault(); // TODO: remove this when guaranteed sort is present for samples (https://github.com/ppy/osu/issues/1933)
var firstSample = Samples.FirstOrDefault(s => s.Name == SampleInfo.HIT_NORMAL)
?? Samples.FirstOrDefault(); // TODO: remove this when guaranteed sort is present for samples (https://github.com/ppy/osu/issues/1933)
var sampleList = new List<SampleInfo>();
if (firstSample != null)
+1 -1
View File
@@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Osu.Objects
// This is so on repeats ticks don't appear too late to be visually processed by the player.
offset = 200;
else
offset = TimeFadein * 0.66f;
offset = TimeFadeIn * 0.66f;
TimePreempt = (StartTime - SpanStartTime) / 2 + offset;
}
@@ -6,6 +6,7 @@ using osu.Game.Beatmaps;
using osu.Game.Rulesets.Osu.Objects;
using System;
using System.Collections.Generic;
using osu.Game.Rulesets.Osu.UI;
using osu.Game.Rulesets.Replays;
using osu.Game.Users;
@@ -18,7 +19,7 @@ namespace osu.Game.Rulesets.Osu.Replays
/// <summary>
/// Constants (for spinners).
/// </summary>
protected static readonly Vector2 SPINNER_CENTRE = new Vector2(256, 192);
protected static readonly Vector2 SPINNER_CENTRE = OsuPlayfield.BASE_SIZE / 2;
protected const float SPIN_RADIUS = 50;
/// <summary>
@@ -1,124 +1,256 @@
{
"Mappings": [{
"StartTime": 500,
"Objects": [{
"StartTime": 500,
"EndTime": 2500,
"StartX": 96,
"StartY": 192,
"EndX": 96,
"EndY": 192
}]
},
{
"StartTime": 3000,
"Objects": [{
"StartTime": 3000,
"EndTime": 4000,
"StartX": 256,
"StartY": 192,
"EndX": 256,
"EndY": 192
}]
},
{
"StartTime": 4500,
"Objects": [{
"StartTime": 4500,
"EndTime": 5500,
"StartX": 256,
"StartY": 192,
"EndX": 256,
"EndY": 192
}]
},
{
"StartTime": 6000,
"Objects": [{
"StartTime": 6000,
"EndTime": 6500,
"StartX": 256,
"StartY": 192,
"EndX": 256,
"EndY": 192
}]
},
{
"StartTime": 7000,
"Objects": [{
"StartTime": 7000,
"EndTime": 8000,
"StartX": 256,
"StartY": 128,
"EndX": 256,
"EndY": 128
}]
},
{
"StartTime": 8500,
"Objects": [{
"StartTime": 8500,
"EndTime": 10999,
"StartX": 32,
"StartY": 192,
"EndX": 508.166229,
"EndY": 153.299271
}]
},
{
"StartTime": 11500,
"Objects": [{
"StartTime": 11500,
"EndTime": 12000,
"StartX": 256,
"StartY": 192,
"EndX": 256,
"EndY": 192
}]
},
{
"StartTime": 12500,
"Objects": [{
"StartTime": 12500,
"EndTime": 16500,
"StartX": 512,
"StartY": 320,
"EndX": 291.1977,
"EndY": 40.799427
}]
},
{
"StartTime": 17000,
"Objects": [{
"StartTime": 17000,
"EndTime": 18000,
"StartX": 256,
"StartY": 256,
"EndX": 256,
"EndY": 256
}]
},
{
"StartTime": 18500,
"Objects": [{
"StartTime": 18500,
"EndTime": 19450,
"StartX": 256,
"StartY": 192,
"EndX": 256,
"EndY": 192
}]
},
{
"StartTime": 19875,
"Objects": [{
"StartTime": 19875,
"EndTime": 23874,
"StartX": 216,
"StartY": 231,
"EndX": 408.720825,
"EndY": 339.810455
}]
}
]
"StartTime": 500.0,
"Objects": [{
"StartTime": 500.0,
"EndTime": 500.0,
"X": 96.0,
"Y": 192.0
}, {
"StartTime": 1000.0,
"EndTime": 1000.0,
"X": 256.0,
"Y": 192.0
}, {
"StartTime": 1500.0,
"EndTime": 1500.0,
"X": 416.0,
"Y": 192.0
}, {
"StartTime": 2000.0,
"EndTime": 2000.0,
"X": 256.0,
"Y": 192.0
}, {
"StartTime": 2464.0,
"EndTime": 2464.0,
"X": 96.0,
"Y": 192.0
}]
}, {
"StartTime": 3000.0,
"Objects": [{
"StartTime": 3000.0,
"EndTime": 4000.0,
"X": 256.0,
"Y": 192.0
}]
}, {
"StartTime": 4500.0,
"Objects": [{
"StartTime": 4500.0,
"EndTime": 5500.0,
"X": 256.0,
"Y": 192.0
}]
}, {
"StartTime": 6000.0,
"Objects": [{
"StartTime": 6000.0,
"EndTime": 6500.0,
"X": 256.0,
"Y": 192.0
}]
}, {
"StartTime": 7000.0,
"Objects": [{
"StartTime": 7000.0,
"EndTime": 7000.0,
"X": 256.0,
"Y": 128.0
}, {
"StartTime": 7250.0,
"EndTime": 7250.0,
"X": 336.0,
"Y": 128.0
}, {
"StartTime": 7500.0,
"EndTime": 7500.0,
"X": 256.0,
"Y": 128.0
}, {
"StartTime": 7750.0,
"EndTime": 7750.0,
"X": 336.0,
"Y": 128.0
}, {
"StartTime": 7964.0,
"EndTime": 7964.0,
"X": 256.0,
"Y": 128.0
}]
}, {
"StartTime": 8500.0,
"Objects": [{
"StartTime": 8500.0,
"EndTime": 8500.0,
"X": 32.0,
"Y": 192.0
}, {
"StartTime": 9000.0,
"EndTime": 9000.0,
"X": 101.81015,
"Y": 326.4915
}, {
"StartTime": 9500.0,
"EndTime": 9500.0,
"X": 237.2304,
"Y": 276.282928
}, {
"StartTime": 10000.0,
"EndTime": 10000.0,
"X": 270.339874,
"Y": 121.1423
}, {
"StartTime": 10500.0,
"EndTime": 10500.0,
"X": 401.0588,
"Y": 49.1515045
}, {
"StartTime": 10964.0,
"EndTime": 10964.0,
"X": 508.166229,
"Y": 153.299271
}]
}, {
"StartTime": 11500.0,
"Objects": [{
"StartTime": 11500.0,
"EndTime": 12000.0,
"X": 256.0,
"Y": 192.0
}]
}, {
"StartTime": 12500.0,
"Objects": [{
"StartTime": 12500.0,
"EndTime": 12500.0,
"X": 512.0,
"Y": 320.0
}, {
"StartTime": 13000.0,
"EndTime": 13000.0,
"X": 353.235535,
"Y": 300.154449
}, {
"StartTime": 13500.0,
"EndTime": 13500.0,
"X": 194.471069,
"Y": 280.3089
}, {
"StartTime": 14000.0,
"EndTime": 14000.0,
"X": 35.7066345,
"Y": 260.463318
}, {
"StartTime": 14500.0,
"EndTime": 14500.0,
"X": 118.370323,
"Y": 219.009277
}, {
"StartTime": 15000.0,
"EndTime": 15000.0,
"X": 271.087128,
"Y": 171.285278
}, {
"StartTime": 15500.0,
"EndTime": 15500.0,
"X": 423.803925,
"Y": 123.561279
}, {
"StartTime": 16000.0,
"EndTime": 16000.0,
"X": 446.420532,
"Y": 79.60513
}, {
"StartTime": 16464.0,
"EndTime": 16464.0,
"X": 291.1977,
"Y": 40.799427
}]
}, {
"StartTime": 17000.0,
"Objects": [{
"StartTime": 17000.0,
"EndTime": 17000.0,
"X": 256.0,
"Y": 256.0
}, {
"StartTime": 17250.0,
"EndTime": 17250.0,
"X": 176.0,
"Y": 256.0
}, {
"StartTime": 17500.0,
"EndTime": 17500.0,
"X": 256.0,
"Y": 256.0
}, {
"StartTime": 17750.0,
"EndTime": 17750.0,
"X": 176.0,
"Y": 256.0
}, {
"StartTime": 17964.0,
"EndTime": 17964.0,
"X": 256.0,
"Y": 256.0
}]
}, {
"StartTime": 18500.0,
"Objects": [{
"StartTime": 18500.0,
"EndTime": 19450.0,
"X": 256.0,
"Y": 192.0
}]
}, {
"StartTime": 19875.0,
"Objects": [{
"StartTime": 19875.0,
"EndTime": 19875.0,
"X": 216.0,
"Y": 231.0
}, {
"StartTime": 20375.0,
"EndTime": 20375.0,
"X": 317.446747,
"Y": 171.345245
}, {
"StartTime": 20875.0,
"EndTime": 20875.0,
"X": 270.3294,
"Y": 310.4395
}, {
"StartTime": 21375.0,
"EndTime": 21375.0,
"X": 119.121056,
"Y": 322.8657
}, {
"StartTime": 21875.0,
"EndTime": 21875.0,
"X": 124.28746,
"Y": 165.224731
}, {
"StartTime": 22375.0,
"EndTime": 22375.0,
"X": 240.4715,
"Y": 62.65587
}, {
"StartTime": 22875.0,
"EndTime": 22875.0,
"X": 398.054047,
"Y": 39.064167
}, {
"StartTime": 23375.0,
"EndTime": 23375.0,
"X": 439.749878,
"Y": 183.668091
}, {
"StartTime": 23839.0,
"EndTime": 23839.0,
"X": 408.720825,
"Y": 339.810455
}]
}]
}
@@ -1,13 +1,16 @@
{
"Mappings": [{
"StartTime": 118858,
"Objects": [{
"StartTime": 118858,
"EndTime": 119088,
"StartX": 219,
"StartY": 215,
"EndX": 239.6507,
"EndY": 29.1437378
"Mappings": [{
"StartTime": 118858.0,
"Objects": [{
"StartTime": 118858.0,
"EndTime": 118858.0,
"X": 219.0,
"Y": 215.0
}, {
"StartTime": 119052.0,
"EndTime": 119052.0,
"X": 239.6507,
"Y": 29.1437378
}]
}]
}]
}
@@ -0,0 +1,331 @@
{
"Mappings": [{
"StartTime": 500.0,
"Objects": [{
"StartTime": 500.0,
"EndTime": 500.0,
"X": 96.0,
"Y": 192.0
}, {
"StartTime": 624.0,
"EndTime": 624.0,
"X": 105.921242,
"Y": 192.0
}, {
"StartTime": 749.0,
"EndTime": 749.0,
"X": 115.922493,
"Y": 192.0
}, {
"StartTime": 874.0,
"EndTime": 874.0,
"X": 125.923737,
"Y": 192.0
}, {
"StartTime": 999.0,
"EndTime": 999.0,
"X": 135.924988,
"Y": 192.0
}, {
"StartTime": 1124.0,
"EndTime": 1124.0,
"X": 145.926239,
"Y": 192.0
}, {
"StartTime": 1249.0,
"EndTime": 1249.0,
"X": 155.92749,
"Y": 192.0
}, {
"StartTime": 1374.0,
"EndTime": 1374.0,
"X": 165.928741,
"Y": 192.0
}, {
"StartTime": 1499.0,
"EndTime": 1499.0,
"X": 175.93,
"Y": 192.0
}, {
"StartTime": 1624.0,
"EndTime": 1624.0,
"X": 185.931244,
"Y": 192.0
}, {
"StartTime": 1749.0,
"EndTime": 1749.0,
"X": 195.9325,
"Y": 192.0
}, {
"StartTime": 1874.0,
"EndTime": 1874.0,
"X": 205.933746,
"Y": 192.0
}, {
"StartTime": 1999.0,
"EndTime": 1999.0,
"X": 215.935,
"Y": 192.0
}, {
"StartTime": 2124.0,
"EndTime": 2124.0,
"X": 225.936234,
"Y": 192.0
}, {
"StartTime": 2249.0,
"EndTime": 2249.0,
"X": 235.9375,
"Y": 192.0
}, {
"StartTime": 2374.0,
"EndTime": 2374.0,
"X": 245.938751,
"Y": 192.0
}, {
"StartTime": 2499.0,
"EndTime": 2499.0,
"X": 255.94,
"Y": 192.0
}, {
"StartTime": 2624.0,
"EndTime": 2624.0,
"X": 265.941223,
"Y": 192.0
}, {
"StartTime": 2749.0,
"EndTime": 2749.0,
"X": 275.9425,
"Y": 192.0
}, {
"StartTime": 2874.0,
"EndTime": 2874.0,
"X": 285.943756,
"Y": 192.0
}, {
"StartTime": 2999.0,
"EndTime": 2999.0,
"X": 295.945,
"Y": 192.0
}, {
"StartTime": 3124.0,
"EndTime": 3124.0,
"X": 305.946259,
"Y": 192.0
}, {
"StartTime": 3249.0,
"EndTime": 3249.0,
"X": 315.9475,
"Y": 192.0
}, {
"StartTime": 3374.0,
"EndTime": 3374.0,
"X": 325.94873,
"Y": 192.0
}, {
"StartTime": 3499.0,
"EndTime": 3499.0,
"X": 335.949982,
"Y": 192.0
}, {
"StartTime": 3624.0,
"EndTime": 3624.0,
"X": 345.951233,
"Y": 192.0
}, {
"StartTime": 3749.0,
"EndTime": 3749.0,
"X": 355.952484,
"Y": 192.0
}, {
"StartTime": 3874.0,
"EndTime": 3874.0,
"X": 365.953766,
"Y": 192.0
}, {
"StartTime": 3999.0,
"EndTime": 3999.0,
"X": 375.955,
"Y": 192.0
}, {
"StartTime": 4124.0,
"EndTime": 4124.0,
"X": 385.956238,
"Y": 192.0
}, {
"StartTime": 4249.0,
"EndTime": 4249.0,
"X": 395.9575,
"Y": 192.0
}, {
"StartTime": 4374.0,
"EndTime": 4374.0,
"X": 405.95874,
"Y": 192.0
}, {
"StartTime": 4499.0,
"EndTime": 4499.0,
"X": 415.960022,
"Y": 192.0
}, {
"StartTime": 4624.0,
"EndTime": 4624.0,
"X": 406.038757,
"Y": 192.0
}, {
"StartTime": 4749.0,
"EndTime": 4749.0,
"X": 396.0375,
"Y": 192.0
}, {
"StartTime": 4874.0,
"EndTime": 4874.0,
"X": 386.036255,
"Y": 192.0
}, {
"StartTime": 4999.0,
"EndTime": 4999.0,
"X": 376.035034,
"Y": 192.0
}, {
"StartTime": 5124.0,
"EndTime": 5124.0,
"X": 366.033752,
"Y": 192.0
}, {
"StartTime": 5249.0,
"EndTime": 5249.0,
"X": 356.0325,
"Y": 192.0
}, {
"StartTime": 5374.0,
"EndTime": 5374.0,
"X": 346.03125,
"Y": 192.0
}, {
"StartTime": 5499.0,
"EndTime": 5499.0,
"X": 336.030029,
"Y": 192.0
}, {
"StartTime": 5624.0,
"EndTime": 5624.0,
"X": 326.028748,
"Y": 192.0
}, {
"StartTime": 5749.0,
"EndTime": 5749.0,
"X": 316.0275,
"Y": 192.0
}, {
"StartTime": 5874.0,
"EndTime": 5874.0,
"X": 306.026245,
"Y": 192.0
}, {
"StartTime": 5999.0,
"EndTime": 5999.0,
"X": 296.025,
"Y": 192.0
}, {
"StartTime": 6124.0,
"EndTime": 6124.0,
"X": 286.023773,
"Y": 192.0
}, {
"StartTime": 6249.0,
"EndTime": 6249.0,
"X": 276.022522,
"Y": 192.0
}, {
"StartTime": 6374.0,
"EndTime": 6374.0,
"X": 266.02124,
"Y": 192.0
}, {
"StartTime": 6499.0,
"EndTime": 6499.0,
"X": 256.02,
"Y": 192.0
}, {
"StartTime": 6624.0,
"EndTime": 6624.0,
"X": 246.018768,
"Y": 192.0
}, {
"StartTime": 6749.0,
"EndTime": 6749.0,
"X": 236.017517,
"Y": 192.0
}, {
"StartTime": 6874.0,
"EndTime": 6874.0,
"X": 226.016251,
"Y": 192.0
}, {
"StartTime": 6999.0,
"EndTime": 6999.0,
"X": 216.014984,
"Y": 192.0
}, {
"StartTime": 7124.0,
"EndTime": 7124.0,
"X": 206.013733,
"Y": 192.0
}, {
"StartTime": 7249.0,
"EndTime": 7249.0,
"X": 196.012512,
"Y": 192.0
}, {
"StartTime": 7374.0,
"EndTime": 7374.0,
"X": 186.011261,
"Y": 192.0
}, {
"StartTime": 7499.0,
"EndTime": 7499.0,
"X": 176.01,
"Y": 192.0
}, {
"StartTime": 7624.0,
"EndTime": 7624.0,
"X": 166.008728,
"Y": 192.0
}, {
"StartTime": 7749.0,
"EndTime": 7749.0,
"X": 156.0075,
"Y": 192.0
}, {
"StartTime": 7874.0,
"EndTime": 7874.0,
"X": 146.006256,
"Y": 192.0
}, {
"StartTime": 7999.0,
"EndTime": 7999.0,
"X": 136.005,
"Y": 192.0
}, {
"StartTime": 8124.0,
"EndTime": 8124.0,
"X": 126.003738,
"Y": 192.0
}, {
"StartTime": 8249.0,
"EndTime": 8249.0,
"X": 116.002518,
"Y": 192.0
}, {
"StartTime": 8374.0,
"EndTime": 8374.0,
"X": 106.001259,
"Y": 192.0
}, {
"StartTime": 8463.0,
"EndTime": 8463.0,
"X": 96.0,
"Y": 192.0
}]
}]
}
@@ -0,0 +1,20 @@
osu file format v14
[General]
StackLeniency: 0.7
[Difficulty]
HPDrainRate:6
CircleSize:4
OverallDifficulty:7
ApproachRate:8.3
SliderMultiplier:0.400000005960464
SliderTickRate:4
[TimingPoints]
500,500,4,2,1,50,1,0
13426,-100,4,3,1,45,0,0
14884,-100,4,2,1,50,0,0
[HitObjects]
96,192,500,6,0,L|416:192,2,320.000004768372
+1 -2
View File
@@ -11,7 +11,6 @@ using osu.Game.Rulesets.Osu.Objects.Drawables.Connections;
using osu.Game.Rulesets.UI;
using System.Linq;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Osu.Judgements;
namespace osu.Game.Rulesets.Osu.UI
{
@@ -75,7 +74,7 @@ namespace osu.Game.Rulesets.Osu.UI
DrawableOsuJudgement explosion = new DrawableOsuJudgement(judgement, judgedObject)
{
Origin = Anchor.Centre,
Position = ((OsuHitObject)judgedObject.HitObject).StackedEndPosition + ((OsuJudgement)judgement).PositionOffset
Position = ((OsuHitObject)judgedObject.HitObject).StackedEndPosition
};
judgementLayer.Add(explosion);
@@ -12,7 +12,8 @@ using osu.Game.Tests.Beatmaps;
namespace osu.Game.Rulesets.Taiko.Tests
{
internal class TaikoBeatmapConversionTest : BeatmapConversionTest<ConvertValue>
[TestFixture]
public class TaikoBeatmapConversionTest : BeatmapConversionTest<ConvertValue>
{
protected override string ResourceAssembly => "osu.Game.Rulesets.Taiko";
@@ -41,7 +42,7 @@ namespace osu.Game.Rulesets.Taiko.Tests
protected override Ruleset CreateRuleset() => new TaikoRuleset();
}
internal struct ConvertValue : IEquatable<ConvertValue>
public struct ConvertValue : IEquatable<ConvertValue>
{
/// <summary>
/// A sane value to account for osu!stable using ints everwhere.

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