mirror of
https://github.com/osukey/osukey.git
synced 2025-05-20 21:17:32 +09:00
Merge remote-tracking branch 'upstream/master' into leaderboard-scopes
This commit is contained in:
commit
ccbbd09d84
77
.vscode/tasks.json
vendored
77
.vscode/tasks.json
vendored
@ -2,63 +2,70 @@
|
|||||||
// See https://go.microsoft.com/fwlink/?LinkId=733558
|
// See https://go.microsoft.com/fwlink/?LinkId=733558
|
||||||
// for the documentation about the tasks.json format
|
// for the documentation about the tasks.json format
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"command": "msbuild",
|
|
||||||
"type": "shell",
|
|
||||||
"suppressTaskName": true,
|
|
||||||
"args": [
|
|
||||||
"/property:GenerateFullPaths=true",
|
|
||||||
"/property:DebugType=portable",
|
|
||||||
"/verbosity:minimal",
|
|
||||||
"/m" //parallel compiling support.
|
|
||||||
],
|
|
||||||
"tasks": [{
|
"tasks": [{
|
||||||
"taskName": "Build (Debug)",
|
"label": "Build (Debug)",
|
||||||
|
"type": "shell",
|
||||||
|
"command": "msbuild",
|
||||||
|
"args": [
|
||||||
|
"/p:GenerateFullPaths=true",
|
||||||
|
"/p:DebugType=portable",
|
||||||
|
"/m",
|
||||||
|
"/v:m"
|
||||||
|
],
|
||||||
"group": {
|
"group": {
|
||||||
"kind": "build",
|
"kind": "build",
|
||||||
"isDefault": true
|
"isDefault": true
|
||||||
},
|
},
|
||||||
"problemMatcher": [
|
"problemMatcher": "$msCompile"
|
||||||
"$msCompile"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"taskName": "Build (Release)",
|
"label": "Build (Release)",
|
||||||
|
"type": "shell",
|
||||||
|
"command": "msbuild",
|
||||||
|
"args": [
|
||||||
|
"/p:Configuration=Release",
|
||||||
|
"/p:DebugType=portable",
|
||||||
|
"/p:GenerateFullPaths=true",
|
||||||
|
"/m",
|
||||||
|
"/v:m"
|
||||||
|
],
|
||||||
"group": "build",
|
"group": "build",
|
||||||
"args": [
|
"problemMatcher": "$msCompile"
|
||||||
"/property:Configuration=Release"
|
|
||||||
],
|
|
||||||
"problemMatcher": [
|
|
||||||
"$msCompile"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"taskName": "Clean (Debug)",
|
"label": "Clean (Debug)",
|
||||||
|
"type": "shell",
|
||||||
|
"command": "msbuild",
|
||||||
"args": [
|
"args": [
|
||||||
"/target:Clean"
|
"/p:DebugType=portable",
|
||||||
|
"/p:GenerateFullPaths=true",
|
||||||
|
"/m",
|
||||||
|
"/t:Clean",
|
||||||
|
"/v:m"
|
||||||
],
|
],
|
||||||
"problemMatcher": [
|
"problemMatcher": "$msCompile"
|
||||||
"$msCompile"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"taskName": "Clean (Release)",
|
"label": "Clean (Release)",
|
||||||
|
"type": "shell",
|
||||||
|
"command": "msbuild",
|
||||||
"args": [
|
"args": [
|
||||||
"/target:Clean",
|
"/p:Configuration=Release",
|
||||||
"/property:Configuration=Release"
|
"/p:GenerateFullPaths=true",
|
||||||
|
"/p:DebugType=portable",
|
||||||
|
"/m",
|
||||||
|
"/t:Clean",
|
||||||
|
"/v:m"
|
||||||
],
|
],
|
||||||
"problemMatcher": [
|
"problemMatcher": "$msCompile"
|
||||||
"$msCompile"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"taskName": "Clean All",
|
"label": "Clean All",
|
||||||
"dependsOn": [
|
"dependsOn": [
|
||||||
"Clean (Debug)",
|
"Clean (Debug)",
|
||||||
"Clean (Release)"
|
"Clean (Release)"
|
||||||
],
|
],
|
||||||
"problemMatcher": [
|
"problemMatcher": "$msCompile"
|
||||||
"$msCompile"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
@ -9,17 +9,17 @@ cache:
|
|||||||
- inspectcode -> appveyor.yml
|
- inspectcode -> appveyor.yml
|
||||||
- packages -> **\packages.config
|
- packages -> **\packages.config
|
||||||
install:
|
install:
|
||||||
- cmd: git submodule update --init --recursive
|
- cmd: git submodule update --init --recursive --depth=5
|
||||||
- cmd: choco install resharper-clt -y
|
- cmd: choco install resharper-clt -y
|
||||||
- cmd: choco install nvika -y
|
- cmd: choco install nvika -y
|
||||||
- cmd: appveyor DownloadFile https://github.com/peppy/CodeFileSanity/releases/download/v0.2.3/CodeFileSanity.exe
|
- cmd: appveyor DownloadFile https://github.com/peppy/CodeFileSanity/releases/download/v0.2.3/CodeFileSanity.exe
|
||||||
before_build:
|
before_build:
|
||||||
- cmd: CodeFileSanity.exe
|
- cmd: CodeFileSanity.exe
|
||||||
- cmd: nuget restore
|
- cmd: nuget restore -verbosity quiet
|
||||||
build:
|
build:
|
||||||
project: osu.sln
|
project: osu.sln
|
||||||
parallel: true
|
parallel: true
|
||||||
verbosity: minimal
|
verbosity: minimal
|
||||||
after_build:
|
after_build:
|
||||||
- cmd: inspectcode /o="inspectcodereport.xml" /caches-home="inspectcode" osu.sln
|
- cmd: inspectcode --o="inspectcodereport.xml" --projects:osu.Game* --caches-home="inspectcode" osu.sln > NUL
|
||||||
- cmd: NVika parsereport "inspectcodereport.xml" --treatwarningsaserrors
|
- cmd: NVika parsereport "inspectcodereport.xml" --treatwarningsaserrors
|
@ -1 +1 @@
|
|||||||
Subproject commit fe49ccb3c8f8661d653752d225ae1dc183944bb4
|
Subproject commit 8480ab5009b2b4a7810a817a12433959424d5339
|
@ -1 +1 @@
|
|||||||
Subproject commit 1750ab8f6761ab35592fd46da71fbe0c141bfd93
|
Subproject commit 4287ee8043fb1419017359bc3a5db5dc06bc643f
|
@ -13,7 +13,7 @@ Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/maste
|
|||||||
<add key="ProjectName" value="osu.Desktop" />
|
<add key="ProjectName" value="osu.Desktop" />
|
||||||
<add key="NuSpecName" value="osu.Desktop\osu.nuspec" />
|
<add key="NuSpecName" value="osu.Desktop\osu.nuspec" />
|
||||||
<add key="SolutionName" value="osu" />
|
<add key="SolutionName" value="osu" />
|
||||||
<add key="TargetName" value="Client\osu.Desktop" />
|
<add key="TargetName" value="osu.Desktop" />
|
||||||
<add key="PackageName" value="osulazer" />
|
<add key="PackageName" value="osulazer" />
|
||||||
<add key="IconName" value="lazer.ico" />
|
<add key="IconName" value="lazer.ico" />
|
||||||
<add key="CodeSigningCertificate" value="" />
|
<add key="CodeSigningCertificate" value="" />
|
||||||
|
@ -145,6 +145,8 @@ namespace osu.Desktop.Deploy
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private static void checkReleaseFiles()
|
private static void checkReleaseFiles()
|
||||||
{
|
{
|
||||||
|
if (!canGitHub) return;
|
||||||
|
|
||||||
var releaseLines = getReleaseLines();
|
var releaseLines = getReleaseLines();
|
||||||
|
|
||||||
//ensure we have all files necessary
|
//ensure we have all files necessary
|
||||||
@ -157,6 +159,8 @@ namespace osu.Desktop.Deploy
|
|||||||
|
|
||||||
private static void pruneReleases()
|
private static void pruneReleases()
|
||||||
{
|
{
|
||||||
|
if (!canGitHub) return;
|
||||||
|
|
||||||
write("Pruning RELEASES...");
|
write("Pruning RELEASES...");
|
||||||
|
|
||||||
var releaseLines = getReleaseLines().ToList();
|
var releaseLines = getReleaseLines().ToList();
|
||||||
@ -190,7 +194,7 @@ namespace osu.Desktop.Deploy
|
|||||||
|
|
||||||
private static void uploadBuild(string version)
|
private static void uploadBuild(string version)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(GitHubAccessToken) || string.IsNullOrEmpty(codeSigningCertPath))
|
if (!canGitHub || string.IsNullOrEmpty(CodeSigningCertificate))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
write("Publishing to GitHub...");
|
write("Publishing to GitHub...");
|
||||||
@ -228,8 +232,12 @@ namespace osu.Desktop.Deploy
|
|||||||
|
|
||||||
private static void openGitHubReleasePage() => Process.Start(GitHubReleasePage);
|
private static void openGitHubReleasePage() => Process.Start(GitHubReleasePage);
|
||||||
|
|
||||||
|
private static bool canGitHub => !string.IsNullOrEmpty(GitHubAccessToken);
|
||||||
|
|
||||||
private static void checkGitHubReleases()
|
private static void checkGitHubReleases()
|
||||||
{
|
{
|
||||||
|
if (!canGitHub) return;
|
||||||
|
|
||||||
write("Checking GitHub releases...");
|
write("Checking GitHub releases...");
|
||||||
var req = new JsonWebRequest<List<GitHubRelease>>($"{GitHubApiEndpoint}");
|
var req = new JsonWebRequest<List<GitHubRelease>>($"{GitHubApiEndpoint}");
|
||||||
req.AuthenticatedBlockingPerform();
|
req.AuthenticatedBlockingPerform();
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
<Import Project="..\osu.Game.props" />
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||||
@ -22,7 +22,6 @@
|
|||||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||||
<ErrorReport>prompt</ErrorReport>
|
<ErrorReport>prompt</ErrorReport>
|
||||||
<WarningLevel>4</WarningLevel>
|
<WarningLevel>4</WarningLevel>
|
||||||
<LangVersion>6</LangVersion>
|
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||||
@ -102,9 +101,6 @@
|
|||||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<None Include="..\osu.licenseheader">
|
|
||||||
<Link>osu.licenseheader</Link>
|
|
||||||
</None>
|
|
||||||
<None Include="App.config">
|
<None Include="App.config">
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
</None>
|
</None>
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="12.0">
|
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="14.0">
|
||||||
|
<Import Project="..\osu.Game.props" />
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<ProjectGuid>{419659FD-72EA-4678-9EB8-B22A746CED70}</ProjectGuid>
|
<ProjectGuid>{419659FD-72EA-4678-9EB8-B22A746CED70}</ProjectGuid>
|
||||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||||
@ -62,7 +63,6 @@
|
|||||||
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
|
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
|
||||||
<Commandlineparameters>
|
<Commandlineparameters>
|
||||||
</Commandlineparameters>
|
</Commandlineparameters>
|
||||||
<LangVersion>6</LangVersion>
|
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||||
<DebugType>none</DebugType>
|
<DebugType>none</DebugType>
|
||||||
@ -98,7 +98,6 @@
|
|||||||
<DebugType>full</DebugType>
|
<DebugType>full</DebugType>
|
||||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||||
<UseVSHostingProcess>false</UseVSHostingProcess>
|
<UseVSHostingProcess>false</UseVSHostingProcess>
|
||||||
<LangVersion>6</LangVersion>
|
|
||||||
<ErrorReport>prompt</ErrorReport>
|
<ErrorReport>prompt</ErrorReport>
|
||||||
<StartArguments>--tests</StartArguments>
|
<StartArguments>--tests</StartArguments>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
@ -174,9 +173,6 @@
|
|||||||
<Reference Include="System.Windows.Forms" />
|
<Reference Include="System.Windows.Forms" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<None Include="..\osu.licenseheader">
|
|
||||||
<Link>osu.licenseheader</Link>
|
|
||||||
</None>
|
|
||||||
<None Include="app.config" />
|
<None Include="app.config" />
|
||||||
<None Include="OpenTK.dll.config" />
|
<None Include="OpenTK.dll.config" />
|
||||||
<None Include="osu!.res" />
|
<None Include="osu!.res" />
|
||||||
|
@ -11,11 +11,11 @@ using osu.Game.Rulesets.Objects;
|
|||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Beatmaps
|
namespace osu.Game.Rulesets.Catch.Beatmaps
|
||||||
{
|
{
|
||||||
internal class CatchBeatmapConverter : BeatmapConverter<CatchBaseHit>
|
internal class CatchBeatmapConverter : BeatmapConverter<CatchHitObject>
|
||||||
{
|
{
|
||||||
protected override IEnumerable<Type> ValidConversionTypes { get; } = new[] { typeof(IHasXPosition) };
|
protected override IEnumerable<Type> ValidConversionTypes { get; } = new[] { typeof(IHasXPosition) };
|
||||||
|
|
||||||
protected override IEnumerable<CatchBaseHit> ConvertHitObject(HitObject obj, Beatmap beatmap)
|
protected override IEnumerable<CatchHitObject> ConvertHitObject(HitObject obj, Beatmap beatmap)
|
||||||
{
|
{
|
||||||
var curveData = obj as IHasCurve;
|
var curveData = obj as IHasCurve;
|
||||||
var positionData = obj as IHasXPosition;
|
var positionData = obj as IHasXPosition;
|
||||||
|
@ -1,14 +1,20 @@
|
|||||||
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
|
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
|
||||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Rulesets.Catch.Objects;
|
using osu.Game.Rulesets.Catch.Objects;
|
||||||
|
using osu.Game.Rulesets.Catch.UI;
|
||||||
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
|
using OpenTK;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Beatmaps
|
namespace osu.Game.Rulesets.Catch.Beatmaps
|
||||||
{
|
{
|
||||||
internal class CatchBeatmapProcessor : BeatmapProcessor<CatchBaseHit>
|
internal class CatchBeatmapProcessor : BeatmapProcessor<CatchHitObject>
|
||||||
{
|
{
|
||||||
public override void PostProcess(Beatmap<CatchBaseHit> beatmap)
|
public override void PostProcess(Beatmap<CatchHitObject> beatmap)
|
||||||
{
|
{
|
||||||
if (beatmap.ComboColors.Count == 0)
|
if (beatmap.ComboColors.Count == 0)
|
||||||
return;
|
return;
|
||||||
@ -16,7 +22,9 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
|
|||||||
int comboIndex = 0;
|
int comboIndex = 0;
|
||||||
int colourIndex = 0;
|
int colourIndex = 0;
|
||||||
|
|
||||||
CatchBaseHit lastObj = null;
|
CatchHitObject lastObj = null;
|
||||||
|
|
||||||
|
initialiseHyperDash(beatmap.HitObjects);
|
||||||
|
|
||||||
foreach (var obj in beatmap.HitObjects)
|
foreach (var obj in beatmap.HitObjects)
|
||||||
{
|
{
|
||||||
@ -34,5 +42,49 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
|
|||||||
lastObj = obj;
|
lastObj = obj;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void initialiseHyperDash(List<CatchHitObject> objects)
|
||||||
|
{
|
||||||
|
// todo: add difficulty adjust.
|
||||||
|
double halfCatcherWidth = CatcherArea.CATCHER_SIZE * (objects.FirstOrDefault()?.Scale ?? 1) / CatchPlayfield.BASE_WIDTH / 2;
|
||||||
|
|
||||||
|
int lastDirection = 0;
|
||||||
|
double lastExcess = halfCatcherWidth;
|
||||||
|
|
||||||
|
int objCount = objects.Count;
|
||||||
|
|
||||||
|
for (int i = 0; i < objCount - 1; i++)
|
||||||
|
{
|
||||||
|
CatchHitObject currentObject = objects[i];
|
||||||
|
|
||||||
|
// not needed?
|
||||||
|
// if (currentObject is TinyDroplet) continue;
|
||||||
|
|
||||||
|
CatchHitObject nextObject = objects[i + 1];
|
||||||
|
|
||||||
|
// while (nextObject is TinyDroplet)
|
||||||
|
// {
|
||||||
|
// if (++i == objCount - 1) break;
|
||||||
|
// nextObject = objects[i + 1];
|
||||||
|
// }
|
||||||
|
|
||||||
|
int thisDirection = nextObject.X > currentObject.X ? 1 : -1;
|
||||||
|
double timeToNext = nextObject.StartTime - ((currentObject as IHasEndTime)?.EndTime ?? currentObject.StartTime) - 4;
|
||||||
|
double distanceToNext = Math.Abs(nextObject.X - currentObject.X) - (lastDirection == thisDirection ? lastExcess : halfCatcherWidth);
|
||||||
|
|
||||||
|
if (timeToNext * CatcherArea.Catcher.BASE_SPEED < distanceToNext)
|
||||||
|
{
|
||||||
|
currentObject.HyperDashTarget = nextObject;
|
||||||
|
lastExcess = halfCatcherWidth;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
//currentObject.DistanceToHyperDash = timeToNext - distanceToNext;
|
||||||
|
lastExcess = MathHelper.Clamp(timeToNext - distanceToNext, 0, halfCatcherWidth);
|
||||||
|
}
|
||||||
|
|
||||||
|
lastDirection = thisDirection;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,14 +8,14 @@ using System.Collections.Generic;
|
|||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch
|
namespace osu.Game.Rulesets.Catch
|
||||||
{
|
{
|
||||||
public class CatchDifficultyCalculator : DifficultyCalculator<CatchBaseHit>
|
public class CatchDifficultyCalculator : DifficultyCalculator<CatchHitObject>
|
||||||
{
|
{
|
||||||
public CatchDifficultyCalculator(Beatmap beatmap) : base(beatmap)
|
public CatchDifficultyCalculator(Beatmap beatmap) : base(beatmap)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public override double Calculate(Dictionary<string, string> categoryDifficulty = null) => 0;
|
public override double Calculate(Dictionary<string, double> categoryDifficulty = null) => 0;
|
||||||
|
|
||||||
protected override BeatmapConverter<CatchBaseHit> CreateBeatmapConverter(Beatmap beatmap) => new CatchBeatmapConverter();
|
protected override BeatmapConverter<CatchHitObject> CreateBeatmapConverter(Beatmap beatmap) => new CatchBeatmapConverter();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -93,6 +93,8 @@ namespace osu.Game.Rulesets.Catch
|
|||||||
|
|
||||||
public override string Description => "osu!catch";
|
public override string Description => "osu!catch";
|
||||||
|
|
||||||
|
public override string ShortName => "fruits";
|
||||||
|
|
||||||
public override Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.fa_osu_fruits_o };
|
public override Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.fa_osu_fruits_o };
|
||||||
|
|
||||||
public override DifficultyCalculator CreateDifficultyCalculator(Beatmap beatmap, Mod[] mods = null) => new CatchDifficultyCalculator(beatmap);
|
public override DifficultyCalculator CreateDifficultyCalculator(Beatmap beatmap, Mod[] mods = null) => new CatchDifficultyCalculator(beatmap);
|
||||||
|
@ -1,24 +0,0 @@
|
|||||||
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
|
|
||||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
|
||||||
|
|
||||||
using osu.Game.Rulesets.Objects;
|
|
||||||
using osu.Game.Rulesets.Objects.Types;
|
|
||||||
using OpenTK.Graphics;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Objects
|
|
||||||
{
|
|
||||||
public abstract class CatchBaseHit : HitObject, IHasXPosition, IHasCombo
|
|
||||||
{
|
|
||||||
public float X { get; set; }
|
|
||||||
|
|
||||||
public Color4 ComboColour { get; set; } = Color4.Gray;
|
|
||||||
public int ComboIndex { get; set; }
|
|
||||||
|
|
||||||
public virtual bool NewCombo { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The next fruit starts a new combo. Used for explodey.
|
|
||||||
/// </summary>
|
|
||||||
public virtual bool LastInCombo { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
47
osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs
Normal file
47
osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
|
||||||
|
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||||
|
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
|
using OpenTK.Graphics;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Catch.Objects
|
||||||
|
{
|
||||||
|
public abstract class CatchHitObject : HitObject, IHasXPosition, IHasCombo
|
||||||
|
{
|
||||||
|
public const double OBJECT_RADIUS = 44;
|
||||||
|
|
||||||
|
public float X { get; set; }
|
||||||
|
|
||||||
|
public Color4 ComboColour { get; set; } = Color4.Gray;
|
||||||
|
public int ComboIndex { get; set; }
|
||||||
|
|
||||||
|
public virtual bool NewCombo { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The next fruit starts a new combo. Used for explodey.
|
||||||
|
/// </summary>
|
||||||
|
public virtual bool LastInCombo { get; set; }
|
||||||
|
|
||||||
|
public float Scale { get; set; } = 1;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether this fruit can initiate a hyperdash.
|
||||||
|
/// </summary>
|
||||||
|
public bool HyperDash => HyperDashTarget != null;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The target fruit if we are to initiate a hyperdash.
|
||||||
|
/// </summary>
|
||||||
|
public CatchHitObject HyperDashTarget;
|
||||||
|
|
||||||
|
public override void ApplyDefaults(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty)
|
||||||
|
{
|
||||||
|
base.ApplyDefaults(controlPointInfo, difficulty);
|
||||||
|
|
||||||
|
Scale = 1.0f - 0.7f * (difficulty.CircleSize - 5) / 5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -5,11 +5,12 @@ using System;
|
|||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Game.Rulesets.Judgements;
|
using osu.Game.Rulesets.Judgements;
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
|
using OpenTK;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Objects.Drawable
|
namespace osu.Game.Rulesets.Catch.Objects.Drawable
|
||||||
{
|
{
|
||||||
public abstract class DrawableCatchHitObject<TObject> : DrawableCatchHitObject
|
public abstract class DrawableCatchHitObject<TObject> : DrawableCatchHitObject
|
||||||
where TObject : CatchBaseHit
|
where TObject : CatchHitObject
|
||||||
{
|
{
|
||||||
public new TObject HitObject;
|
public new TObject HitObject;
|
||||||
|
|
||||||
@ -17,12 +18,14 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
|
|||||||
: base(hitObject)
|
: base(hitObject)
|
||||||
{
|
{
|
||||||
HitObject = hitObject;
|
HitObject = hitObject;
|
||||||
|
|
||||||
|
Scale = new Vector2(HitObject.Scale);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract class DrawableCatchHitObject : DrawableScrollingHitObject<CatchBaseHit>
|
public abstract class DrawableCatchHitObject : DrawableScrollingHitObject<CatchHitObject>
|
||||||
{
|
{
|
||||||
protected DrawableCatchHitObject(CatchBaseHit hitObject)
|
protected DrawableCatchHitObject(CatchHitObject hitObject)
|
||||||
: base(hitObject)
|
: base(hitObject)
|
||||||
{
|
{
|
||||||
RelativePositionAxes = Axes.Both;
|
RelativePositionAxes = Axes.Both;
|
||||||
@ -30,7 +33,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
|
|||||||
Y = (float)HitObject.StartTime;
|
Y = (float)HitObject.StartTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Func<CatchBaseHit, bool> CheckPosition;
|
public Func<CatchHitObject, bool> CheckPosition;
|
||||||
|
|
||||||
protected override void CheckForJudgements(bool userTriggered, double timeOffset)
|
protected override void CheckForJudgements(bool userTriggered, double timeOffset)
|
||||||
{
|
{
|
||||||
|
@ -7,6 +7,7 @@ using osu.Framework.Graphics.Containers;
|
|||||||
using osu.Framework.MathUtils;
|
using osu.Framework.MathUtils;
|
||||||
using osu.Game.Rulesets.Catch.Objects.Drawable.Pieces;
|
using osu.Game.Rulesets.Catch.Objects.Drawable.Pieces;
|
||||||
using OpenTK;
|
using OpenTK;
|
||||||
|
using OpenTK.Graphics;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Objects.Drawable
|
namespace osu.Game.Rulesets.Catch.Objects.Drawable
|
||||||
{
|
{
|
||||||
@ -70,6 +71,20 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (HitObject.HyperDash)
|
||||||
|
{
|
||||||
|
Add(new Pulp
|
||||||
|
{
|
||||||
|
RelativePositionAxes = Axes.Both,
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
AccentColour = Color4.Red,
|
||||||
|
Blending = BlendingMode.Additive,
|
||||||
|
Alpha = 0.5f,
|
||||||
|
Scale = new Vector2(2)
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -25,7 +25,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
|
|||||||
RelativeChildSize = new Vector2(1, (float)HitObject.Duration)
|
RelativeChildSize = new Vector2(1, (float)HitObject.Duration)
|
||||||
};
|
};
|
||||||
|
|
||||||
foreach (CatchBaseHit tick in s.Ticks)
|
foreach (CatchHitObject tick in s.Ticks)
|
||||||
{
|
{
|
||||||
TinyDroplet tiny = tick as TinyDroplet;
|
TinyDroplet tiny = tick as TinyDroplet;
|
||||||
if (tiny != null)
|
if (tiny != null)
|
||||||
@ -44,7 +44,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void AddNested(DrawableHitObject<CatchBaseHit> h)
|
protected override void AddNested(DrawableHitObject<CatchHitObject> h)
|
||||||
{
|
{
|
||||||
((DrawableCatchHitObject)h).CheckPosition = o => CheckPosition?.Invoke(o) ?? false;
|
((DrawableCatchHitObject)h).CheckPosition = o => CheckPosition?.Invoke(o) ?? false;
|
||||||
dropletContainer.Add(h);
|
dropletContainer.Add(h);
|
||||||
|
@ -13,7 +13,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable.Pieces
|
|||||||
{
|
{
|
||||||
public class Pulp : Circle, IHasAccentColour
|
public class Pulp : Circle, IHasAccentColour
|
||||||
{
|
{
|
||||||
public const float PULP_SIZE = 20;
|
public const float PULP_SIZE = (float)CatchHitObject.OBJECT_RADIUS / 2.2f;
|
||||||
|
|
||||||
public Pulp()
|
public Pulp()
|
||||||
{
|
{
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Objects
|
namespace osu.Game.Rulesets.Catch.Objects
|
||||||
{
|
{
|
||||||
public class Droplet : CatchBaseHit
|
public class Droplet : CatchHitObject
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Objects
|
namespace osu.Game.Rulesets.Catch.Objects
|
||||||
{
|
{
|
||||||
public class Fruit : CatchBaseHit
|
public class Fruit : CatchHitObject
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,7 +15,7 @@ using osu.Framework.Lists;
|
|||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Objects
|
namespace osu.Game.Rulesets.Catch.Objects
|
||||||
{
|
{
|
||||||
public class JuiceStream : CatchBaseHit, IHasCurve
|
public class JuiceStream : CatchHitObject, IHasCurve
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Positional distance that results in a duration of one second, before any speed adjustments.
|
/// Positional distance that results in a duration of one second, before any speed adjustments.
|
||||||
@ -42,11 +42,11 @@ namespace osu.Game.Rulesets.Catch.Objects
|
|||||||
TickDistance = scoringDistance / difficulty.SliderTickRate;
|
TickDistance = scoringDistance / difficulty.SliderTickRate;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IEnumerable<CatchBaseHit> Ticks
|
public IEnumerable<CatchHitObject> Ticks
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
SortedList<CatchBaseHit> ticks = new SortedList<CatchBaseHit>((a, b) => a.StartTime.CompareTo(b.StartTime));
|
SortedList<CatchHitObject> ticks = new SortedList<CatchHitObject>((a, b) => a.StartTime.CompareTo(b.StartTime));
|
||||||
|
|
||||||
if (TickDistance == 0)
|
if (TickDistance == 0)
|
||||||
return ticks;
|
return ticks;
|
||||||
|
@ -10,14 +10,14 @@ using osu.Game.Rulesets.UI;
|
|||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Scoring
|
namespace osu.Game.Rulesets.Catch.Scoring
|
||||||
{
|
{
|
||||||
internal class CatchScoreProcessor : ScoreProcessor<CatchBaseHit>
|
internal class CatchScoreProcessor : ScoreProcessor<CatchHitObject>
|
||||||
{
|
{
|
||||||
public CatchScoreProcessor(RulesetContainer<CatchBaseHit> rulesetContainer)
|
public CatchScoreProcessor(RulesetContainer<CatchHitObject> rulesetContainer)
|
||||||
: base(rulesetContainer)
|
: base(rulesetContainer)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void SimulateAutoplay(Beatmap<CatchBaseHit> beatmap)
|
protected override void SimulateAutoplay(Beatmap<CatchHitObject> beatmap)
|
||||||
{
|
{
|
||||||
foreach (var obj in beatmap.HitObjects)
|
foreach (var obj in beatmap.HitObjects)
|
||||||
{
|
{
|
||||||
|
@ -11,16 +11,26 @@ namespace osu.Game.Rulesets.Catch.Tests
|
|||||||
[Ignore("getting CI working")]
|
[Ignore("getting CI working")]
|
||||||
public class TestCaseCatchStacker : Game.Tests.Visual.TestCasePlayer
|
public class TestCaseCatchStacker : Game.Tests.Visual.TestCasePlayer
|
||||||
{
|
{
|
||||||
public TestCaseCatchStacker() : base(typeof(CatchRuleset))
|
public TestCaseCatchStacker()
|
||||||
|
: base(typeof(CatchRuleset))
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override Beatmap CreateBeatmap()
|
protected override Beatmap CreateBeatmap()
|
||||||
{
|
{
|
||||||
var beatmap = new Beatmap();
|
var beatmap = new Beatmap
|
||||||
|
{
|
||||||
|
BeatmapInfo = new BeatmapInfo
|
||||||
|
{
|
||||||
|
BaseDifficulty = new BeatmapDifficulty
|
||||||
|
{
|
||||||
|
CircleSize = 6,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
for (int i = 0; i < 256; i++)
|
for (int i = 0; i < 512; i++)
|
||||||
beatmap.HitObjects.Add(new Fruit { X = 0.5f, StartTime = i * 100, NewCombo = i % 8 == 0 });
|
beatmap.HitObjects.Add(new Fruit { X = 0.5f + i / 2048f * (i % 10 - 5), StartTime = i * 100, NewCombo = i % 8 == 0 });
|
||||||
|
|
||||||
return beatmap;
|
return beatmap;
|
||||||
}
|
}
|
||||||
|
@ -1,44 +0,0 @@
|
|||||||
// Copyright (c) 2007-2017 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.Allocation;
|
|
||||||
using osu.Framework.Graphics;
|
|
||||||
using osu.Game.Rulesets.Catch.UI;
|
|
||||||
using osu.Game.Tests.Visual;
|
|
||||||
using OpenTK;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Tests
|
|
||||||
{
|
|
||||||
[TestFixture]
|
|
||||||
[Ignore("getting CI working")]
|
|
||||||
internal class TestCaseCatcher : OsuTestCase
|
|
||||||
{
|
|
||||||
public override IReadOnlyList<Type> RequiredTypes => new[]
|
|
||||||
{
|
|
||||||
typeof(Catcher),
|
|
||||||
};
|
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
|
||||||
private void load(RulesetStore rulesets)
|
|
||||||
{
|
|
||||||
Children = new Drawable[]
|
|
||||||
{
|
|
||||||
new CatchInputManager(rulesets.GetRuleset(2))
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Child = new Catcher
|
|
||||||
{
|
|
||||||
RelativePositionAxes = Axes.Both,
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Anchor = Anchor.BottomLeft,
|
|
||||||
Origin = Anchor.BottomLeft,
|
|
||||||
Size = new Vector2(1, 0.2f),
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
62
osu.Game.Rulesets.Catch/Tests/TestCaseCatcherArea.cs
Normal file
62
osu.Game.Rulesets.Catch/Tests/TestCaseCatcherArea.cs
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
// Copyright (c) 2007-2017 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.Allocation;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Rulesets.Catch.UI;
|
||||||
|
using osu.Game.Tests.Visual;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Catch.Tests
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
[Ignore("getting CI working")]
|
||||||
|
internal class TestCaseCatcherArea : OsuTestCase
|
||||||
|
{
|
||||||
|
private RulesetInfo catchRuleset;
|
||||||
|
private TestCatcherArea catcherArea;
|
||||||
|
|
||||||
|
public override IReadOnlyList<Type> RequiredTypes => new[]
|
||||||
|
{
|
||||||
|
typeof(CatcherArea),
|
||||||
|
};
|
||||||
|
|
||||||
|
public TestCaseCatcherArea()
|
||||||
|
{
|
||||||
|
AddSliderStep<float>("CircleSize", 0, 8, 5, createCatcher);
|
||||||
|
AddToggleStep("Hyperdash", t => catcherArea.ToggleHyperDash(t));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createCatcher(float size)
|
||||||
|
{
|
||||||
|
Child = new CatchInputManager(catchRuleset)
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Child = catcherArea = new TestCatcherArea(new BeatmapDifficulty { CircleSize = size })
|
||||||
|
{
|
||||||
|
Anchor = Anchor.CentreLeft,
|
||||||
|
Origin = Anchor.BottomLeft
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(RulesetStore rulesets)
|
||||||
|
{
|
||||||
|
catchRuleset = rulesets.GetRuleset(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TestCatcherArea : CatcherArea
|
||||||
|
{
|
||||||
|
public TestCatcherArea(BeatmapDifficulty beatmapDifficulty)
|
||||||
|
: base(beatmapDifficulty)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ToggleHyperDash(bool status) => MovableCatcher.HyperDashModifier = status ? 2 : 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
30
osu.Game.Rulesets.Catch/Tests/TestCaseHyperdash.cs
Normal file
30
osu.Game.Rulesets.Catch/Tests/TestCaseHyperdash.cs
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
|
||||||
|
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||||
|
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Rulesets.Catch.Objects;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Catch.Tests
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
[Ignore("getting CI working")]
|
||||||
|
public class TestCaseHyperdash : Game.Tests.Visual.TestCasePlayer
|
||||||
|
{
|
||||||
|
public TestCaseHyperdash()
|
||||||
|
: base(typeof(CatchRuleset))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override Beatmap CreateBeatmap()
|
||||||
|
{
|
||||||
|
var beatmap = new Beatmap();
|
||||||
|
|
||||||
|
for (int i = 0; i < 512; i++)
|
||||||
|
if (i % 5 < 3)
|
||||||
|
beatmap.HitObjects.Add(new Fruit { X = i % 10 < 5 ? 0.02f : 0.98f, StartTime = i * 100, NewCombo = i % 8 == 0 });
|
||||||
|
|
||||||
|
return beatmap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
16
osu.Game.Rulesets.Catch/Tests/TestCasePerformancePoints.cs
Normal file
16
osu.Game.Rulesets.Catch/Tests/TestCasePerformancePoints.cs
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
|
||||||
|
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||||
|
|
||||||
|
using NUnit.Framework;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Catch.Tests
|
||||||
|
{
|
||||||
|
[Ignore("getting CI working")]
|
||||||
|
public class TestCasePerformancePoints : Game.Tests.Visual.TestCasePerformancePoints
|
||||||
|
{
|
||||||
|
public TestCasePerformancePoints()
|
||||||
|
: base(new CatchRuleset(new RulesetInfo()))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,11 +1,11 @@
|
|||||||
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
|
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
|
||||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||||
|
|
||||||
using System;
|
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Game.Rulesets.UI;
|
using osu.Game.Rulesets.UI;
|
||||||
using OpenTK;
|
using OpenTK;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Rulesets.Catch.Objects;
|
using osu.Game.Rulesets.Catch.Objects;
|
||||||
using osu.Game.Rulesets.Catch.Objects.Drawable;
|
using osu.Game.Rulesets.Catch.Objects.Drawable;
|
||||||
using osu.Game.Rulesets.Judgements;
|
using osu.Game.Rulesets.Judgements;
|
||||||
@ -15,15 +15,14 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
{
|
{
|
||||||
public class CatchPlayfield : ScrollingPlayfield
|
public class CatchPlayfield : ScrollingPlayfield
|
||||||
{
|
{
|
||||||
public static readonly float BASE_WIDTH = 512;
|
public const float BASE_WIDTH = 512;
|
||||||
|
|
||||||
protected override Container<Drawable> Content => content;
|
protected override Container<Drawable> Content => content;
|
||||||
private readonly Container<Drawable> content;
|
private readonly Container<Drawable> content;
|
||||||
|
|
||||||
private readonly Container catcherContainer;
|
private readonly CatcherArea catcherArea;
|
||||||
private readonly Catcher catcher;
|
|
||||||
|
|
||||||
public CatchPlayfield()
|
public CatchPlayfield(BeatmapDifficulty difficulty)
|
||||||
: base(Axes.Y)
|
: base(Axes.Y)
|
||||||
{
|
{
|
||||||
Container explodingFruitContainer;
|
Container explodingFruitContainer;
|
||||||
@ -43,30 +42,16 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
},
|
},
|
||||||
catcherContainer = new Container
|
catcherArea = new CatcherArea(difficulty)
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.X,
|
|
||||||
Anchor = Anchor.BottomLeft,
|
|
||||||
Origin = Anchor.TopLeft,
|
|
||||||
Height = 180,
|
|
||||||
Child = catcher = new Catcher
|
|
||||||
{
|
{
|
||||||
ExplodingFruitTarget = explodingFruitContainer,
|
ExplodingFruitTarget = explodingFruitContainer,
|
||||||
RelativePositionAxes = Axes.Both,
|
Anchor = Anchor.BottomLeft,
|
||||||
Origin = Anchor.TopCentre,
|
Origin = Anchor.TopLeft,
|
||||||
X = 0.5f,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Update()
|
public bool CheckIfWeCanCatch(CatchHitObject obj) => catcherArea.AttemptCatch(obj);
|
||||||
{
|
|
||||||
base.Update();
|
|
||||||
catcher.Size = new Vector2(catcherContainer.DrawSize.Y);
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool CheckIfWeCanCatch(CatchBaseHit obj) => Math.Abs(catcher.Position.X - obj.X) < catcher.DrawSize.X / DrawSize.X / 2;
|
|
||||||
|
|
||||||
public override void Add(DrawableHitObject h)
|
public override void Add(DrawableHitObject h)
|
||||||
{
|
{
|
||||||
@ -88,7 +73,7 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
(judgedObject.Parent as Container<DrawableHitObject>)?.Remove(judgedObject);
|
(judgedObject.Parent as Container<DrawableHitObject>)?.Remove(judgedObject);
|
||||||
(judgedObject.Parent as Container)?.Remove(judgedObject);
|
(judgedObject.Parent as Container)?.Remove(judgedObject);
|
||||||
|
|
||||||
catcher.Add(judgedObject, screenPosition);
|
catcherArea.Add(judgedObject, screenPosition);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,7 +13,7 @@ using osu.Game.Rulesets.UI;
|
|||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.UI
|
namespace osu.Game.Rulesets.Catch.UI
|
||||||
{
|
{
|
||||||
public class CatchRulesetContainer : ScrollingRulesetContainer<CatchPlayfield, CatchBaseHit>
|
public class CatchRulesetContainer : ScrollingRulesetContainer<CatchPlayfield, CatchHitObject>
|
||||||
{
|
{
|
||||||
public CatchRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap, bool isForCurrentRuleset)
|
public CatchRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap, bool isForCurrentRuleset)
|
||||||
: base(ruleset, beatmap, isForCurrentRuleset)
|
: base(ruleset, beatmap, isForCurrentRuleset)
|
||||||
@ -22,15 +22,15 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
|
|
||||||
public override ScoreProcessor CreateScoreProcessor() => new CatchScoreProcessor(this);
|
public override ScoreProcessor CreateScoreProcessor() => new CatchScoreProcessor(this);
|
||||||
|
|
||||||
protected override BeatmapProcessor<CatchBaseHit> CreateBeatmapProcessor() => new CatchBeatmapProcessor();
|
protected override BeatmapProcessor<CatchHitObject> CreateBeatmapProcessor() => new CatchBeatmapProcessor();
|
||||||
|
|
||||||
protected override BeatmapConverter<CatchBaseHit> CreateBeatmapConverter() => new CatchBeatmapConverter();
|
protected override BeatmapConverter<CatchHitObject> CreateBeatmapConverter() => new CatchBeatmapConverter();
|
||||||
|
|
||||||
protected override Playfield CreatePlayfield() => new CatchPlayfield();
|
protected override Playfield CreatePlayfield() => new CatchPlayfield(Beatmap.BeatmapInfo.BaseDifficulty);
|
||||||
|
|
||||||
public override PassThroughInputManager CreateInputManager() => new CatchInputManager(Ruleset.RulesetInfo);
|
public override PassThroughInputManager CreateInputManager() => new CatchInputManager(Ruleset.RulesetInfo);
|
||||||
|
|
||||||
protected override DrawableHitObject<CatchBaseHit> GetVisualRepresentation(CatchBaseHit h)
|
protected override DrawableHitObject<CatchHitObject> GetVisualRepresentation(CatchHitObject h)
|
||||||
{
|
{
|
||||||
var fruit = h as Fruit;
|
var fruit = h as Fruit;
|
||||||
if (fruit != null)
|
if (fruit != null)
|
||||||
|
@ -1,193 +0,0 @@
|
|||||||
// Copyright (c) 2007-2017 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.Framework.Allocation;
|
|
||||||
using osu.Framework.Graphics;
|
|
||||||
using osu.Framework.Graphics.Containers;
|
|
||||||
using osu.Framework.Graphics.Sprites;
|
|
||||||
using osu.Framework.Graphics.Textures;
|
|
||||||
using osu.Framework.Input.Bindings;
|
|
||||||
using osu.Framework.MathUtils;
|
|
||||||
using osu.Game.Rulesets.Catch.Objects;
|
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
|
||||||
using OpenTK;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.UI
|
|
||||||
{
|
|
||||||
public class Catcher : Container, IKeyBindingHandler<CatchAction>
|
|
||||||
{
|
|
||||||
private Texture texture;
|
|
||||||
|
|
||||||
private Container<DrawableHitObject> caughtFruit;
|
|
||||||
|
|
||||||
public Container ExplodingFruitTarget;
|
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
|
||||||
private void load(TextureStore textures)
|
|
||||||
{
|
|
||||||
texture = textures.Get(@"Play/Catch/fruit-catcher-idle");
|
|
||||||
|
|
||||||
Children = new Drawable[]
|
|
||||||
{
|
|
||||||
createCatcherSprite(),
|
|
||||||
caughtFruit = new Container<DrawableHitObject>
|
|
||||||
{
|
|
||||||
Anchor = Anchor.TopCentre,
|
|
||||||
Origin = Anchor.BottomCentre,
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private int currentDirection;
|
|
||||||
|
|
||||||
private bool dashing;
|
|
||||||
|
|
||||||
protected bool Dashing
|
|
||||||
{
|
|
||||||
get { return dashing; }
|
|
||||||
set
|
|
||||||
{
|
|
||||||
if (value == dashing) return;
|
|
||||||
|
|
||||||
dashing = value;
|
|
||||||
|
|
||||||
if (dashing)
|
|
||||||
Schedule(addAdditiveSprite);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void addAdditiveSprite()
|
|
||||||
{
|
|
||||||
if (!dashing) return;
|
|
||||||
|
|
||||||
var additive = createCatcherSprite();
|
|
||||||
|
|
||||||
additive.RelativePositionAxes = Axes.Both;
|
|
||||||
additive.Blending = BlendingMode.Additive;
|
|
||||||
additive.Position = Position;
|
|
||||||
additive.Scale = Scale;
|
|
||||||
|
|
||||||
((Container)Parent).Add(additive);
|
|
||||||
|
|
||||||
additive.FadeTo(0.4f).FadeOut(800, Easing.OutQuint).Expire();
|
|
||||||
|
|
||||||
Scheduler.AddDelayed(addAdditiveSprite, 50);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Sprite createCatcherSprite() => new Sprite
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
FillMode = FillMode.Fit,
|
|
||||||
Texture = texture,
|
|
||||||
OriginPosition = new Vector2(DrawWidth / 2, 10) //temporary until the sprite is aligned correctly.
|
|
||||||
};
|
|
||||||
|
|
||||||
public bool OnPressed(CatchAction action)
|
|
||||||
{
|
|
||||||
switch (action)
|
|
||||||
{
|
|
||||||
case CatchAction.MoveLeft:
|
|
||||||
currentDirection--;
|
|
||||||
return true;
|
|
||||||
case CatchAction.MoveRight:
|
|
||||||
currentDirection++;
|
|
||||||
return true;
|
|
||||||
case CatchAction.Dash:
|
|
||||||
Dashing = true;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool OnReleased(CatchAction action)
|
|
||||||
{
|
|
||||||
switch (action)
|
|
||||||
{
|
|
||||||
case CatchAction.MoveLeft:
|
|
||||||
currentDirection++;
|
|
||||||
return true;
|
|
||||||
case CatchAction.MoveRight:
|
|
||||||
currentDirection--;
|
|
||||||
return true;
|
|
||||||
case CatchAction.Dash:
|
|
||||||
Dashing = false;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The relative space to cover in 1 millisecond. based on 1 game pixel per millisecond as in osu-stable.
|
|
||||||
/// </summary>
|
|
||||||
private const double base_speed = 1.0 / 512;
|
|
||||||
|
|
||||||
protected override void Update()
|
|
||||||
{
|
|
||||||
base.Update();
|
|
||||||
|
|
||||||
if (currentDirection == 0) return;
|
|
||||||
|
|
||||||
double dashModifier = Dashing ? 1 : 0.5;
|
|
||||||
|
|
||||||
Scale = new Vector2(Math.Sign(currentDirection), 1);
|
|
||||||
X = (float)MathHelper.Clamp(X + Math.Sign(currentDirection) * Clock.ElapsedFrameTime * base_speed * dashModifier, 0, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Add(DrawableHitObject fruit, Vector2 absolutePosition)
|
|
||||||
{
|
|
||||||
fruit.RelativePositionAxes = Axes.None;
|
|
||||||
fruit.Position = new Vector2(ToLocalSpace(absolutePosition).X - DrawSize.X / 2, 0);
|
|
||||||
|
|
||||||
fruit.Anchor = Anchor.TopCentre;
|
|
||||||
fruit.Origin = Anchor.BottomCentre;
|
|
||||||
fruit.Scale *= 0.7f;
|
|
||||||
fruit.LifetimeEnd = double.MaxValue;
|
|
||||||
|
|
||||||
float distance = fruit.DrawSize.X / 2 * fruit.Scale.X;
|
|
||||||
|
|
||||||
while (caughtFruit.Any(f => f.LifetimeEnd == double.MaxValue && Vector2Extensions.DistanceSquared(f.Position, fruit.Position) < distance * distance))
|
|
||||||
{
|
|
||||||
fruit.X += RNG.Next(-5, 5);
|
|
||||||
fruit.Y -= RNG.Next(0, 5);
|
|
||||||
}
|
|
||||||
|
|
||||||
caughtFruit.Add(fruit);
|
|
||||||
|
|
||||||
if (((CatchBaseHit)fruit.HitObject).LastInCombo)
|
|
||||||
explode();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void explode()
|
|
||||||
{
|
|
||||||
var fruit = caughtFruit.ToArray();
|
|
||||||
|
|
||||||
foreach (var f in fruit)
|
|
||||||
{
|
|
||||||
var originalX = f.X * Scale.X;
|
|
||||||
|
|
||||||
if (ExplodingFruitTarget != null)
|
|
||||||
{
|
|
||||||
f.Anchor = Anchor.TopLeft;
|
|
||||||
f.Position = caughtFruit.ToSpaceOfOtherDrawable(f.DrawPosition, ExplodingFruitTarget);
|
|
||||||
|
|
||||||
caughtFruit.Remove(f);
|
|
||||||
|
|
||||||
ExplodingFruitTarget.Add(f);
|
|
||||||
}
|
|
||||||
|
|
||||||
f.MoveToY(f.Y - 50, 250, Easing.OutSine)
|
|
||||||
.Then()
|
|
||||||
.MoveToY(f.Y + 50, 500, Easing.InSine);
|
|
||||||
|
|
||||||
f.MoveToX(f.X + originalX * 6, 1000);
|
|
||||||
f.FadeOut(750);
|
|
||||||
|
|
||||||
f.Expire();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
342
osu.Game.Rulesets.Catch/UI/CatcherArea.cs
Normal file
342
osu.Game.Rulesets.Catch/UI/CatcherArea.cs
Normal file
@ -0,0 +1,342 @@
|
|||||||
|
// Copyright (c) 2007-2017 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.Framework.Allocation;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
using osu.Framework.Graphics.Textures;
|
||||||
|
using osu.Framework.Input.Bindings;
|
||||||
|
using osu.Framework.MathUtils;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Rulesets.Catch.Objects;
|
||||||
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
|
using OpenTK;
|
||||||
|
using OpenTK.Graphics;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Catch.UI
|
||||||
|
{
|
||||||
|
public class CatcherArea : Container
|
||||||
|
{
|
||||||
|
public const float CATCHER_SIZE = 172;
|
||||||
|
|
||||||
|
protected readonly Catcher MovableCatcher;
|
||||||
|
|
||||||
|
public Container ExplodingFruitTarget
|
||||||
|
{
|
||||||
|
set { MovableCatcher.ExplodingFruitTarget = value; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public CatcherArea(BeatmapDifficulty difficulty = null)
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X;
|
||||||
|
Height = CATCHER_SIZE;
|
||||||
|
Child = MovableCatcher = new Catcher(difficulty)
|
||||||
|
{
|
||||||
|
AdditiveTarget = this,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Add(DrawableHitObject fruit, Vector2 absolutePosition)
|
||||||
|
{
|
||||||
|
fruit.RelativePositionAxes = Axes.None;
|
||||||
|
fruit.Position = new Vector2(MovableCatcher.ToLocalSpace(absolutePosition).X - MovableCatcher.DrawSize.X / 2, 0);
|
||||||
|
|
||||||
|
fruit.Anchor = Anchor.TopCentre;
|
||||||
|
fruit.Origin = Anchor.BottomCentre;
|
||||||
|
fruit.Scale *= 0.7f;
|
||||||
|
fruit.LifetimeEnd = double.MaxValue;
|
||||||
|
|
||||||
|
MovableCatcher.Add(fruit);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool AttemptCatch(CatchHitObject obj) => MovableCatcher.AttemptCatch(obj);
|
||||||
|
|
||||||
|
public class Catcher : Container, IKeyBindingHandler<CatchAction>
|
||||||
|
{
|
||||||
|
private Texture texture;
|
||||||
|
|
||||||
|
private Container<DrawableHitObject> caughtFruit;
|
||||||
|
|
||||||
|
public Container ExplodingFruitTarget;
|
||||||
|
|
||||||
|
public Container AdditiveTarget;
|
||||||
|
|
||||||
|
public Catcher(BeatmapDifficulty difficulty = null)
|
||||||
|
{
|
||||||
|
RelativePositionAxes = Axes.X;
|
||||||
|
X = 0.5f;
|
||||||
|
|
||||||
|
Origin = Anchor.TopCentre;
|
||||||
|
Anchor = Anchor.TopLeft;
|
||||||
|
|
||||||
|
Size = new Vector2(CATCHER_SIZE);
|
||||||
|
if (difficulty != null)
|
||||||
|
Scale = new Vector2(1.0f - 0.7f * (difficulty.CircleSize - 5) / 5);
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(TextureStore textures)
|
||||||
|
{
|
||||||
|
texture = textures.Get(@"Play/Catch/fruit-catcher-idle");
|
||||||
|
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
createCatcherSprite(),
|
||||||
|
caughtFruit = new Container<DrawableHitObject>
|
||||||
|
{
|
||||||
|
Anchor = Anchor.TopCentre,
|
||||||
|
Origin = Anchor.BottomCentre,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private int currentDirection;
|
||||||
|
|
||||||
|
private bool dashing;
|
||||||
|
|
||||||
|
protected bool Dashing
|
||||||
|
{
|
||||||
|
get { return dashing; }
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (value == dashing) return;
|
||||||
|
|
||||||
|
dashing = value;
|
||||||
|
|
||||||
|
Trail |= dashing;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool trail;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Activate or deactive the trail. Will be automatically deactivated when conditions to keep the trail displayed are no longer met.
|
||||||
|
/// </summary>
|
||||||
|
protected bool Trail
|
||||||
|
{
|
||||||
|
get { return trail; }
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (value == trail) return;
|
||||||
|
|
||||||
|
trail = value;
|
||||||
|
|
||||||
|
if (Trail)
|
||||||
|
beginTrail();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void beginTrail()
|
||||||
|
{
|
||||||
|
Trail &= dashing || HyperDashing;
|
||||||
|
Trail &= AdditiveTarget != null;
|
||||||
|
|
||||||
|
if (!Trail) return;
|
||||||
|
|
||||||
|
var additive = createCatcherSprite();
|
||||||
|
|
||||||
|
additive.Anchor = Anchor;
|
||||||
|
additive.OriginPosition = additive.OriginPosition + new Vector2(DrawWidth / 2, 0); // also temporary to align sprite correctly.
|
||||||
|
additive.Position = Position;
|
||||||
|
additive.Scale = Scale;
|
||||||
|
additive.Colour = HyperDashing ? Color4.Red : Color4.White;
|
||||||
|
additive.RelativePositionAxes = RelativePositionAxes;
|
||||||
|
additive.Blending = BlendingMode.Additive;
|
||||||
|
|
||||||
|
AdditiveTarget.Add(additive);
|
||||||
|
|
||||||
|
additive.FadeTo(0.4f).FadeOut(800, Easing.OutQuint).Expire();
|
||||||
|
|
||||||
|
Scheduler.AddDelayed(beginTrail, HyperDashing ? 25 : 50);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Sprite createCatcherSprite() => new Sprite
|
||||||
|
{
|
||||||
|
Size = new Vector2(CATCHER_SIZE),
|
||||||
|
FillMode = FillMode.Fill,
|
||||||
|
Texture = texture,
|
||||||
|
OriginPosition = new Vector2(-3, 10) // temporary until the sprite is aligned correctly.
|
||||||
|
};
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Add a caught fruit to the catcher's stack.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="fruit">The fruit that was caught.</param>
|
||||||
|
public void Add(DrawableHitObject fruit)
|
||||||
|
{
|
||||||
|
float distance = fruit.DrawSize.X / 2 * fruit.Scale.X;
|
||||||
|
|
||||||
|
while (caughtFruit.Any(f => f.LifetimeEnd == double.MaxValue && Vector2Extensions.DistanceSquared(f.Position, fruit.Position) < distance * distance))
|
||||||
|
{
|
||||||
|
fruit.X += RNG.Next(-5, 5);
|
||||||
|
fruit.Y -= RNG.Next(0, 5);
|
||||||
|
}
|
||||||
|
|
||||||
|
caughtFruit.Add(fruit);
|
||||||
|
|
||||||
|
var catchObject = (CatchHitObject)fruit.HitObject;
|
||||||
|
|
||||||
|
if (catchObject.LastInCombo)
|
||||||
|
explode();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Let the catcher attempt to catch a fruit.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="fruit">The fruit to catch.</param>
|
||||||
|
/// <returns>Whether the catch is possible.</returns>
|
||||||
|
public bool AttemptCatch(CatchHitObject fruit)
|
||||||
|
{
|
||||||
|
const double relative_catcher_width = CATCHER_SIZE / 2;
|
||||||
|
|
||||||
|
// this stuff wil disappear once we move fruit to non-relative coordinate space in the future.
|
||||||
|
var catchObjectPosition = fruit.X * CatchPlayfield.BASE_WIDTH;
|
||||||
|
var catcherPosition = Position.X * CatchPlayfield.BASE_WIDTH;
|
||||||
|
|
||||||
|
var validCatch =
|
||||||
|
catchObjectPosition >= catcherPosition - relative_catcher_width / 2 &&
|
||||||
|
catchObjectPosition <= catcherPosition + relative_catcher_width / 2;
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
HyperDashModifier = 1;
|
||||||
|
|
||||||
|
return validCatch;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether we are hypderdashing or not.
|
||||||
|
/// </summary>
|
||||||
|
public bool HyperDashing => hyperDashModifier != 1;
|
||||||
|
|
||||||
|
private double hyperDashModifier = 1;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The direction in which hyperdash is allowed. 0 allows both directions.
|
||||||
|
/// </summary>
|
||||||
|
public double HyperDashDirection;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The speed modifier resultant from hyperdash. Will trigger hyperdash when not equal to 1.
|
||||||
|
/// </summary>
|
||||||
|
public double HyperDashModifier
|
||||||
|
{
|
||||||
|
get { return hyperDashModifier; }
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (value == hyperDashModifier) return;
|
||||||
|
hyperDashModifier = value;
|
||||||
|
|
||||||
|
const float transition_length = 180;
|
||||||
|
|
||||||
|
if (HyperDashing)
|
||||||
|
{
|
||||||
|
this.FadeColour(Color4.OrangeRed, transition_length, Easing.OutQuint);
|
||||||
|
this.FadeTo(0.2f, transition_length, Easing.OutQuint);
|
||||||
|
Trail = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
HyperDashDirection = 0;
|
||||||
|
this.FadeColour(Color4.White, transition_length, Easing.OutQuint);
|
||||||
|
this.FadeTo(1, transition_length, Easing.OutQuint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool OnPressed(CatchAction action)
|
||||||
|
{
|
||||||
|
switch (action)
|
||||||
|
{
|
||||||
|
case CatchAction.MoveLeft:
|
||||||
|
currentDirection--;
|
||||||
|
return true;
|
||||||
|
case CatchAction.MoveRight:
|
||||||
|
currentDirection++;
|
||||||
|
return true;
|
||||||
|
case CatchAction.Dash:
|
||||||
|
Dashing = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool OnReleased(CatchAction action)
|
||||||
|
{
|
||||||
|
switch (action)
|
||||||
|
{
|
||||||
|
case CatchAction.MoveLeft:
|
||||||
|
currentDirection++;
|
||||||
|
return true;
|
||||||
|
case CatchAction.MoveRight:
|
||||||
|
currentDirection--;
|
||||||
|
return true;
|
||||||
|
case CatchAction.Dash:
|
||||||
|
Dashing = false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The relative space to cover in 1 millisecond. based on 1 game pixel per millisecond as in osu-stable.
|
||||||
|
/// </summary>
|
||||||
|
public const double BASE_SPEED = 1.0 / 512;
|
||||||
|
|
||||||
|
protected override void Update()
|
||||||
|
{
|
||||||
|
base.Update();
|
||||||
|
|
||||||
|
if (currentDirection == 0) return;
|
||||||
|
|
||||||
|
var direction = Math.Sign(currentDirection);
|
||||||
|
|
||||||
|
double dashModifier = Dashing ? 1 : 0.5;
|
||||||
|
|
||||||
|
if (hyperDashModifier != 1 && (HyperDashDirection == 0 || direction == Math.Sign(HyperDashDirection)))
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void explode()
|
||||||
|
{
|
||||||
|
var fruit = caughtFruit.ToArray();
|
||||||
|
|
||||||
|
foreach (var f in fruit)
|
||||||
|
{
|
||||||
|
var originalX = f.X * Scale.X;
|
||||||
|
|
||||||
|
if (ExplodingFruitTarget != null)
|
||||||
|
{
|
||||||
|
f.Anchor = Anchor.TopLeft;
|
||||||
|
f.Position = caughtFruit.ToSpaceOfOtherDrawable(f.DrawPosition, ExplodingFruitTarget);
|
||||||
|
|
||||||
|
caughtFruit.Remove(f);
|
||||||
|
|
||||||
|
ExplodingFruitTarget.Add(f);
|
||||||
|
}
|
||||||
|
|
||||||
|
f.MoveToY(f.Y - 50, 250, Easing.OutSine)
|
||||||
|
.Then()
|
||||||
|
.MoveToY(f.Y + 50, 500, Easing.InSine);
|
||||||
|
|
||||||
|
f.MoveToX(f.X + originalX * 6, 1000);
|
||||||
|
f.FadeOut(750);
|
||||||
|
|
||||||
|
f.Expire();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
<Import Project="..\osu.Game.props" />
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||||
@ -21,7 +21,6 @@
|
|||||||
<ErrorReport>prompt</ErrorReport>
|
<ErrorReport>prompt</ErrorReport>
|
||||||
<WarningLevel>4</WarningLevel>
|
<WarningLevel>4</WarningLevel>
|
||||||
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
|
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
|
||||||
<LangVersion>6</LangVersion>
|
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||||
<DebugType>pdbonly</DebugType>
|
<DebugType>pdbonly</DebugType>
|
||||||
@ -57,25 +56,24 @@
|
|||||||
<Compile Include="Objects\JuiceStream.cs" />
|
<Compile Include="Objects\JuiceStream.cs" />
|
||||||
<Compile Include="Scoring\CatchScoreProcessor.cs" />
|
<Compile Include="Scoring\CatchScoreProcessor.cs" />
|
||||||
<Compile Include="Judgements\CatchJudgement.cs" />
|
<Compile Include="Judgements\CatchJudgement.cs" />
|
||||||
<Compile Include="Objects\CatchBaseHit.cs" />
|
<Compile Include="Objects\CatchHitObject.cs" />
|
||||||
<Compile Include="Objects\Drawable\DrawableFruit.cs" />
|
<Compile Include="Objects\Drawable\DrawableFruit.cs" />
|
||||||
<Compile Include="Objects\Droplet.cs" />
|
<Compile Include="Objects\Droplet.cs" />
|
||||||
<Compile Include="Objects\Fruit.cs" />
|
<Compile Include="Objects\Fruit.cs" />
|
||||||
<Compile Include="Objects\TinyDroplet.cs" />
|
<Compile Include="Objects\TinyDroplet.cs" />
|
||||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||||
<Compile Include="Tests\TestCaseCatcher.cs" />
|
<Compile Include="Tests\TestCaseCatcherArea.cs" />
|
||||||
<Compile Include="Tests\TestCaseCatchStacker.cs" />
|
<Compile Include="Tests\TestCaseCatchStacker.cs" />
|
||||||
|
<Compile Include="Tests\TestCasePerformancePoints.cs" />
|
||||||
<Compile Include="Tests\TestCaseCatchPlayer.cs" />
|
<Compile Include="Tests\TestCaseCatchPlayer.cs" />
|
||||||
<Compile Include="UI\Catcher.cs" />
|
<Compile Include="Tests\TestCaseHyperdash.cs" />
|
||||||
|
<Compile Include="UI\CatcherArea.cs" />
|
||||||
<Compile Include="UI\CatchRulesetContainer.cs" />
|
<Compile Include="UI\CatchRulesetContainer.cs" />
|
||||||
<Compile Include="UI\CatchPlayfield.cs" />
|
<Compile Include="UI\CatchPlayfield.cs" />
|
||||||
<Compile Include="CatchRuleset.cs" />
|
<Compile Include="CatchRuleset.cs" />
|
||||||
<Compile Include="Mods\CatchMod.cs" />
|
<Compile Include="Mods\CatchMod.cs" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<None Include="..\osu.licenseheader">
|
|
||||||
<Link>osu.licenseheader</Link>
|
|
||||||
</None>
|
|
||||||
<None Include="app.config" />
|
<None Include="app.config" />
|
||||||
<None Include="OpenTK.dll.config" />
|
<None Include="OpenTK.dll.config" />
|
||||||
<None Include="packages.config" />
|
<None Include="packages.config" />
|
||||||
@ -92,6 +90,9 @@
|
|||||||
<Private>True</Private>
|
<Private>True</Private>
|
||||||
</ProjectReference>
|
</ProjectReference>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" />
|
||||||
|
</ItemGroup>
|
||||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||||
Other similar extension points exist, see Microsoft.Common.targets.
|
Other similar extension points exist, see Microsoft.Common.targets.
|
||||||
|
@ -138,8 +138,8 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
|
|||||||
Pattern newPattern = conversion.Generate();
|
Pattern newPattern = conversion.Generate();
|
||||||
lastPattern = newPattern;
|
lastPattern = newPattern;
|
||||||
|
|
||||||
var stairPatternGenerator = (HitObjectPatternGenerator)conversion;
|
var stairPatternGenerator = conversion as HitObjectPatternGenerator;
|
||||||
lastStair = stairPatternGenerator.StairType;
|
lastStair = stairPatternGenerator?.StairType ?? lastStair;
|
||||||
|
|
||||||
return newPattern.HitObjects;
|
return newPattern.HitObjects;
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,7 @@ namespace osu.Game.Rulesets.Mania
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public override double Calculate(Dictionary<string, string> categoryDifficulty = null) => 0;
|
public override double Calculate(Dictionary<string, double> categoryDifficulty = null) => 0;
|
||||||
|
|
||||||
protected override BeatmapConverter<ManiaHitObject> CreateBeatmapConverter(Beatmap beatmap) => new ManiaBeatmapConverter(true, (int)Math.Max(1, Math.Round(beatmap.BeatmapInfo.BaseDifficulty.CircleSize)));
|
protected override BeatmapConverter<ManiaHitObject> CreateBeatmapConverter(Beatmap beatmap) => new ManiaBeatmapConverter(true, (int)Math.Max(1, Math.Round(beatmap.BeatmapInfo.BaseDifficulty.CircleSize)));
|
||||||
}
|
}
|
||||||
|
@ -105,6 +105,8 @@ namespace osu.Game.Rulesets.Mania
|
|||||||
|
|
||||||
public override string Description => "osu!mania";
|
public override string Description => "osu!mania";
|
||||||
|
|
||||||
|
public override string ShortName => "mania";
|
||||||
|
|
||||||
public override Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.fa_osu_mania_o };
|
public override Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.fa_osu_mania_o };
|
||||||
|
|
||||||
public override DifficultyCalculator CreateDifficultyCalculator(Beatmap beatmap, Mod[] mods = null) => new ManiaDifficultyCalculator(beatmap);
|
public override DifficultyCalculator CreateDifficultyCalculator(Beatmap beatmap, Mod[] mods = null) => new ManiaDifficultyCalculator(beatmap);
|
||||||
|
@ -176,22 +176,10 @@ namespace osu.Game.Rulesets.Mania.Mods
|
|||||||
|
|
||||||
public class ManiaModAutoplay : ModAutoplay<ManiaHitObject>
|
public class ManiaModAutoplay : ModAutoplay<ManiaHitObject>
|
||||||
{
|
{
|
||||||
private int availableColumns;
|
|
||||||
|
|
||||||
public override void ApplyToRulesetContainer(RulesetContainer<ManiaHitObject> rulesetContainer)
|
|
||||||
{
|
|
||||||
// Todo: This shouldn't be done, we should be getting a ManiaBeatmap which should store AvailableColumns
|
|
||||||
// But this is dependent on a _lot_ of refactoring
|
|
||||||
var maniaRulesetContainer = (ManiaRulesetContainer)rulesetContainer;
|
|
||||||
availableColumns = maniaRulesetContainer.AvailableColumns;
|
|
||||||
|
|
||||||
base.ApplyToRulesetContainer(rulesetContainer);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override Score CreateReplayScore(Beatmap<ManiaHitObject> beatmap) => new Score
|
protected override Score CreateReplayScore(Beatmap<ManiaHitObject> beatmap) => new Score
|
||||||
{
|
{
|
||||||
User = new User { Username = "osu!topus!" },
|
User = new User { Username = "osu!topus!" },
|
||||||
Replay = new ManiaAutoGenerator(beatmap, availableColumns).Generate(),
|
Replay = new ManiaAutoGenerator(beatmap).Generate(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
|
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
|
||||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||||
|
|
||||||
using System;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Rulesets.Mania.Objects;
|
using osu.Game.Rulesets.Mania.Objects;
|
||||||
@ -13,15 +13,11 @@ namespace osu.Game.Rulesets.Mania.Replays
|
|||||||
{
|
{
|
||||||
internal class ManiaAutoGenerator : AutoGenerator<ManiaHitObject>
|
internal class ManiaAutoGenerator : AutoGenerator<ManiaHitObject>
|
||||||
{
|
{
|
||||||
private const double release_delay = 20;
|
public const double RELEASE_DELAY = 20;
|
||||||
|
|
||||||
private readonly int availableColumns;
|
public ManiaAutoGenerator(Beatmap<ManiaHitObject> beatmap)
|
||||||
|
|
||||||
public ManiaAutoGenerator(Beatmap<ManiaHitObject> beatmap, int availableColumns)
|
|
||||||
: base(beatmap)
|
: base(beatmap)
|
||||||
{
|
{
|
||||||
this.availableColumns = availableColumns;
|
|
||||||
|
|
||||||
Replay = new Replay { User = new User { Username = @"Autoplay" } };
|
Replay = new Replay { User = new User { Username = @"Autoplay" } };
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -30,104 +26,52 @@ namespace osu.Game.Rulesets.Mania.Replays
|
|||||||
public override Replay Generate()
|
public override Replay Generate()
|
||||||
{
|
{
|
||||||
// Todo: Realistically this shouldn't be needed, but the first frame is skipped with the way replays are currently handled
|
// Todo: Realistically this shouldn't be needed, but the first frame is skipped with the way replays are currently handled
|
||||||
Replay.Frames.Add(new ReplayFrame(-100000, null, null, ReplayButtonState.None));
|
Replay.Frames.Add(new ManiaReplayFrame(-100000, 0));
|
||||||
|
|
||||||
double[] holdEndTimes = new double[availableColumns];
|
var pointGroups = generateActionPoints().GroupBy(a => a.Time).OrderBy(g => g.First().Time);
|
||||||
for (int i = 0; i < availableColumns; i++)
|
|
||||||
holdEndTimes[i] = double.NegativeInfinity;
|
|
||||||
|
|
||||||
// Notes are handled row-by-row
|
|
||||||
foreach (var objGroup in Beatmap.HitObjects.GroupBy(h => h.StartTime))
|
|
||||||
{
|
|
||||||
double groupTime = objGroup.Key;
|
|
||||||
|
|
||||||
int activeColumns = 0;
|
int activeColumns = 0;
|
||||||
|
foreach (var group in pointGroups)
|
||||||
// Get the previously held-down active columns
|
|
||||||
for (int i = 0; i < availableColumns; i++)
|
|
||||||
{
|
{
|
||||||
if (holdEndTimes[i] > groupTime)
|
foreach (var point in group)
|
||||||
activeColumns |= 1 << i;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add on the group columns, keeping track of the held notes for the next rows
|
|
||||||
foreach (var obj in objGroup)
|
|
||||||
{
|
{
|
||||||
var holdNote = obj as HoldNote;
|
if (point is HitPoint)
|
||||||
if (holdNote != null)
|
activeColumns |= 1 << point.Column;
|
||||||
holdEndTimes[obj.Column] = Math.Max(holdEndTimes[obj.Column], holdNote.EndTime);
|
if (point is ReleasePoint)
|
||||||
|
activeColumns ^= 1 << point.Column;
|
||||||
activeColumns |= 1 << obj.Column;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Replay.Frames.Add(new ReplayFrame(groupTime, activeColumns, null, ReplayButtonState.None));
|
Replay.Frames.Add(new ManiaReplayFrame(group.First().Time, activeColumns));
|
||||||
|
|
||||||
// Add the release frames. We can't do this with the loop above because we need activeColumns to be fully populated
|
|
||||||
foreach (var obj in objGroup.GroupBy(h => (h as IHasEndTime)?.EndTime ?? h.StartTime + release_delay).OrderBy(h => h.Key))
|
|
||||||
{
|
|
||||||
var groupEndTime = obj.Key;
|
|
||||||
|
|
||||||
int activeColumnsAtEnd = 0;
|
|
||||||
for (int i = 0; i < availableColumns; i++)
|
|
||||||
{
|
|
||||||
if (holdEndTimes[i] > groupEndTime)
|
|
||||||
activeColumnsAtEnd |= 1 << i;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Replay.Frames.Add(new ReplayFrame(groupEndTime, activeColumnsAtEnd, 0, ReplayButtonState.None));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Replay.Frames = Replay.Frames
|
|
||||||
// Pick the maximum activeColumns for all frames at the same time
|
|
||||||
.GroupBy(f => f.Time)
|
|
||||||
.Select(g => new ReplayFrame(g.First().Time, maxMouseX(g), 0, ReplayButtonState.None))
|
|
||||||
// The addition of release frames above maybe result in unordered frames, but we need them ordered
|
|
||||||
.OrderBy(f => f.Time)
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
return Replay;
|
return Replay;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
private IEnumerable<IActionPoint> generateActionPoints()
|
||||||
/// Finds the maximum <see cref="ReplayFrame.MouseX"/> by count of bits from a grouping of <see cref="ReplayFrame"/>s.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="group">The <see cref="ReplayFrame"/> grouping to search.</param>
|
|
||||||
/// <returns>The maximum <see cref="ReplayFrame.MouseX"/> by count of bits.</returns>
|
|
||||||
private float maxMouseX(IGrouping<double, ReplayFrame> group)
|
|
||||||
{
|
{
|
||||||
int currentCount = -1;
|
foreach (var obj in Beatmap.HitObjects)
|
||||||
int currentMax = 0;
|
|
||||||
|
|
||||||
foreach (var val in group)
|
|
||||||
{
|
{
|
||||||
int newCount = countBits((int)(val.MouseX ?? 0));
|
yield return new HitPoint { Time = obj.StartTime, Column = obj.Column };
|
||||||
if (newCount > currentCount)
|
yield return new ReleasePoint { Time = ((obj as IHasEndTime)?.EndTime ?? obj.StartTime) + RELEASE_DELAY, Column = obj.Column };
|
||||||
{
|
|
||||||
currentCount = newCount;
|
|
||||||
currentMax = (int)(val.MouseX ?? 0);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return currentMax;
|
private interface IActionPoint
|
||||||
|
{
|
||||||
|
double Time { get; set; }
|
||||||
|
int Column { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
private struct HitPoint : IActionPoint
|
||||||
/// Counts the number of bits set in a value.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="value">The value to count.</param>
|
|
||||||
/// <returns>The number of set bits.</returns>
|
|
||||||
private int countBits(int value)
|
|
||||||
{
|
{
|
||||||
int count = 0;
|
public double Time { get; set; }
|
||||||
while (value > 0)
|
public int Column { get; set; }
|
||||||
{
|
|
||||||
if ((value & 1) > 0)
|
|
||||||
count++;
|
|
||||||
value >>= 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return count;
|
private struct ReleasePoint : IActionPoint
|
||||||
|
{
|
||||||
|
public double Time { get; set; }
|
||||||
|
public int Column { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,29 +2,37 @@
|
|||||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using osu.Framework.Input;
|
using osu.Framework.Input;
|
||||||
|
using osu.Game.Rulesets.Mania.UI;
|
||||||
using osu.Game.Rulesets.Replays;
|
using osu.Game.Rulesets.Replays;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.Replays
|
namespace osu.Game.Rulesets.Mania.Replays
|
||||||
{
|
{
|
||||||
internal class ManiaFramedReplayInputHandler : FramedReplayInputHandler
|
internal class ManiaFramedReplayInputHandler : FramedReplayInputHandler
|
||||||
{
|
{
|
||||||
public ManiaFramedReplayInputHandler(Replay replay)
|
private readonly ManiaRulesetContainer container;
|
||||||
|
|
||||||
|
public ManiaFramedReplayInputHandler(Replay replay, ManiaRulesetContainer container)
|
||||||
: base(replay)
|
: base(replay)
|
||||||
{
|
{
|
||||||
|
this.container = container;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private ManiaPlayfield playfield;
|
||||||
public override List<InputState> GetPendingStates()
|
public override List<InputState> GetPendingStates()
|
||||||
{
|
{
|
||||||
var actions = new List<ManiaAction>();
|
var actions = new List<ManiaAction>();
|
||||||
|
|
||||||
int activeColumns = (int)(CurrentFrame.MouseX ?? 0);
|
if (playfield == null)
|
||||||
|
playfield = (ManiaPlayfield)container.Playfield;
|
||||||
|
|
||||||
|
int activeColumns = (int)(CurrentFrame.MouseX ?? 0);
|
||||||
int counter = 0;
|
int counter = 0;
|
||||||
while (activeColumns > 0)
|
while (activeColumns > 0)
|
||||||
{
|
{
|
||||||
if ((activeColumns & 1) > 0)
|
if ((activeColumns & 1) > 0)
|
||||||
actions.Add(ManiaAction.Key1 + counter);
|
actions.Add(playfield.Columns.ElementAt(counter).Action);
|
||||||
counter++;
|
counter++;
|
||||||
activeColumns >>= 1;
|
activeColumns >>= 1;
|
||||||
}
|
}
|
||||||
|
17
osu.Game.Rulesets.Mania/Replays/ManiaReplayFrame.cs
Normal file
17
osu.Game.Rulesets.Mania/Replays/ManiaReplayFrame.cs
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
|
||||||
|
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||||
|
|
||||||
|
using osu.Game.Rulesets.Replays;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Mania.Replays
|
||||||
|
{
|
||||||
|
public class ManiaReplayFrame : ReplayFrame
|
||||||
|
{
|
||||||
|
public override bool IsImportant => MouseX > 0;
|
||||||
|
|
||||||
|
public ManiaReplayFrame(double time, int activeColumns)
|
||||||
|
: base(time, activeColumns, null, ReplayButtonState.None)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
173
osu.Game.Rulesets.Mania/Tests/TestCaseAutoGeneration.cs
Normal file
173
osu.Game.Rulesets.Mania/Tests/TestCaseAutoGeneration.cs
Normal file
@ -0,0 +1,173 @@
|
|||||||
|
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
|
||||||
|
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||||
|
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Rulesets.Mania.Objects;
|
||||||
|
using osu.Game.Rulesets.Mania.Replays;
|
||||||
|
using osu.Game.Tests.Visual;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Mania.Tests
|
||||||
|
{
|
||||||
|
[Ignore("getting CI working")]
|
||||||
|
public class TestCaseAutoGeneration : OsuTestCase
|
||||||
|
{
|
||||||
|
[Test]
|
||||||
|
public void TestSingleNote()
|
||||||
|
{
|
||||||
|
// | |
|
||||||
|
// | - |
|
||||||
|
// | |
|
||||||
|
|
||||||
|
var beatmap = new Beatmap<ManiaHitObject>();
|
||||||
|
beatmap.HitObjects.Add(new Note { StartTime = 1000 });
|
||||||
|
|
||||||
|
var generated = new ManiaAutoGenerator(beatmap).Generate();
|
||||||
|
|
||||||
|
Assert.IsTrue(generated.Frames.Count == 3, "Replay must have 3 frames");
|
||||||
|
Assert.AreEqual(1000, generated.Frames[1].Time, "Incorrect hit time");
|
||||||
|
Assert.AreEqual(1000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[2].Time, "Incorrect release time");
|
||||||
|
Assert.AreEqual(1, generated.Frames[1].MouseX, "Key 0 has not been pressed");
|
||||||
|
Assert.AreEqual(0, generated.Frames[2].MouseX, "Key 0 has not been released");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestSingleHoldNote()
|
||||||
|
{
|
||||||
|
// | |
|
||||||
|
// | * |
|
||||||
|
// | * |
|
||||||
|
// | * |
|
||||||
|
// | |
|
||||||
|
|
||||||
|
var beatmap = new Beatmap<ManiaHitObject>();
|
||||||
|
beatmap.HitObjects.Add(new HoldNote { StartTime = 1000, Duration = 2000 });
|
||||||
|
|
||||||
|
var generated = new ManiaAutoGenerator(beatmap).Generate();
|
||||||
|
|
||||||
|
Assert.IsTrue(generated.Frames.Count == 3, "Replay must have 3 frames");
|
||||||
|
Assert.AreEqual(1000, generated.Frames[1].Time, "Incorrect hit time");
|
||||||
|
Assert.AreEqual(3000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[2].Time, "Incorrect release time");
|
||||||
|
Assert.AreEqual(1, generated.Frames[1].MouseX, "Key 0 has not been pressed");
|
||||||
|
Assert.AreEqual(0, generated.Frames[2].MouseX, "Key 0 has not been released");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestSingleNoteChord()
|
||||||
|
{
|
||||||
|
// | | |
|
||||||
|
// | - | - |
|
||||||
|
// | | |
|
||||||
|
|
||||||
|
var beatmap = new Beatmap<ManiaHitObject>();
|
||||||
|
beatmap.HitObjects.Add(new Note { StartTime = 1000 });
|
||||||
|
beatmap.HitObjects.Add(new Note { StartTime = 1000, Column = 1 });
|
||||||
|
|
||||||
|
var generated = new ManiaAutoGenerator(beatmap).Generate();
|
||||||
|
|
||||||
|
Assert.IsTrue(generated.Frames.Count == 3, "Replay must have 3 frames");
|
||||||
|
Assert.AreEqual(1000, generated.Frames[1].Time, "Incorrect hit time");
|
||||||
|
Assert.AreEqual(1000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[2].Time, "Incorrect release time");
|
||||||
|
Assert.AreEqual(3, generated.Frames[1].MouseX, "Keys 1 and 2 have not been pressed");
|
||||||
|
Assert.AreEqual(0, generated.Frames[2].MouseX, "Keys 1 and 2 have not been released");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestHoldNoteChord()
|
||||||
|
{
|
||||||
|
// | | |
|
||||||
|
// | * | * |
|
||||||
|
// | * | * |
|
||||||
|
// | * | * |
|
||||||
|
// | | |
|
||||||
|
|
||||||
|
var beatmap = new Beatmap<ManiaHitObject>();
|
||||||
|
beatmap.HitObjects.Add(new HoldNote { StartTime = 1000, Duration = 2000 });
|
||||||
|
beatmap.HitObjects.Add(new HoldNote { StartTime = 1000, Duration = 2000, Column = 1 });
|
||||||
|
|
||||||
|
var generated = new ManiaAutoGenerator(beatmap).Generate();
|
||||||
|
|
||||||
|
Assert.IsTrue(generated.Frames.Count == 3, "Replay must have 3 frames");
|
||||||
|
Assert.AreEqual(1000, generated.Frames[1].Time, "Incorrect hit time");
|
||||||
|
Assert.AreEqual(3000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[2].Time, "Incorrect release time");
|
||||||
|
Assert.AreEqual(3, generated.Frames[1].MouseX, "Keys 1 and 2 have not been pressed");
|
||||||
|
Assert.AreEqual(0, generated.Frames[2].MouseX, "Keys 1 and 2 have not been released");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestSingleNoteStair()
|
||||||
|
{
|
||||||
|
// | | |
|
||||||
|
// | | - |
|
||||||
|
// | - | |
|
||||||
|
// | | |
|
||||||
|
|
||||||
|
var beatmap = new Beatmap<ManiaHitObject>();
|
||||||
|
beatmap.HitObjects.Add(new Note { StartTime = 1000 });
|
||||||
|
beatmap.HitObjects.Add(new Note { StartTime = 2000, Column = 1 });
|
||||||
|
|
||||||
|
var generated = new ManiaAutoGenerator(beatmap).Generate();
|
||||||
|
|
||||||
|
Assert.IsTrue(generated.Frames.Count == 5, "Replay must have 5 frames");
|
||||||
|
Assert.AreEqual(1000, generated.Frames[1].Time, "Incorrect first note hit time");
|
||||||
|
Assert.AreEqual(1000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[2].Time, "Incorrect first note release time");
|
||||||
|
Assert.AreEqual(2000, generated.Frames[3].Time, "Incorrect second note hit time");
|
||||||
|
Assert.AreEqual(2000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[4].Time, "Incorrect second note release time");
|
||||||
|
Assert.AreEqual(1, generated.Frames[1].MouseX, "Key 1 has not been pressed");
|
||||||
|
Assert.AreEqual(0, generated.Frames[2].MouseX, "Key 1 has not been released");
|
||||||
|
Assert.AreEqual(2, generated.Frames[3].MouseX, "Key 2 has not been pressed");
|
||||||
|
Assert.AreEqual(0, generated.Frames[4].MouseX, "Key 2 has not been released");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestHoldNoteStair()
|
||||||
|
{
|
||||||
|
// | | |
|
||||||
|
// | | * |
|
||||||
|
// | * | * |
|
||||||
|
// | * | * |
|
||||||
|
// | * | |
|
||||||
|
// | | |
|
||||||
|
|
||||||
|
var beatmap = new Beatmap<ManiaHitObject>();
|
||||||
|
beatmap.HitObjects.Add(new HoldNote { StartTime = 1000, Duration = 2000 });
|
||||||
|
beatmap.HitObjects.Add(new HoldNote { StartTime = 2000, Duration = 2000, Column = 1 });
|
||||||
|
|
||||||
|
var generated = new ManiaAutoGenerator(beatmap).Generate();
|
||||||
|
|
||||||
|
Assert.IsTrue(generated.Frames.Count == 5, "Replay must have 5 frames");
|
||||||
|
Assert.AreEqual(1000, generated.Frames[1].Time, "Incorrect first note hit time");
|
||||||
|
Assert.AreEqual(3000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[3].Time, "Incorrect first note release time");
|
||||||
|
Assert.AreEqual(2000, generated.Frames[2].Time, "Incorrect second note hit time");
|
||||||
|
Assert.AreEqual(4000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[4].Time, "Incorrect second note release time");
|
||||||
|
Assert.AreEqual(1, generated.Frames[1].MouseX, "Key 1 has not been pressed");
|
||||||
|
Assert.AreEqual(3, generated.Frames[2].MouseX, "Keys 1 and 2 have not been pressed");
|
||||||
|
Assert.AreEqual(2, generated.Frames[3].MouseX, "Key 1 has not been released");
|
||||||
|
Assert.AreEqual(0, generated.Frames[4].MouseX, "Key 2 has not been released");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestHoldNoteWithReleasePress()
|
||||||
|
{
|
||||||
|
// | | |
|
||||||
|
// | * | - |
|
||||||
|
// | * | |
|
||||||
|
// | * | |
|
||||||
|
// | | |
|
||||||
|
|
||||||
|
var beatmap = new Beatmap<ManiaHitObject>();
|
||||||
|
beatmap.HitObjects.Add(new HoldNote { StartTime = 1000, Duration = 2000 - ManiaAutoGenerator.RELEASE_DELAY });
|
||||||
|
beatmap.HitObjects.Add(new Note { StartTime = 3000, Column = 1 });
|
||||||
|
|
||||||
|
var generated = new ManiaAutoGenerator(beatmap).Generate();
|
||||||
|
|
||||||
|
Assert.IsTrue(generated.Frames.Count == 4, "Replay must have 4 frames");
|
||||||
|
Assert.AreEqual(1000, generated.Frames[1].Time, "Incorrect first note hit time");
|
||||||
|
Assert.AreEqual(3000, generated.Frames[2].Time, "Incorrect second note press time + first note release time");
|
||||||
|
Assert.AreEqual(3000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[3].Time, "Incorrect second note release time");
|
||||||
|
Assert.AreEqual(1, generated.Frames[1].MouseX, "Key 1 has not been pressed");
|
||||||
|
Assert.AreEqual(2, generated.Frames[2].MouseX, "Key 1 has not been released or key 2 has not been pressed");
|
||||||
|
Assert.AreEqual(0, generated.Frames[3].MouseX, "Keys 1 and 2 have not been released");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
16
osu.Game.Rulesets.Mania/Tests/TestCasePerformancePoints.cs
Normal file
16
osu.Game.Rulesets.Mania/Tests/TestCasePerformancePoints.cs
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
|
||||||
|
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||||
|
|
||||||
|
using NUnit.Framework;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Mania.Tests
|
||||||
|
{
|
||||||
|
[Ignore("getting CI working")]
|
||||||
|
public class TestCasePerformancePoints : Game.Tests.Visual.TestCasePerformancePoints
|
||||||
|
{
|
||||||
|
public TestCasePerformancePoints()
|
||||||
|
: base(new ManiaRuleset(new RulesetInfo()))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -124,6 +124,6 @@ namespace osu.Game.Rulesets.Mania.UI
|
|||||||
|
|
||||||
protected override SpeedAdjustmentContainer CreateSpeedAdjustmentContainer(MultiplierControlPoint controlPoint) => new ManiaSpeedAdjustmentContainer(controlPoint, ScrollingAlgorithm.Basic);
|
protected override SpeedAdjustmentContainer CreateSpeedAdjustmentContainer(MultiplierControlPoint controlPoint) => new ManiaSpeedAdjustmentContainer(controlPoint, ScrollingAlgorithm.Basic);
|
||||||
|
|
||||||
protected override FramedReplayInputHandler CreateReplayInputHandler(Replay replay) => new ManiaFramedReplayInputHandler(replay);
|
protected override FramedReplayInputHandler CreateReplayInputHandler(Replay replay) => new ManiaFramedReplayInputHandler(replay, this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
<Import Project="..\osu.Game.props" />
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||||
@ -21,7 +21,6 @@
|
|||||||
<ErrorReport>prompt</ErrorReport>
|
<ErrorReport>prompt</ErrorReport>
|
||||||
<WarningLevel>4</WarningLevel>
|
<WarningLevel>4</WarningLevel>
|
||||||
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
|
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
|
||||||
<LangVersion>6</LangVersion>
|
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||||
<DebugType>pdbonly</DebugType>
|
<DebugType>pdbonly</DebugType>
|
||||||
@ -72,6 +71,7 @@
|
|||||||
<Compile Include="Objects\Types\IHasColumn.cs" />
|
<Compile Include="Objects\Types\IHasColumn.cs" />
|
||||||
<Compile Include="Replays\ManiaAutoGenerator.cs" />
|
<Compile Include="Replays\ManiaAutoGenerator.cs" />
|
||||||
<Compile Include="Replays\ManiaFramedReplayInputHandler.cs" />
|
<Compile Include="Replays\ManiaFramedReplayInputHandler.cs" />
|
||||||
|
<Compile Include="Replays\ManiaReplayFrame.cs" />
|
||||||
<Compile Include="Scoring\ManiaScoreProcessor.cs" />
|
<Compile Include="Scoring\ManiaScoreProcessor.cs" />
|
||||||
<Compile Include="Objects\BarLine.cs" />
|
<Compile Include="Objects\BarLine.cs" />
|
||||||
<Compile Include="Objects\HoldNote.cs" />
|
<Compile Include="Objects\HoldNote.cs" />
|
||||||
@ -80,8 +80,10 @@
|
|||||||
<Compile Include="Objects\Note.cs" />
|
<Compile Include="Objects\Note.cs" />
|
||||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||||
<Compile Include="ManiaInputManager.cs" />
|
<Compile Include="ManiaInputManager.cs" />
|
||||||
|
<Compile Include="Tests\TestCaseAutoGeneration.cs" />
|
||||||
<Compile Include="Tests\TestCaseManiaHitObjects.cs" />
|
<Compile Include="Tests\TestCaseManiaHitObjects.cs" />
|
||||||
<Compile Include="Tests\TestCaseManiaPlayfield.cs" />
|
<Compile Include="Tests\TestCaseManiaPlayfield.cs" />
|
||||||
|
<Compile Include="Tests\TestCasePerformancePoints.cs" />
|
||||||
<Compile Include="Timing\GravityScrollingContainer.cs" />
|
<Compile Include="Timing\GravityScrollingContainer.cs" />
|
||||||
<Compile Include="Timing\ScrollingAlgorithm.cs" />
|
<Compile Include="Timing\ScrollingAlgorithm.cs" />
|
||||||
<Compile Include="UI\Column.cs" />
|
<Compile Include="UI\Column.cs" />
|
||||||
@ -96,9 +98,6 @@
|
|||||||
<Compile Include="Timing\ManiaSpeedAdjustmentContainer.cs" />
|
<Compile Include="Timing\ManiaSpeedAdjustmentContainer.cs" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<None Include="..\osu.licenseheader">
|
|
||||||
<Link>osu.licenseheader</Link>
|
|
||||||
</None>
|
|
||||||
<None Include="app.config" />
|
<None Include="app.config" />
|
||||||
<None Include="OpenTK.dll.config" />
|
<None Include="OpenTK.dll.config" />
|
||||||
<None Include="packages.config" />
|
<None Include="packages.config" />
|
||||||
@ -115,6 +114,9 @@
|
|||||||
<Private>True</Private>
|
<Private>True</Private>
|
||||||
</ProjectReference>
|
</ProjectReference>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" />
|
||||||
|
</ItemGroup>
|
||||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||||
Other similar extension points exist, see Microsoft.Common.targets.
|
Other similar extension points exist, see Microsoft.Common.targets.
|
||||||
|
13
osu.Game.Rulesets.Osu/Edit/OsuEditPlayfield.cs
Normal file
13
osu.Game.Rulesets.Osu/Edit/OsuEditPlayfield.cs
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
|
||||||
|
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||||
|
|
||||||
|
using osu.Framework.Graphics.Cursor;
|
||||||
|
using osu.Game.Rulesets.Osu.UI;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Edit
|
||||||
|
{
|
||||||
|
public class OsuEditPlayfield : OsuPlayfield
|
||||||
|
{
|
||||||
|
protected override CursorContainer CreateCursor() => null;
|
||||||
|
}
|
||||||
|
}
|
19
osu.Game.Rulesets.Osu/Edit/OsuEditRulesetContainer.cs
Normal file
19
osu.Game.Rulesets.Osu/Edit/OsuEditRulesetContainer.cs
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
|
||||||
|
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||||
|
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Rulesets.Osu.UI;
|
||||||
|
using osu.Game.Rulesets.UI;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Edit
|
||||||
|
{
|
||||||
|
public class OsuEditRulesetContainer : OsuRulesetContainer
|
||||||
|
{
|
||||||
|
public OsuEditRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap, bool isForCurrentRuleset)
|
||||||
|
: base(ruleset, beatmap, isForCurrentRuleset)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override Playfield CreatePlayfield() => new OsuEditPlayfield();
|
||||||
|
}
|
||||||
|
}
|
29
osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs
Normal file
29
osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
|
||||||
|
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Rulesets.Edit;
|
||||||
|
using osu.Game.Rulesets.Edit.Tools;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
|
using osu.Game.Rulesets.UI;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Edit
|
||||||
|
{
|
||||||
|
public class OsuHitObjectComposer : HitObjectComposer
|
||||||
|
{
|
||||||
|
public OsuHitObjectComposer(Ruleset ruleset)
|
||||||
|
: base(ruleset)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override RulesetContainer CreateRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap) => new OsuEditRulesetContainer(ruleset, beatmap, true);
|
||||||
|
|
||||||
|
protected override IReadOnlyList<ICompositionTool> CompositionTools => new ICompositionTool[]
|
||||||
|
{
|
||||||
|
new HitObjectCompositionTool<HitCircle>(),
|
||||||
|
new HitObjectCompositionTool<Slider>(),
|
||||||
|
new HitObjectCompositionTool<Spinner>()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
@ -45,6 +45,18 @@ namespace osu.Game.Rulesets.Osu.Objects
|
|||||||
set { Curve.Distance = value; }
|
set { Curve.Distance = value; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <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.
|
||||||
|
/// </summary>
|
||||||
|
internal Vector2? LazyEndPosition;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The distance travelled by the cursor upon completion of this <see cref="Slider"/> if it was hit
|
||||||
|
/// with as few movements as possible. This is set and used by difficulty calculation.
|
||||||
|
/// </summary>
|
||||||
|
internal float LazyTravelDistance;
|
||||||
|
|
||||||
public List<SampleInfoList> RepeatSamples { get; set; } = new List<SampleInfoList>();
|
public List<SampleInfoList> RepeatSamples { get; set; } = new List<SampleInfoList>();
|
||||||
public int RepeatCount { get; set; } = 1;
|
public int RepeatCount { get; set; } = 1;
|
||||||
|
|
||||||
|
@ -33,9 +33,9 @@ namespace osu.Game.Rulesets.Osu.OsuDifficulty
|
|||||||
(h as Slider)?.Curve?.Calculate();
|
(h as Slider)?.Curve?.Calculate();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override double Calculate(Dictionary<string, string> categoryDifficulty = null)
|
public override double Calculate(Dictionary<string, double> categoryDifficulty = null)
|
||||||
{
|
{
|
||||||
OsuDifficultyBeatmap beatmap = new OsuDifficultyBeatmap(Beatmap.HitObjects);
|
OsuDifficultyBeatmap beatmap = new OsuDifficultyBeatmap(Beatmap.HitObjects, TimeRate);
|
||||||
Skill[] skills =
|
Skill[] skills =
|
||||||
{
|
{
|
||||||
new Aim(),
|
new Aim(),
|
||||||
@ -67,8 +67,8 @@ namespace osu.Game.Rulesets.Osu.OsuDifficulty
|
|||||||
|
|
||||||
if (categoryDifficulty != null)
|
if (categoryDifficulty != null)
|
||||||
{
|
{
|
||||||
categoryDifficulty.Add("Aim", aimRating.ToString("0.00"));
|
categoryDifficulty.Add("Aim", aimRating);
|
||||||
categoryDifficulty.Add("Speed", speedRating.ToString("0.00"));
|
categoryDifficulty.Add("Speed", speedRating);
|
||||||
}
|
}
|
||||||
|
|
||||||
return starRating;
|
return starRating;
|
||||||
|
@ -20,12 +20,12 @@ namespace osu.Game.Rulesets.Osu.OsuDifficulty.Preprocessing
|
|||||||
/// Creates an enumerator, which preprocesses a list of <see cref="OsuHitObject"/>s recieved as input, wrapping them as
|
/// Creates an enumerator, which preprocesses a list of <see cref="OsuHitObject"/>s recieved as input, wrapping them as
|
||||||
/// <see cref="OsuDifficultyHitObject"/> which contains extra data required for difficulty calculation.
|
/// <see cref="OsuDifficultyHitObject"/> which contains extra data required for difficulty calculation.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public OsuDifficultyBeatmap(List<OsuHitObject> objects)
|
public OsuDifficultyBeatmap(List<OsuHitObject> objects, double timeRate)
|
||||||
{
|
{
|
||||||
// Sort OsuHitObjects by StartTime - they are not correctly ordered in some cases.
|
// Sort OsuHitObjects by StartTime - they are not correctly ordered in some cases.
|
||||||
// This should probably happen before the objects reach the difficulty calculator.
|
// This should probably happen before the objects reach the difficulty calculator.
|
||||||
objects.Sort((a, b) => a.StartTime.CompareTo(b.StartTime));
|
objects.Sort((a, b) => a.StartTime.CompareTo(b.StartTime));
|
||||||
difficultyObjects = createDifficultyObjectEnumerator(objects);
|
difficultyObjects = createDifficultyObjectEnumerator(objects, timeRate);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -67,7 +67,7 @@ namespace osu.Game.Rulesets.Osu.OsuDifficulty.Preprocessing
|
|||||||
|
|
||||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||||
|
|
||||||
private IEnumerator<OsuDifficultyHitObject> createDifficultyObjectEnumerator(List<OsuHitObject> objects)
|
private IEnumerator<OsuDifficultyHitObject> createDifficultyObjectEnumerator(List<OsuHitObject> objects, double timeRate)
|
||||||
{
|
{
|
||||||
// We will process OsuHitObjects in groups of three to form a triangle, so we can calculate an angle for each object.
|
// We will process OsuHitObjects in groups of three to form a triangle, so we can calculate an angle for each object.
|
||||||
OsuHitObject[] triangle = new OsuHitObject[3];
|
OsuHitObject[] triangle = new OsuHitObject[3];
|
||||||
@ -87,7 +87,7 @@ namespace osu.Game.Rulesets.Osu.OsuDifficulty.Preprocessing
|
|||||||
triangle[1] = triangle[0];
|
triangle[1] = triangle[0];
|
||||||
triangle[0] = objects[i];
|
triangle[0] = objects[i];
|
||||||
|
|
||||||
yield return new OsuDifficultyHitObject(triangle);
|
yield return new OsuDifficultyHitObject(triangle, timeRate);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,8 @@
|
|||||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using OpenTK;
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.OsuDifficulty.Preprocessing
|
namespace osu.Game.Rulesets.Osu.OsuDifficulty.Preprocessing
|
||||||
@ -33,13 +35,17 @@ namespace osu.Game.Rulesets.Osu.OsuDifficulty.Preprocessing
|
|||||||
|
|
||||||
private const int normalized_radius = 52;
|
private const int normalized_radius = 52;
|
||||||
|
|
||||||
|
private readonly double timeRate;
|
||||||
|
|
||||||
private readonly OsuHitObject[] t;
|
private readonly OsuHitObject[] t;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes the object calculating extra data required for difficulty calculation.
|
/// Initializes the object calculating extra data required for difficulty calculation.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public OsuDifficultyHitObject(OsuHitObject[] triangle)
|
public OsuDifficultyHitObject(OsuHitObject[] triangle, double timeRate)
|
||||||
{
|
{
|
||||||
|
this.timeRate = timeRate;
|
||||||
|
|
||||||
t = triangle;
|
t = triangle;
|
||||||
BaseObject = t[0];
|
BaseObject = t[0];
|
||||||
setDistances();
|
setDistances();
|
||||||
@ -57,14 +63,53 @@ namespace osu.Game.Rulesets.Osu.OsuDifficulty.Preprocessing
|
|||||||
scalingFactor *= 1 + smallCircleBonus;
|
scalingFactor *= 1 + smallCircleBonus;
|
||||||
}
|
}
|
||||||
|
|
||||||
Distance = (t[0].StackedPosition - t[1].StackedPosition).Length * scalingFactor;
|
Vector2 lastCursorPosition = t[1].StackedPosition;
|
||||||
|
float lastTravelDistance = 0;
|
||||||
|
|
||||||
|
var lastSlider = t[1] as Slider;
|
||||||
|
if (lastSlider != null)
|
||||||
|
{
|
||||||
|
computeSliderCursorPosition(lastSlider);
|
||||||
|
lastCursorPosition = lastSlider.LazyEndPosition ?? lastCursorPosition;
|
||||||
|
lastTravelDistance = lastSlider.LazyTravelDistance;
|
||||||
|
}
|
||||||
|
|
||||||
|
Distance = (lastTravelDistance + (BaseObject.StackedPosition - lastCursorPosition).Length) * scalingFactor;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setTimingValues()
|
private void setTimingValues()
|
||||||
{
|
{
|
||||||
// Every timing inverval is hard capped at the equivalent of 375 BPM streaming speed as a safety measure.
|
// Every timing inverval is hard capped at the equivalent of 375 BPM streaming speed as a safety measure.
|
||||||
DeltaTime = Math.Max(40, t[0].StartTime - t[1].StartTime);
|
DeltaTime = Math.Max(40, (t[0].StartTime - t[1].StartTime) / timeRate);
|
||||||
TimeUntilHit = 450; // BaseObject.PreEmpt;
|
TimeUntilHit = 450; // BaseObject.PreEmpt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void computeSliderCursorPosition(Slider slider)
|
||||||
|
{
|
||||||
|
if (slider.LazyEndPosition != null)
|
||||||
|
return;
|
||||||
|
slider.LazyEndPosition = slider.StackedPosition;
|
||||||
|
|
||||||
|
float approxFollowCircleRadius = (float)(slider.Radius * 3);
|
||||||
|
var computeVertex = new Action<double>(t =>
|
||||||
|
{
|
||||||
|
var diff = slider.PositionAt(t) - slider.LazyEndPosition.Value;
|
||||||
|
float dist = diff.Length;
|
||||||
|
|
||||||
|
if (dist > approxFollowCircleRadius)
|
||||||
|
{
|
||||||
|
// The cursor would be outside the follow circle, we need to move it
|
||||||
|
diff.Normalize(); // Obtain direction of diff
|
||||||
|
dist -= approxFollowCircleRadius;
|
||||||
|
slider.LazyEndPosition += diff * dist;
|
||||||
|
slider.LazyTravelDistance += dist;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var scoringTimes = slider.Ticks.Select(t => t.StartTime).Concat(slider.RepeatPoints.Select(r => r.StartTime)).OrderBy(t => t);
|
||||||
|
foreach (var time in scoringTimes)
|
||||||
|
computeVertex(time);
|
||||||
|
computeVertex(slider.EndTime);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
|
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
|
||||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using osu.Framework.Input.Bindings;
|
using osu.Framework.Input.Bindings;
|
||||||
using osu.Game.Rulesets.UI;
|
using osu.Game.Rulesets.UI;
|
||||||
@ -9,6 +10,8 @@ namespace osu.Game.Rulesets.Osu
|
|||||||
{
|
{
|
||||||
public class OsuInputManager : RulesetInputManager<OsuAction>
|
public class OsuInputManager : RulesetInputManager<OsuAction>
|
||||||
{
|
{
|
||||||
|
public IEnumerable<OsuAction> PressedActions => KeyBindingContainer.PressedActions;
|
||||||
|
|
||||||
public OsuInputManager(RulesetInfo ruleset) : base(ruleset, 0, SimultaneousBindingMode.Unique)
|
public OsuInputManager(RulesetInfo ruleset) : base(ruleset, 0, SimultaneousBindingMode.Unique)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
@ -14,6 +14,10 @@ using System.Linq;
|
|||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Game.Overlays.Settings;
|
using osu.Game.Overlays.Settings;
|
||||||
using osu.Framework.Input.Bindings;
|
using osu.Framework.Input.Bindings;
|
||||||
|
using osu.Game.Rulesets.Scoring;
|
||||||
|
using osu.Game.Rulesets.Osu.Scoring;
|
||||||
|
using osu.Game.Rulesets.Osu.Edit;
|
||||||
|
using osu.Game.Rulesets.Edit;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu
|
namespace osu.Game.Rulesets.Osu
|
||||||
{
|
{
|
||||||
@ -114,8 +118,14 @@ namespace osu.Game.Rulesets.Osu
|
|||||||
|
|
||||||
public override DifficultyCalculator CreateDifficultyCalculator(Beatmap beatmap, Mod[] mods = null) => new OsuDifficultyCalculator(beatmap, mods);
|
public override DifficultyCalculator CreateDifficultyCalculator(Beatmap beatmap, Mod[] mods = null) => new OsuDifficultyCalculator(beatmap, mods);
|
||||||
|
|
||||||
|
public override PerformanceCalculator CreatePerformanceCalculator(Beatmap beatmap, Score score) => new OsuPerformanceCalculator(this, beatmap, score);
|
||||||
|
|
||||||
|
public override HitObjectComposer CreateHitObjectComposer() => new OsuHitObjectComposer(this);
|
||||||
|
|
||||||
public override string Description => "osu!";
|
public override string Description => "osu!";
|
||||||
|
|
||||||
|
public override string ShortName => "osu";
|
||||||
|
|
||||||
public override SettingsSubsection CreateSettings() => new OsuSettings();
|
public override SettingsSubsection CreateSettings() => new OsuSettings();
|
||||||
|
|
||||||
public override int LegacyID => 0;
|
public override int LegacyID => 0;
|
||||||
|
199
osu.Game.Rulesets.Osu/Scoring/OsuPerformanceCalculator.cs
Normal file
199
osu.Game.Rulesets.Osu/Scoring/OsuPerformanceCalculator.cs
Normal file
@ -0,0 +1,199 @@
|
|||||||
|
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
|
||||||
|
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Rulesets.Mods;
|
||||||
|
using osu.Game.Rulesets.Osu.Beatmaps;
|
||||||
|
using osu.Game.Rulesets.Osu.Mods;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
|
using osu.Game.Rulesets.Scoring;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Scoring
|
||||||
|
{
|
||||||
|
public class OsuPerformanceCalculator : PerformanceCalculator<OsuHitObject>
|
||||||
|
{
|
||||||
|
private readonly int countHitCircles;
|
||||||
|
private readonly int beatmapMaxCombo;
|
||||||
|
|
||||||
|
private Mod[] mods;
|
||||||
|
private double realApproachRate;
|
||||||
|
private double accuracy;
|
||||||
|
private int scoreMaxCombo;
|
||||||
|
private int count300;
|
||||||
|
private int count100;
|
||||||
|
private int count50;
|
||||||
|
private int countMiss;
|
||||||
|
|
||||||
|
public OsuPerformanceCalculator(Ruleset ruleset, Beatmap beatmap, Score score)
|
||||||
|
: base(ruleset, beatmap, score)
|
||||||
|
{
|
||||||
|
countHitCircles = Beatmap.HitObjects.Count(h => h is HitCircle);
|
||||||
|
|
||||||
|
beatmapMaxCombo = Beatmap.HitObjects.Count;
|
||||||
|
beatmapMaxCombo += Beatmap.HitObjects.OfType<Slider>().Sum(s => s.RepeatCount + s.Ticks.Count());
|
||||||
|
}
|
||||||
|
|
||||||
|
public override double Calculate(Dictionary<string, double> categoryRatings = null)
|
||||||
|
{
|
||||||
|
mods = Score.Mods;
|
||||||
|
accuracy = Score.Accuracy;
|
||||||
|
scoreMaxCombo = Score.MaxCombo;
|
||||||
|
count300 = Convert.ToInt32(Score.Statistics["300"]);
|
||||||
|
count100 = Convert.ToInt32(Score.Statistics["100"]);
|
||||||
|
count50 = Convert.ToInt32(Score.Statistics["50"]);
|
||||||
|
countMiss = Convert.ToInt32(Score.Statistics["x"]);
|
||||||
|
|
||||||
|
// Don't count scores made with supposedly unranked mods
|
||||||
|
if (mods.Any(m => !m.Ranked))
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
// Todo: In the future we should apply changes to PreEmpt/AR at an OsuHitObject/BaseDifficulty level, but this is done
|
||||||
|
// locally for now as doing so would modify animations and other things unexpectedly
|
||||||
|
// DO NOT MODIFY THIS
|
||||||
|
double ar = Beatmap.BeatmapInfo.BaseDifficulty.ApproachRate;
|
||||||
|
if (mods.Any(m => m is OsuModHardRock))
|
||||||
|
ar = Math.Min(10, ar * 1.4);
|
||||||
|
if (mods.Any(m => m is OsuModEasy))
|
||||||
|
ar = Math.Max(0, ar / 2);
|
||||||
|
double preEmpt = BeatmapDifficulty.DifficultyRange(ar, 1800, 1200, 450);
|
||||||
|
realApproachRate = preEmpt > 1200 ? (1800 - preEmpt) / 120 : (1200 - preEmpt) / 150 + 5;
|
||||||
|
|
||||||
|
// 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
|
||||||
|
|
||||||
|
if (mods.Any(m => m is OsuModNoFail))
|
||||||
|
multiplier *= 0.90f;
|
||||||
|
|
||||||
|
if (mods.Any(m => m is OsuModSpunOut))
|
||||||
|
multiplier *= 0.95f;
|
||||||
|
|
||||||
|
double aimValue = computeAimValue();
|
||||||
|
double speedValue = computeSpeedValue();
|
||||||
|
double accuracyValue = computeAccuracyValue();
|
||||||
|
double totalValue =
|
||||||
|
Math.Pow(
|
||||||
|
Math.Pow(aimValue, 1.1f) +
|
||||||
|
Math.Pow(speedValue, 1.1f) +
|
||||||
|
Math.Pow(accuracyValue, 1.1f), 1.0f / 1.1f
|
||||||
|
) * multiplier;
|
||||||
|
|
||||||
|
if (categoryRatings != null)
|
||||||
|
{
|
||||||
|
categoryRatings.Add("Aim", aimValue);
|
||||||
|
categoryRatings.Add("Speed", speedValue);
|
||||||
|
categoryRatings.Add("Accuracy", accuracyValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
return totalValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
private double computeAimValue()
|
||||||
|
{
|
||||||
|
double aimValue = Math.Pow(5.0f * Math.Max(1.0f, Attributes["Aim"] / 0.0675f) - 4.0f, 3.0f) / 100000.0f;
|
||||||
|
|
||||||
|
// Longer maps are worth more
|
||||||
|
double lengthBonus = 0.95f + 0.4f * Math.Min(1.0f, totalHits / 2000.0f) +
|
||||||
|
(totalHits > 2000 ? Math.Log10(totalHits / 2000.0f) * 0.5f : 0.0f);
|
||||||
|
|
||||||
|
aimValue *= lengthBonus;
|
||||||
|
|
||||||
|
// Penalize misses exponentially. This mainly fixes tag4 maps and the likes until a per-hitobject solution is available
|
||||||
|
aimValue *= Math.Pow(0.97f, countMiss);
|
||||||
|
|
||||||
|
// Combo scaling
|
||||||
|
if (beatmapMaxCombo > 0)
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
// HD is worth more with lower ar!
|
||||||
|
if (mods.Any(h => h is OsuModHidden))
|
||||||
|
approachRateFactor += 0.02f * (8.0f - realApproachRate);
|
||||||
|
else
|
||||||
|
approachRateFactor += 0.01f * (8.0f - realApproachRate);
|
||||||
|
}
|
||||||
|
|
||||||
|
aimValue *= approachRateFactor;
|
||||||
|
|
||||||
|
if (mods.Any(h => h is OsuModHidden))
|
||||||
|
aimValue *= 1.18f;
|
||||||
|
|
||||||
|
if (mods.Any(h => h is OsuModFlashlight))
|
||||||
|
{
|
||||||
|
// Apply length bonus again if flashlight is on simply because it becomes a lot harder on longer maps.
|
||||||
|
aimValue *= 1.45f * lengthBonus;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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(Beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty, 2) / 2500;
|
||||||
|
|
||||||
|
return aimValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
private double computeSpeedValue()
|
||||||
|
{
|
||||||
|
double speedValue = Math.Pow(5.0f * Math.Max(1.0f, Attributes["Speed"] / 0.0675f) - 4.0f, 3.0f) / 100000.0f;
|
||||||
|
|
||||||
|
// Longer maps are worth more
|
||||||
|
speedValue *= 0.95f + 0.4f * Math.Min(1.0f, totalHits / 2000.0f) +
|
||||||
|
(totalHits > 2000 ? Math.Log10(totalHits / 2000.0f) * 0.5f : 0.0f);
|
||||||
|
|
||||||
|
// Penalize misses exponentially. This mainly fixes tag4 maps and the likes until a per-hitobject solution is available
|
||||||
|
speedValue *= Math.Pow(0.97f, countMiss);
|
||||||
|
|
||||||
|
// Combo scaling
|
||||||
|
if (beatmapMaxCombo > 0)
|
||||||
|
speedValue *= Math.Min(Math.Pow(scoreMaxCombo, 0.8f) / Math.Pow(beatmapMaxCombo, 0.8f), 1.0f);
|
||||||
|
|
||||||
|
// 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(Beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty, 2) / 2500;
|
||||||
|
|
||||||
|
return speedValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
private double computeAccuracyValue()
|
||||||
|
{
|
||||||
|
// This percentage only considers HitCircles of any value - in this part of the calculation we focus on hitting the timing hit window
|
||||||
|
double betterAccuracyPercentage;
|
||||||
|
int amountHitObjectsWithAccuracy = countHitCircles;
|
||||||
|
|
||||||
|
if (amountHitObjectsWithAccuracy > 0)
|
||||||
|
betterAccuracyPercentage = ((count300 - (totalHits - amountHitObjectsWithAccuracy)) * 6 + count100 * 2 + count50) / (amountHitObjectsWithAccuracy * 6);
|
||||||
|
else
|
||||||
|
betterAccuracyPercentage = 0;
|
||||||
|
|
||||||
|
// It is possible to reach a negative accuracy with this formula. Cap it at zero - zero points
|
||||||
|
if (betterAccuracyPercentage < 0)
|
||||||
|
betterAccuracyPercentage = 0;
|
||||||
|
|
||||||
|
// 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, Beatmap.BeatmapInfo.BaseDifficulty.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));
|
||||||
|
|
||||||
|
if (mods.Any(m => m is OsuModHidden))
|
||||||
|
accuracyValue *= 1.02f;
|
||||||
|
if (mods.Any(m => m is OsuModFlashlight))
|
||||||
|
accuracyValue *= 1.02f;
|
||||||
|
|
||||||
|
return accuracyValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
private double totalHits => count300 + count100 + count50 + countMiss;
|
||||||
|
private double totalSuccessfulHits => count300 + count100 + count50;
|
||||||
|
|
||||||
|
protected override BeatmapConverter<OsuHitObject> CreateBeatmapConverter() => new OsuBeatmapConverter();
|
||||||
|
}
|
||||||
|
}
|
16
osu.Game.Rulesets.Osu/Tests/TestCasePerformancePoints.cs
Normal file
16
osu.Game.Rulesets.Osu/Tests/TestCasePerformancePoints.cs
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
|
||||||
|
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||||
|
|
||||||
|
using NUnit.Framework;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Tests
|
||||||
|
{
|
||||||
|
[Ignore("getting CI working")]
|
||||||
|
public class TestCasePerformancePoints : Game.Tests.Visual.TestCasePerformancePoints
|
||||||
|
{
|
||||||
|
public TestCasePerformancePoints()
|
||||||
|
: base(new OsuRuleset(new RulesetInfo()))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -20,8 +20,6 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
|
|||||||
{
|
{
|
||||||
internal class CursorTrail : Drawable
|
internal class CursorTrail : Drawable
|
||||||
{
|
{
|
||||||
public override bool HandleInput => true;
|
|
||||||
|
|
||||||
private int currentIndex;
|
private int currentIndex;
|
||||||
|
|
||||||
private Shader shader;
|
private Shader shader;
|
||||||
|
@ -13,6 +13,7 @@ using System.Linq;
|
|||||||
using osu.Game.Rulesets.Judgements;
|
using osu.Game.Rulesets.Judgements;
|
||||||
using osu.Game.Rulesets.Osu.Judgements;
|
using osu.Game.Rulesets.Osu.Judgements;
|
||||||
using osu.Game.Rulesets.Osu.UI.Cursor;
|
using osu.Game.Rulesets.Osu.UI.Cursor;
|
||||||
|
using osu.Framework.Graphics.Cursor;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.UI
|
namespace osu.Game.Rulesets.Osu.UI
|
||||||
{
|
{
|
||||||
@ -65,7 +66,10 @@ namespace osu.Game.Rulesets.Osu.UI
|
|||||||
protected override void LoadComplete()
|
protected override void LoadComplete()
|
||||||
{
|
{
|
||||||
base.LoadComplete();
|
base.LoadComplete();
|
||||||
AddInternal(new GameplayCursor());
|
|
||||||
|
var cursor = CreateCursor();
|
||||||
|
if (cursor != null)
|
||||||
|
AddInternal(cursor);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Add(DrawableHitObject h)
|
public override void Add(DrawableHitObject h)
|
||||||
@ -102,5 +106,7 @@ namespace osu.Game.Rulesets.Osu.UI
|
|||||||
|
|
||||||
judgementLayer.Add(explosion);
|
judgementLayer.Add(explosion);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected virtual CursorContainer CreateCursor() => new GameplayCursor();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
<Import Project="..\osu.Game.props" />
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||||
@ -22,7 +22,6 @@
|
|||||||
<ErrorReport>prompt</ErrorReport>
|
<ErrorReport>prompt</ErrorReport>
|
||||||
<WarningLevel>4</WarningLevel>
|
<WarningLevel>4</WarningLevel>
|
||||||
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
|
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
|
||||||
<LangVersion>6</LangVersion>
|
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||||
<DebugType>pdbonly</DebugType>
|
<DebugType>pdbonly</DebugType>
|
||||||
@ -49,6 +48,9 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Include="Beatmaps\OsuBeatmapConverter.cs" />
|
<Compile Include="Beatmaps\OsuBeatmapConverter.cs" />
|
||||||
<Compile Include="Beatmaps\OsuBeatmapProcessor.cs" />
|
<Compile Include="Beatmaps\OsuBeatmapProcessor.cs" />
|
||||||
|
<Compile Include="Edit\OsuEditPlayfield.cs" />
|
||||||
|
<Compile Include="Edit\OsuEditRulesetContainer.cs" />
|
||||||
|
<Compile Include="Edit\OsuHitObjectComposer.cs" />
|
||||||
<Compile Include="Objects\Drawables\DrawableOsuHitObject.cs" />
|
<Compile Include="Objects\Drawables\DrawableOsuHitObject.cs" />
|
||||||
<Compile Include="Objects\Drawables\Connections\ConnectionRenderer.cs" />
|
<Compile Include="Objects\Drawables\Connections\ConnectionRenderer.cs" />
|
||||||
<Compile Include="Objects\Drawables\Connections\FollowPointRenderer.cs" />
|
<Compile Include="Objects\Drawables\Connections\FollowPointRenderer.cs" />
|
||||||
@ -85,9 +87,11 @@
|
|||||||
<Compile Include="OsuInputManager.cs" />
|
<Compile Include="OsuInputManager.cs" />
|
||||||
<Compile Include="Replays\OsuReplayInputHandler.cs" />
|
<Compile Include="Replays\OsuReplayInputHandler.cs" />
|
||||||
<Compile Include="Tests\TestCaseHitObjects.cs" />
|
<Compile Include="Tests\TestCaseHitObjects.cs" />
|
||||||
|
<Compile Include="Tests\TestCasePerformancePoints.cs" />
|
||||||
<Compile Include="UI\Cursor\CursorTrail.cs" />
|
<Compile Include="UI\Cursor\CursorTrail.cs" />
|
||||||
<Compile Include="UI\Cursor\GameplayCursor.cs" />
|
<Compile Include="UI\Cursor\GameplayCursor.cs" />
|
||||||
<Compile Include="UI\OsuSettings.cs" />
|
<Compile Include="UI\OsuSettings.cs" />
|
||||||
|
<Compile Include="Scoring\OsuPerformanceCalculator.cs" />
|
||||||
<Compile Include="Scoring\OsuScoreProcessor.cs" />
|
<Compile Include="Scoring\OsuScoreProcessor.cs" />
|
||||||
<Compile Include="UI\OsuRulesetContainer.cs" />
|
<Compile Include="UI\OsuRulesetContainer.cs" />
|
||||||
<Compile Include="UI\OsuPlayfield.cs" />
|
<Compile Include="UI\OsuPlayfield.cs" />
|
||||||
@ -103,9 +107,6 @@
|
|||||||
<Compile Include="Replays\OsuAutoGeneratorBase.cs" />
|
<Compile Include="Replays\OsuAutoGeneratorBase.cs" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<None Include="..\osu.licenseheader">
|
|
||||||
<Link>osu.licenseheader</Link>
|
|
||||||
</None>
|
|
||||||
<None Include="app.config" />
|
<None Include="app.config" />
|
||||||
<None Include="OpenTK.dll.config" />
|
<None Include="OpenTK.dll.config" />
|
||||||
<None Include="packages.config" />
|
<None Include="packages.config" />
|
||||||
@ -122,7 +123,9 @@
|
|||||||
<Private>True</Private>
|
<Private>True</Private>
|
||||||
</ProjectReference>
|
</ProjectReference>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup />
|
<ItemGroup>
|
||||||
|
<Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" />
|
||||||
|
</ItemGroup>
|
||||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||||
Other similar extension points exist, see Microsoft.Common.targets.
|
Other similar extension points exist, see Microsoft.Common.targets.
|
||||||
|
@ -5,7 +5,6 @@ using osu.Game.Beatmaps;
|
|||||||
using osu.Game.Rulesets.Taiko.Beatmaps;
|
using osu.Game.Rulesets.Taiko.Beatmaps;
|
||||||
using osu.Game.Rulesets.Taiko.Objects;
|
using osu.Game.Rulesets.Taiko.Objects;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
|
||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Taiko
|
namespace osu.Game.Rulesets.Taiko
|
||||||
@ -36,7 +35,7 @@ namespace osu.Game.Rulesets.Taiko
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public override double Calculate(Dictionary<string, string> categoryDifficulty = null)
|
public override double Calculate(Dictionary<string, double> categoryDifficulty = null)
|
||||||
{
|
{
|
||||||
// Fill our custom DifficultyHitObject class, that carries additional information
|
// Fill our custom DifficultyHitObject class, that carries additional information
|
||||||
difficultyHitObjects.Clear();
|
difficultyHitObjects.Clear();
|
||||||
@ -53,8 +52,8 @@ namespace osu.Game.Rulesets.Taiko
|
|||||||
|
|
||||||
if (categoryDifficulty != null)
|
if (categoryDifficulty != null)
|
||||||
{
|
{
|
||||||
categoryDifficulty.Add("Strain", starRating.ToString("0.00", CultureInfo.InvariantCulture));
|
categoryDifficulty.Add("Strain", starRating);
|
||||||
categoryDifficulty.Add("Hit window 300", (35 /*HitObjectManager.HitWindow300*/ / TimeRate).ToString("0.00", CultureInfo.InvariantCulture));
|
categoryDifficulty.Add("Hit window 300", 35 /*HitObjectManager.HitWindow300*/ / TimeRate);
|
||||||
}
|
}
|
||||||
|
|
||||||
return starRating;
|
return starRating;
|
||||||
|
@ -95,6 +95,8 @@ namespace osu.Game.Rulesets.Taiko
|
|||||||
|
|
||||||
public override string Description => "osu!taiko";
|
public override string Description => "osu!taiko";
|
||||||
|
|
||||||
|
public override string ShortName => "taiko";
|
||||||
|
|
||||||
public override Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.fa_osu_taiko_o };
|
public override Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.fa_osu_taiko_o };
|
||||||
|
|
||||||
public override DifficultyCalculator CreateDifficultyCalculator(Beatmap beatmap, Mod[] mods = null) => new TaikoDifficultyCalculator(beatmap);
|
public override DifficultyCalculator CreateDifficultyCalculator(Beatmap beatmap, Mod[] mods = null) => new TaikoDifficultyCalculator(beatmap);
|
||||||
|
16
osu.Game.Rulesets.Taiko/Tests/TestCasePerformancePoints.cs
Normal file
16
osu.Game.Rulesets.Taiko/Tests/TestCasePerformancePoints.cs
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
|
||||||
|
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||||
|
|
||||||
|
using NUnit.Framework;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Taiko.Tests
|
||||||
|
{
|
||||||
|
[Ignore("getting CI working")]
|
||||||
|
public class TestCasePerformancePoints : Game.Tests.Visual.TestCasePerformancePoints
|
||||||
|
{
|
||||||
|
public TestCasePerformancePoints()
|
||||||
|
: base(new TaikoRuleset(new RulesetInfo()))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
<Import Project="..\osu.Game.props" />
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||||
@ -21,7 +21,6 @@
|
|||||||
<ErrorReport>prompt</ErrorReport>
|
<ErrorReport>prompt</ErrorReport>
|
||||||
<WarningLevel>4</WarningLevel>
|
<WarningLevel>4</WarningLevel>
|
||||||
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
|
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
|
||||||
<LangVersion>6</LangVersion>
|
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||||
<DebugType>pdbonly</DebugType>
|
<DebugType>pdbonly</DebugType>
|
||||||
@ -83,6 +82,7 @@
|
|||||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||||
<Compile Include="Scoring\TaikoScoreProcessor.cs" />
|
<Compile Include="Scoring\TaikoScoreProcessor.cs" />
|
||||||
<Compile Include="TaikoInputManager.cs" />
|
<Compile Include="TaikoInputManager.cs" />
|
||||||
|
<Compile Include="Tests\TestCasePerformancePoints.cs" />
|
||||||
<Compile Include="Tests\TestCaseTaikoPlayfield.cs" />
|
<Compile Include="Tests\TestCaseTaikoPlayfield.cs" />
|
||||||
<Compile Include="UI\HitTarget.cs" />
|
<Compile Include="UI\HitTarget.cs" />
|
||||||
<Compile Include="UI\InputDrum.cs" />
|
<Compile Include="UI\InputDrum.cs" />
|
||||||
@ -95,9 +95,6 @@
|
|||||||
<Compile Include="Mods\TaikoMod.cs" />
|
<Compile Include="Mods\TaikoMod.cs" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<None Include="..\osu.licenseheader">
|
|
||||||
<Link>osu.licenseheader</Link>
|
|
||||||
</None>
|
|
||||||
<None Include="app.config" />
|
<None Include="app.config" />
|
||||||
<None Include="OpenTK.dll.config" />
|
<None Include="OpenTK.dll.config" />
|
||||||
<None Include="packages.config" />
|
<None Include="packages.config" />
|
||||||
@ -114,6 +111,9 @@
|
|||||||
<Private>True</Private>
|
<Private>True</Private>
|
||||||
</ProjectReference>
|
</ProjectReference>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" />
|
||||||
|
</ItemGroup>
|
||||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||||
Other similar extension points exist, see Microsoft.Common.targets.
|
Other similar extension points exist, see Microsoft.Common.targets.
|
||||||
|
214
osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs
Normal file
214
osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs
Normal file
@ -0,0 +1,214 @@
|
|||||||
|
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
|
||||||
|
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||||
|
|
||||||
|
using System.IO;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using OpenTK;
|
||||||
|
using OpenTK.Graphics;
|
||||||
|
using osu.Game.Tests.Resources;
|
||||||
|
using System.Linq;
|
||||||
|
using osu.Game.Audio;
|
||||||
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
|
using osu.Game.Beatmaps.Formats;
|
||||||
|
using osu.Game.Beatmaps.Timing;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Beatmaps.Formats
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class LegacyBeatmapDecoderTest
|
||||||
|
{
|
||||||
|
[Test]
|
||||||
|
public void TestDecodeBeatmapGeneral()
|
||||||
|
{
|
||||||
|
var decoder = new LegacyBeatmapDecoder();
|
||||||
|
using (var resStream = Resource.OpenResource("Soleily - Renatus (Gamu) [Insane].osu"))
|
||||||
|
using (var stream = new StreamReader(resStream))
|
||||||
|
{
|
||||||
|
var beatmap = decoder.DecodeBeatmap(stream);
|
||||||
|
var beatmapInfo = beatmap.BeatmapInfo;
|
||||||
|
var metadata = beatmap.Metadata;
|
||||||
|
|
||||||
|
Assert.AreEqual("03. Renatus - Soleily 192kbps.mp3", metadata.AudioFile);
|
||||||
|
Assert.AreEqual(0, beatmapInfo.AudioLeadIn);
|
||||||
|
Assert.AreEqual(164471, metadata.PreviewTime);
|
||||||
|
Assert.IsFalse(beatmapInfo.Countdown);
|
||||||
|
Assert.AreEqual(0.7f, beatmapInfo.StackLeniency);
|
||||||
|
Assert.IsTrue(beatmapInfo.RulesetID == 0);
|
||||||
|
Assert.IsFalse(beatmapInfo.LetterboxInBreaks);
|
||||||
|
Assert.IsFalse(beatmapInfo.SpecialStyle);
|
||||||
|
Assert.IsFalse(beatmapInfo.WidescreenStoryboard);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestDecodeBeatmapEditor()
|
||||||
|
{
|
||||||
|
var decoder = new LegacyBeatmapDecoder();
|
||||||
|
using (var resStream = Resource.OpenResource("Soleily - Renatus (Gamu) [Insane].osu"))
|
||||||
|
using (var stream = new StreamReader(resStream))
|
||||||
|
{
|
||||||
|
var beatmapInfo = decoder.DecodeBeatmap(stream).BeatmapInfo;
|
||||||
|
|
||||||
|
int[] expectedBookmarks =
|
||||||
|
{
|
||||||
|
11505, 22054, 32604, 43153, 53703, 64252, 74802, 85351,
|
||||||
|
95901, 106450, 116999, 119637, 130186, 140735, 151285,
|
||||||
|
161834, 164471, 175020, 185570, 196119, 206669, 209306
|
||||||
|
};
|
||||||
|
Assert.AreEqual(expectedBookmarks.Length, beatmapInfo.Bookmarks.Length);
|
||||||
|
for (int i = 0; i < expectedBookmarks.Length; i++)
|
||||||
|
Assert.AreEqual(expectedBookmarks[i], beatmapInfo.Bookmarks[i]);
|
||||||
|
Assert.AreEqual(1.8, beatmapInfo.DistanceSpacing);
|
||||||
|
Assert.AreEqual(4, beatmapInfo.BeatDivisor);
|
||||||
|
Assert.AreEqual(4, beatmapInfo.GridSize);
|
||||||
|
Assert.AreEqual(2, beatmapInfo.TimelineZoom);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestDecodeBeatmapMetadata()
|
||||||
|
{
|
||||||
|
var decoder = new LegacyBeatmapDecoder();
|
||||||
|
using (var resStream = Resource.OpenResource("Soleily - Renatus (Gamu) [Insane].osu"))
|
||||||
|
using (var stream = new StreamReader(resStream))
|
||||||
|
{
|
||||||
|
var beatmap = decoder.DecodeBeatmap(stream);
|
||||||
|
var beatmapInfo = beatmap.BeatmapInfo;
|
||||||
|
var metadata = beatmap.Metadata;
|
||||||
|
|
||||||
|
Assert.AreEqual("Renatus", metadata.Title);
|
||||||
|
Assert.AreEqual("Renatus", metadata.TitleUnicode);
|
||||||
|
Assert.AreEqual("Soleily", metadata.Artist);
|
||||||
|
Assert.AreEqual("Soleily", metadata.ArtistUnicode);
|
||||||
|
Assert.AreEqual("Gamu", metadata.AuthorString);
|
||||||
|
Assert.AreEqual("Insane", beatmapInfo.Version);
|
||||||
|
Assert.AreEqual(string.Empty, metadata.Source);
|
||||||
|
Assert.AreEqual("MBC7 Unisphere 地球ヤバイEP Chikyu Yabai", metadata.Tags);
|
||||||
|
Assert.AreEqual(557821, beatmapInfo.OnlineBeatmapID);
|
||||||
|
Assert.AreEqual(241526, metadata.OnlineBeatmapSetID);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestDecodeBeatmapDifficulty()
|
||||||
|
{
|
||||||
|
var decoder = new LegacyBeatmapDecoder();
|
||||||
|
using (var resStream = Resource.OpenResource("Soleily - Renatus (Gamu) [Insane].osu"))
|
||||||
|
using (var stream = new StreamReader(resStream))
|
||||||
|
{
|
||||||
|
var difficulty = decoder.DecodeBeatmap(stream).BeatmapInfo.BaseDifficulty;
|
||||||
|
|
||||||
|
Assert.AreEqual(6.5f, difficulty.DrainRate);
|
||||||
|
Assert.AreEqual(4, difficulty.CircleSize);
|
||||||
|
Assert.AreEqual(8, difficulty.OverallDifficulty);
|
||||||
|
Assert.AreEqual(9, difficulty.ApproachRate);
|
||||||
|
Assert.AreEqual(1.8f, difficulty.SliderMultiplier);
|
||||||
|
Assert.AreEqual(2, difficulty.SliderTickRate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestDecodeBeatmapEvents()
|
||||||
|
{
|
||||||
|
var decoder = new LegacyBeatmapDecoder();
|
||||||
|
using (var resStream = Resource.OpenResource("Soleily - Renatus (Gamu) [Insane].osu"))
|
||||||
|
using (var stream = new StreamReader(resStream))
|
||||||
|
{
|
||||||
|
var beatmap = decoder.DecodeBeatmap(stream);
|
||||||
|
var metadata = beatmap.Metadata;
|
||||||
|
var breakPoint = beatmap.Breaks[0];
|
||||||
|
|
||||||
|
Assert.AreEqual("machinetop_background.jpg", metadata.BackgroundFile);
|
||||||
|
Assert.AreEqual(122474, breakPoint.StartTime);
|
||||||
|
Assert.AreEqual(140135, breakPoint.EndTime);
|
||||||
|
Assert.IsTrue(breakPoint.HasEffect);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestDecodeBeatmapTimingPoints()
|
||||||
|
{
|
||||||
|
var decoder = new LegacyBeatmapDecoder();
|
||||||
|
using (var resStream = Resource.OpenResource("Soleily - Renatus (Gamu) [Insane].osu"))
|
||||||
|
using (var stream = new StreamReader(resStream))
|
||||||
|
{
|
||||||
|
var beatmap = decoder.DecodeBeatmap(stream);
|
||||||
|
var controlPoints = beatmap.ControlPointInfo;
|
||||||
|
|
||||||
|
Assert.AreEqual(4, controlPoints.TimingPoints.Count);
|
||||||
|
var timingPoint = controlPoints.TimingPoints[0];
|
||||||
|
Assert.AreEqual(956, timingPoint.Time);
|
||||||
|
Assert.AreEqual(329.67032967033d, timingPoint.BeatLength);
|
||||||
|
Assert.AreEqual(TimeSignatures.SimpleQuadruple, timingPoint.TimeSignature);
|
||||||
|
|
||||||
|
Assert.AreEqual(5, controlPoints.DifficultyPoints.Count);
|
||||||
|
var difficultyPoint = controlPoints.DifficultyPoints[0];
|
||||||
|
Assert.AreEqual(116999, difficultyPoint.Time);
|
||||||
|
Assert.AreEqual(0.75000000000000189d, difficultyPoint.SpeedMultiplier);
|
||||||
|
|
||||||
|
Assert.AreEqual(34, controlPoints.SoundPoints.Count);
|
||||||
|
var soundPoint = controlPoints.SoundPoints[0];
|
||||||
|
Assert.AreEqual(956, soundPoint.Time);
|
||||||
|
Assert.AreEqual("soft", soundPoint.SampleBank);
|
||||||
|
Assert.AreEqual(60, soundPoint.SampleVolume);
|
||||||
|
|
||||||
|
Assert.AreEqual(8, controlPoints.EffectPoints.Count);
|
||||||
|
var effectPoint = controlPoints.EffectPoints[0];
|
||||||
|
Assert.AreEqual(53703, effectPoint.Time);
|
||||||
|
Assert.IsTrue(effectPoint.KiaiMode);
|
||||||
|
Assert.IsFalse(effectPoint.OmitFirstBarLine);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestDecodeBeatmapColors()
|
||||||
|
{
|
||||||
|
var decoder = new LegacyBeatmapDecoder();
|
||||||
|
using (var resStream = Resource.OpenResource("Soleily - Renatus (Gamu) [Insane].osu"))
|
||||||
|
using (var stream = new StreamReader(resStream))
|
||||||
|
{
|
||||||
|
var comboColors = decoder.DecodeBeatmap(stream).ComboColors;
|
||||||
|
|
||||||
|
Color4[] expectedColors =
|
||||||
|
{
|
||||||
|
new Color4(142, 199, 255, 255),
|
||||||
|
new Color4(255, 128, 128, 255),
|
||||||
|
new Color4(128, 255, 255, 255),
|
||||||
|
new Color4(128, 255, 128, 255),
|
||||||
|
new Color4(255, 187, 255, 255),
|
||||||
|
new Color4(255, 177, 140, 255),
|
||||||
|
};
|
||||||
|
Assert.AreEqual(expectedColors.Length, comboColors.Count);
|
||||||
|
for (int i = 0; i < expectedColors.Length; i++)
|
||||||
|
Assert.AreEqual(expectedColors[i], comboColors[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestDecodeBeatmapHitObjects()
|
||||||
|
{
|
||||||
|
var decoder = new LegacyBeatmapDecoder();
|
||||||
|
using (var resStream = Resource.OpenResource("Soleily - Renatus (Gamu) [Insane].osu"))
|
||||||
|
using (var stream = new StreamReader(resStream))
|
||||||
|
{
|
||||||
|
var hitObjects = decoder.DecodeBeatmap(stream).HitObjects;
|
||||||
|
|
||||||
|
var curveData = hitObjects[0] as IHasCurve;
|
||||||
|
var positionData = hitObjects[0] as IHasPosition;
|
||||||
|
|
||||||
|
Assert.IsNotNull(positionData);
|
||||||
|
Assert.IsNotNull(curveData);
|
||||||
|
Assert.AreEqual(new Vector2(192, 168), positionData.Position);
|
||||||
|
Assert.AreEqual(956, hitObjects[0].StartTime);
|
||||||
|
Assert.IsTrue(hitObjects[0].Samples.Any(s => s.Name == SampleInfo.HIT_NORMAL));
|
||||||
|
|
||||||
|
positionData = hitObjects[1] as IHasPosition;
|
||||||
|
|
||||||
|
Assert.IsNotNull(positionData);
|
||||||
|
Assert.AreEqual(new Vector2(304, 56), positionData.Position);
|
||||||
|
Assert.AreEqual(1285, hitObjects[1].StartTime);
|
||||||
|
Assert.IsTrue(hitObjects[1].Samples.Any(s => s.Name == SampleInfo.HIT_CLAP));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,90 @@
|
|||||||
|
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
|
||||||
|
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||||
|
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using OpenTK;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Game.Beatmaps.Formats;
|
||||||
|
using osu.Game.Storyboards;
|
||||||
|
using osu.Game.Tests.Resources;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Beatmaps.Formats
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class LegacyStoryboardDecoderTest
|
||||||
|
{
|
||||||
|
[Test]
|
||||||
|
public void TestDecodeStoryboardEvents()
|
||||||
|
{
|
||||||
|
var decoder = new LegacyBeatmapDecoder();
|
||||||
|
using (var resStream = Resource.OpenResource("Himeringo - Yotsuya-san ni Yoroshiku (RLC) [Winber1's Extreme].osu"))
|
||||||
|
using (var stream = new StreamReader(resStream))
|
||||||
|
{
|
||||||
|
var storyboard = decoder.GetStoryboardDecoder().DecodeStoryboard(stream);
|
||||||
|
|
||||||
|
Assert.IsTrue(storyboard.HasDrawable);
|
||||||
|
Assert.AreEqual(4, storyboard.Layers.Count());
|
||||||
|
|
||||||
|
StoryboardLayer background = storyboard.Layers.FirstOrDefault(l => l.Depth == 3);
|
||||||
|
Assert.IsNotNull(background);
|
||||||
|
Assert.AreEqual(16, background.Elements.Count());
|
||||||
|
Assert.IsTrue(background.EnabledWhenFailing);
|
||||||
|
Assert.IsTrue(background.EnabledWhenPassing);
|
||||||
|
Assert.AreEqual("Background", background.Name);
|
||||||
|
|
||||||
|
StoryboardLayer fail = storyboard.Layers.FirstOrDefault(l => l.Depth == 2);
|
||||||
|
Assert.IsNotNull(fail);
|
||||||
|
Assert.AreEqual(0, fail.Elements.Count());
|
||||||
|
Assert.IsTrue(fail.EnabledWhenFailing);
|
||||||
|
Assert.IsFalse(fail.EnabledWhenPassing);
|
||||||
|
Assert.AreEqual("Fail", fail.Name);
|
||||||
|
|
||||||
|
StoryboardLayer pass = storyboard.Layers.FirstOrDefault(l => l.Depth == 1);
|
||||||
|
Assert.IsNotNull(pass);
|
||||||
|
Assert.AreEqual(0, pass.Elements.Count());
|
||||||
|
Assert.IsFalse(pass.EnabledWhenFailing);
|
||||||
|
Assert.IsTrue(pass.EnabledWhenPassing);
|
||||||
|
Assert.AreEqual("Pass", pass.Name);
|
||||||
|
|
||||||
|
StoryboardLayer foreground = storyboard.Layers.FirstOrDefault(l => l.Depth == 0);
|
||||||
|
Assert.IsNotNull(foreground);
|
||||||
|
Assert.AreEqual(151, foreground.Elements.Count());
|
||||||
|
Assert.IsTrue(foreground.EnabledWhenFailing);
|
||||||
|
Assert.IsTrue(foreground.EnabledWhenPassing);
|
||||||
|
Assert.AreEqual("Foreground", foreground.Name);
|
||||||
|
|
||||||
|
int spriteCount = background.Elements.Count(x => x.GetType() == typeof(StoryboardSprite));
|
||||||
|
int animationCount = background.Elements.Count(x => x.GetType() == typeof(StoryboardAnimation));
|
||||||
|
int sampleCount = background.Elements.Count(x => x.GetType() == typeof(StoryboardSample));
|
||||||
|
|
||||||
|
Assert.AreEqual(15, spriteCount);
|
||||||
|
Assert.AreEqual(1, animationCount);
|
||||||
|
Assert.AreEqual(0, sampleCount);
|
||||||
|
Assert.AreEqual(background.Elements.Count(), spriteCount + animationCount + sampleCount);
|
||||||
|
|
||||||
|
var sprite = background.Elements.ElementAt(0) as StoryboardSprite;
|
||||||
|
Assert.NotNull(sprite);
|
||||||
|
Assert.IsTrue(sprite.HasCommands);
|
||||||
|
Assert.AreEqual(new Vector2(320, 240), sprite.InitialPosition);
|
||||||
|
Assert.IsTrue(sprite.IsDrawable);
|
||||||
|
Assert.AreEqual(Anchor.Centre, sprite.Origin);
|
||||||
|
Assert.AreEqual("SB/lyric/ja-21.png", sprite.Path);
|
||||||
|
|
||||||
|
var animation = background.Elements.ElementAt(12) as StoryboardAnimation;
|
||||||
|
Assert.NotNull(animation);
|
||||||
|
Assert.AreEqual(141175, animation.EndTime);
|
||||||
|
Assert.AreEqual(10, animation.FrameCount);
|
||||||
|
Assert.AreEqual(30, animation.FrameDelay);
|
||||||
|
Assert.IsTrue(animation.HasCommands);
|
||||||
|
Assert.AreEqual(new Vector2(320, 240), animation.InitialPosition);
|
||||||
|
Assert.IsTrue(animation.IsDrawable);
|
||||||
|
Assert.AreEqual(AnimationLoopType.LoopForever, animation.LoopType);
|
||||||
|
Assert.AreEqual(Anchor.Centre, animation.Origin);
|
||||||
|
Assert.AreEqual("SB/red jitter/red_0000.jpg", animation.Path);
|
||||||
|
Assert.AreEqual(78993, animation.StartTime);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,146 +0,0 @@
|
|||||||
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
|
|
||||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
|
||||||
|
|
||||||
using System.IO;
|
|
||||||
using NUnit.Framework;
|
|
||||||
using OpenTK;
|
|
||||||
using OpenTK.Graphics;
|
|
||||||
using osu.Game.Beatmaps.Formats;
|
|
||||||
using osu.Game.Tests.Resources;
|
|
||||||
using System.Linq;
|
|
||||||
using osu.Game.Audio;
|
|
||||||
using osu.Game.Rulesets.Objects.Types;
|
|
||||||
|
|
||||||
namespace osu.Game.Tests.Beatmaps.Formats
|
|
||||||
{
|
|
||||||
[TestFixture]
|
|
||||||
public class OsuLegacyDecoderTest
|
|
||||||
{
|
|
||||||
[Test]
|
|
||||||
public void TestDecodeMetadata()
|
|
||||||
{
|
|
||||||
var decoder = new OsuLegacyDecoder();
|
|
||||||
using (var stream = Resource.OpenResource("Soleily - Renatus (Gamu) [Insane].osu"))
|
|
||||||
{
|
|
||||||
var beatmap = decoder.Decode(new StreamReader(stream));
|
|
||||||
var meta = beatmap.BeatmapInfo.Metadata;
|
|
||||||
Assert.AreEqual(241526, meta.OnlineBeatmapSetID);
|
|
||||||
Assert.AreEqual("Soleily", meta.Artist);
|
|
||||||
Assert.AreEqual("Soleily", meta.ArtistUnicode);
|
|
||||||
Assert.AreEqual("03. Renatus - Soleily 192kbps.mp3", meta.AudioFile);
|
|
||||||
Assert.AreEqual("Gamu", meta.AuthorString);
|
|
||||||
Assert.AreEqual("machinetop_background.jpg", meta.BackgroundFile);
|
|
||||||
Assert.AreEqual(164471, meta.PreviewTime);
|
|
||||||
Assert.AreEqual(string.Empty, meta.Source);
|
|
||||||
Assert.AreEqual("MBC7 Unisphere 地球ヤバイEP Chikyu Yabai", meta.Tags);
|
|
||||||
Assert.AreEqual("Renatus", meta.Title);
|
|
||||||
Assert.AreEqual("Renatus", meta.TitleUnicode);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void TestDecodeGeneral()
|
|
||||||
{
|
|
||||||
var decoder = new OsuLegacyDecoder();
|
|
||||||
using (var stream = Resource.OpenResource("Soleily - Renatus (Gamu) [Insane].osu"))
|
|
||||||
{
|
|
||||||
var beatmapInfo = decoder.Decode(new StreamReader(stream)).BeatmapInfo;
|
|
||||||
Assert.AreEqual(0, beatmapInfo.AudioLeadIn);
|
|
||||||
Assert.AreEqual(false, beatmapInfo.Countdown);
|
|
||||||
Assert.AreEqual(0.7f, beatmapInfo.StackLeniency);
|
|
||||||
Assert.AreEqual(false, beatmapInfo.SpecialStyle);
|
|
||||||
Assert.IsTrue(beatmapInfo.RulesetID == 0);
|
|
||||||
Assert.AreEqual(false, beatmapInfo.LetterboxInBreaks);
|
|
||||||
Assert.AreEqual(false, beatmapInfo.WidescreenStoryboard);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void TestDecodeEditor()
|
|
||||||
{
|
|
||||||
var decoder = new OsuLegacyDecoder();
|
|
||||||
using (var stream = Resource.OpenResource("Soleily - Renatus (Gamu) [Insane].osu"))
|
|
||||||
{
|
|
||||||
var beatmap = decoder.Decode(new StreamReader(stream)).BeatmapInfo;
|
|
||||||
int[] expectedBookmarks =
|
|
||||||
{
|
|
||||||
11505, 22054, 32604, 43153, 53703, 64252, 74802, 85351,
|
|
||||||
95901, 106450, 116999, 119637, 130186, 140735, 151285,
|
|
||||||
161834, 164471, 175020, 185570, 196119, 206669, 209306
|
|
||||||
};
|
|
||||||
Assert.AreEqual(expectedBookmarks.Length, beatmap.Bookmarks.Length);
|
|
||||||
for (int i = 0; i < expectedBookmarks.Length; i++)
|
|
||||||
Assert.AreEqual(expectedBookmarks[i], beatmap.Bookmarks[i]);
|
|
||||||
Assert.AreEqual(1.8, beatmap.DistanceSpacing);
|
|
||||||
Assert.AreEqual(4, beatmap.BeatDivisor);
|
|
||||||
Assert.AreEqual(4, beatmap.GridSize);
|
|
||||||
Assert.AreEqual(2, beatmap.TimelineZoom);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void TestDecodeDifficulty()
|
|
||||||
{
|
|
||||||
var decoder = new OsuLegacyDecoder();
|
|
||||||
using (var stream = Resource.OpenResource("Soleily - Renatus (Gamu) [Insane].osu"))
|
|
||||||
{
|
|
||||||
var beatmap = decoder.Decode(new StreamReader(stream));
|
|
||||||
var difficulty = beatmap.BeatmapInfo.BaseDifficulty;
|
|
||||||
Assert.AreEqual(6.5f, difficulty.DrainRate);
|
|
||||||
Assert.AreEqual(4, difficulty.CircleSize);
|
|
||||||
Assert.AreEqual(8, difficulty.OverallDifficulty);
|
|
||||||
Assert.AreEqual(9, difficulty.ApproachRate);
|
|
||||||
Assert.AreEqual(1.8f, difficulty.SliderMultiplier);
|
|
||||||
Assert.AreEqual(2, difficulty.SliderTickRate);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void TestDecodeColors()
|
|
||||||
{
|
|
||||||
var decoder = new OsuLegacyDecoder();
|
|
||||||
using (var stream = Resource.OpenResource("Soleily - Renatus (Gamu) [Insane].osu"))
|
|
||||||
{
|
|
||||||
var beatmap = decoder.Decode(new StreamReader(stream));
|
|
||||||
Color4[] expected =
|
|
||||||
{
|
|
||||||
new Color4(142, 199, 255, 255),
|
|
||||||
new Color4(255, 128, 128, 255),
|
|
||||||
new Color4(128, 255, 255, 255),
|
|
||||||
new Color4(128, 255, 128, 255),
|
|
||||||
new Color4(255, 187, 255, 255),
|
|
||||||
new Color4(255, 177, 140, 255),
|
|
||||||
};
|
|
||||||
Assert.AreEqual(expected.Length, beatmap.ComboColors.Count);
|
|
||||||
for (int i = 0; i < expected.Length; i++)
|
|
||||||
Assert.AreEqual(expected[i], beatmap.ComboColors[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void TestDecodeHitObjects()
|
|
||||||
{
|
|
||||||
var decoder = new OsuLegacyDecoder();
|
|
||||||
using (var stream = Resource.OpenResource("Soleily - Renatus (Gamu) [Insane].osu"))
|
|
||||||
{
|
|
||||||
var beatmap = decoder.Decode(new StreamReader(stream));
|
|
||||||
|
|
||||||
var curveData = beatmap.HitObjects[0] as IHasCurve;
|
|
||||||
var positionData = beatmap.HitObjects[0] as IHasPosition;
|
|
||||||
|
|
||||||
Assert.IsNotNull(positionData);
|
|
||||||
Assert.IsNotNull(curveData);
|
|
||||||
Assert.AreEqual(new Vector2(192, 168), positionData.Position);
|
|
||||||
Assert.AreEqual(956, beatmap.HitObjects[0].StartTime);
|
|
||||||
Assert.IsTrue(beatmap.HitObjects[0].Samples.Any(s => s.Name == SampleInfo.HIT_NORMAL));
|
|
||||||
|
|
||||||
positionData = beatmap.HitObjects[1] as IHasPosition;
|
|
||||||
|
|
||||||
Assert.IsNotNull(positionData);
|
|
||||||
Assert.AreEqual(new Vector2(304, 56), positionData.Position);
|
|
||||||
Assert.AreEqual(1285, beatmap.HitObjects[1].StartTime);
|
|
||||||
Assert.IsTrue(beatmap.HitObjects[1].Samples.Any(s => s.Name == SampleInfo.HIT_CLAP));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -50,7 +50,7 @@ namespace osu.Game.Tests.Beatmaps.IO
|
|||||||
|
|
||||||
BeatmapMetadata meta;
|
BeatmapMetadata meta;
|
||||||
using (var stream = new StreamReader(reader.GetStream("Soleily - Renatus (Deif) [Platter].osu")))
|
using (var stream = new StreamReader(reader.GetStream("Soleily - Renatus (Deif) [Platter].osu")))
|
||||||
meta = BeatmapDecoder.GetDecoder(stream).Decode(stream).Metadata;
|
meta = Decoder.GetDecoder(stream).DecodeBeatmap(stream).Metadata;
|
||||||
|
|
||||||
Assert.AreEqual(241526, meta.OnlineBeatmapSetID);
|
Assert.AreEqual(241526, meta.OnlineBeatmapSetID);
|
||||||
Assert.AreEqual("Soleily", meta.Artist);
|
Assert.AreEqual("Soleily", meta.Artist);
|
||||||
|
File diff suppressed because it is too large
Load Diff
46
osu.Game.Tests/Visual/TestCaseEditorCompose.cs
Normal file
46
osu.Game.Tests/Visual/TestCaseEditorCompose.cs
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
|
||||||
|
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Screens.Edit.Screens.Compose;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Visual
|
||||||
|
{
|
||||||
|
public class TestCaseEditorCompose : OsuTestCase
|
||||||
|
{
|
||||||
|
private readonly Random random;
|
||||||
|
private readonly Compose compose;
|
||||||
|
|
||||||
|
public TestCaseEditorCompose()
|
||||||
|
{
|
||||||
|
random = new Random(1337);
|
||||||
|
|
||||||
|
Add(compose = new Compose());
|
||||||
|
AddStep("Next beatmap", nextBeatmap);
|
||||||
|
}
|
||||||
|
|
||||||
|
private OsuGameBase osuGame;
|
||||||
|
private BeatmapManager beatmaps;
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(OsuGameBase osuGame, BeatmapManager beatmaps)
|
||||||
|
{
|
||||||
|
this.osuGame = osuGame;
|
||||||
|
this.beatmaps = beatmaps;
|
||||||
|
|
||||||
|
compose.Beatmap.BindTo(osuGame.Beatmap);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void nextBeatmap()
|
||||||
|
{
|
||||||
|
var sets = beatmaps.GetAllUsableBeatmapSets();
|
||||||
|
if (sets.Count == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var b = sets[random.Next(0, sets.Count)].Beatmaps[0];
|
||||||
|
osuGame.Beatmap.Value = beatmaps.GetWorkingBeatmap(b);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
41
osu.Game.Tests/Visual/TestCaseEditorComposeRadioButtons.cs
Normal file
41
osu.Game.Tests/Visual/TestCaseEditorComposeRadioButtons.cs
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
|
||||||
|
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Game.Screens.Edit.Screens.Compose.RadioButtons;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Visual
|
||||||
|
{
|
||||||
|
public class TestCaseEditorComposeRadioButtons : OsuTestCase
|
||||||
|
{
|
||||||
|
public override IReadOnlyList<Type> RequiredTypes => new[] { typeof(DrawableRadioButton) };
|
||||||
|
|
||||||
|
public TestCaseEditorComposeRadioButtons()
|
||||||
|
{
|
||||||
|
RadioButtonCollection collection;
|
||||||
|
Add(collection = new RadioButtonCollection
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Width = 150,
|
||||||
|
Items = new[]
|
||||||
|
{
|
||||||
|
new RadioButton("Item 1", () => { }),
|
||||||
|
new RadioButton("Item 2", () => { }),
|
||||||
|
new RadioButton("Item 3", () => { }),
|
||||||
|
new RadioButton("Item 4", () => { }),
|
||||||
|
new RadioButton("Item 5", () => { })
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
for (int i = 0; i < collection.Items.Count; i++)
|
||||||
|
{
|
||||||
|
int l = i;
|
||||||
|
AddStep($"Select item {l + 1}", () => collection.Items[l].Select());
|
||||||
|
AddStep($"Deselect item {l + 1}", () => collection.Items[l].Deselect());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
47
osu.Game.Tests/Visual/TestCaseHistoricalSection.cs
Normal file
47
osu.Game.Tests/Visual/TestCaseHistoricalSection.cs
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
|
||||||
|
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Game.Graphics;
|
||||||
|
using osu.Game.Overlays.Profile.Sections;
|
||||||
|
using osu.Game.Overlays.Profile.Sections.Historical;
|
||||||
|
using osu.Game.Users;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Visual
|
||||||
|
{
|
||||||
|
internal class TestCaseHistoricalSection : OsuTestCase
|
||||||
|
{
|
||||||
|
public override IReadOnlyList<Type> RequiredTypes =>
|
||||||
|
new[]
|
||||||
|
{
|
||||||
|
typeof(HistoricalSection),
|
||||||
|
typeof(PaginatedMostPlayedBeatmapContainer),
|
||||||
|
typeof(DrawableMostPlayedRow),
|
||||||
|
typeof(DrawableProfileRow)
|
||||||
|
};
|
||||||
|
|
||||||
|
public TestCaseHistoricalSection()
|
||||||
|
{
|
||||||
|
HistoricalSection section;
|
||||||
|
|
||||||
|
Add(new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Colour = OsuColour.Gray(0.2f)
|
||||||
|
});
|
||||||
|
|
||||||
|
Add(new ScrollContainer
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Child = section = new HistoricalSection(),
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("Show peppy", () => section.User.Value = new User { Id = 2 });
|
||||||
|
AddStep("Show WubWoofWolf", () => section.User.Value = new User { Id = 39828 });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -22,7 +22,7 @@ namespace osu.Game.Tests.Visual
|
|||||||
|
|
||||||
Add(container = new ExampleContainer());
|
Add(container = new ExampleContainer());
|
||||||
|
|
||||||
AddStep(@"Add button", () => container.Add(new OsuButton
|
AddStep(@"Add button", () => container.Add(new TriangleButton
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
Text = @"Button",
|
Text = @"Button",
|
||||||
|
@ -79,7 +79,7 @@ namespace osu.Game.Tests.Visual
|
|||||||
var decoupledClock = new DecoupleableInterpolatingFramedClock { IsCoupled = true };
|
var decoupledClock = new DecoupleableInterpolatingFramedClock { IsCoupled = true };
|
||||||
storyboardContainer.Clock = decoupledClock;
|
storyboardContainer.Clock = decoupledClock;
|
||||||
|
|
||||||
storyboard = working.Beatmap.Storyboard.CreateDrawable(beatmapBacking);
|
storyboard = working.Storyboard.CreateDrawable(beatmapBacking);
|
||||||
storyboard.Passing = false;
|
storyboard.Passing = false;
|
||||||
|
|
||||||
storyboardContainer.Add(storyboard);
|
storyboardContainer.Add(storyboard);
|
||||||
|
@ -15,7 +15,7 @@ namespace osu.Game.Tests.Visual
|
|||||||
{
|
{
|
||||||
internal class TestCaseUserRanks : OsuTestCase
|
internal class TestCaseUserRanks : OsuTestCase
|
||||||
{
|
{
|
||||||
public override IReadOnlyList<Type> RequiredTypes => new[] { typeof(DrawableScore), typeof(RanksSection) };
|
public override IReadOnlyList<Type> RequiredTypes => new[] { typeof(DrawableProfileScore), typeof(RanksSection) };
|
||||||
|
|
||||||
public TestCaseUserRanks()
|
public TestCaseUserRanks()
|
||||||
{
|
{
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
<Project DefaultTargets="Build" ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
|
<Import Project="..\osu.Game.props" />
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||||
@ -19,7 +20,6 @@
|
|||||||
<WarningLevel>4</WarningLevel>
|
<WarningLevel>4</WarningLevel>
|
||||||
<ConsolePause>false</ConsolePause>
|
<ConsolePause>false</ConsolePause>
|
||||||
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
|
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
|
||||||
<LangVersion>6</LangVersion>
|
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||||
<Optimize>true</Optimize>
|
<Optimize>true</Optimize>
|
||||||
@ -45,9 +45,6 @@
|
|||||||
</Reference>
|
</Reference>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<None Include="..\osu.licenseheader">
|
|
||||||
<Link>osu.licenseheader</Link>
|
|
||||||
</None>
|
|
||||||
<None Include="app.config" />
|
<None Include="app.config" />
|
||||||
<None Include="packages.config" />
|
<None Include="packages.config" />
|
||||||
<None Include="OpenTK.dll.config" />
|
<None Include="OpenTK.dll.config" />
|
||||||
@ -86,10 +83,11 @@
|
|||||||
<Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" />
|
<Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<Compile Include="Beatmaps\Formats\LegacyStoryboardDecoderTest.cs" />
|
||||||
<Compile Include="Beatmaps\IO\OszArchiveReaderTest.cs" />
|
<Compile Include="Beatmaps\IO\OszArchiveReaderTest.cs" />
|
||||||
<Compile Include="Beatmaps\IO\ImportBeatmapTest.cs" />
|
<Compile Include="Beatmaps\IO\ImportBeatmapTest.cs" />
|
||||||
<Compile Include="Resources\Resource.cs" />
|
<Compile Include="Resources\Resource.cs" />
|
||||||
<Compile Include="Beatmaps\Formats\OsuLegacyDecoderTest.cs" />
|
<Compile Include="Beatmaps\Formats\LegacyBeatmapDecoderTest.cs" />
|
||||||
<Compile Include="Visual\TestCaseBeatmapDetailArea.cs" />
|
<Compile Include="Visual\TestCaseBeatmapDetailArea.cs" />
|
||||||
<Compile Include="Visual\TestCaseBeatmapDetails.cs" />
|
<Compile Include="Visual\TestCaseBeatmapDetails.cs" />
|
||||||
<Compile Include="Visual\TestCaseBeatmapOptionsOverlay.cs" />
|
<Compile Include="Visual\TestCaseBeatmapOptionsOverlay.cs" />
|
||||||
@ -105,11 +103,14 @@
|
|||||||
<Compile Include="Visual\TestCaseDrawableRoom.cs" />
|
<Compile Include="Visual\TestCaseDrawableRoom.cs" />
|
||||||
<Compile Include="Visual\TestCaseDrawings.cs" />
|
<Compile Include="Visual\TestCaseDrawings.cs" />
|
||||||
<Compile Include="Visual\TestCaseEditor.cs" />
|
<Compile Include="Visual\TestCaseEditor.cs" />
|
||||||
|
<Compile Include="Visual\TestCaseEditorCompose.cs" />
|
||||||
|
<Compile Include="Visual\TestCaseEditorComposeRadioButtons.cs" />
|
||||||
<Compile Include="Visual\TestCaseEditorComposeTimeline.cs" />
|
<Compile Include="Visual\TestCaseEditorComposeTimeline.cs" />
|
||||||
<Compile Include="Visual\TestCaseEditorMenuBar.cs" />
|
<Compile Include="Visual\TestCaseEditorMenuBar.cs" />
|
||||||
<Compile Include="Visual\TestCaseEditorSummaryTimeline.cs" />
|
<Compile Include="Visual\TestCaseEditorSummaryTimeline.cs" />
|
||||||
<Compile Include="Visual\TestCaseGamefield.cs" />
|
<Compile Include="Visual\TestCaseGamefield.cs" />
|
||||||
<Compile Include="Visual\TestCaseGraph.cs" />
|
<Compile Include="Visual\TestCaseGraph.cs" />
|
||||||
|
<Compile Include="Visual\TestCaseHistoricalSection.cs" />
|
||||||
<Compile Include="Visual\TestCaseIconButton.cs" />
|
<Compile Include="Visual\TestCaseIconButton.cs" />
|
||||||
<Compile Include="Visual\TestCaseIntroSequence.cs" />
|
<Compile Include="Visual\TestCaseIntroSequence.cs" />
|
||||||
<Compile Include="Visual\TestCaseKeyConfiguration.cs" />
|
<Compile Include="Visual\TestCaseKeyConfiguration.cs" />
|
||||||
@ -147,6 +148,7 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<EmbeddedResource Include="Resources\Soleily - Renatus %28Gamu%29 [Insane].osu" />
|
<EmbeddedResource Include="Resources\Soleily - Renatus %28Gamu%29 [Insane].osu" />
|
||||||
|
<EmbeddedResource Include="Resources\Himeringo - Yotsuya-san ni Yoroshiku %28RLC%29 [Winber1%27s Extreme].osu" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
|
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
|
||||||
</Project>
|
</Project>
|
13
osu.Game.props
Normal file
13
osu.Game.props
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<!-- Contains required properties for osu!framework projects. -->
|
||||||
|
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
|
<Import Project="$(SolutionDir)\packages\Microsoft.Net.Compilers.2.3.2\build\Microsoft.Net.Compilers.props" Condition="Exists('$(SolutionDir)\packages\Microsoft.Net.Compilers.2.3.2\build\Microsoft.Net.Compilers.props')" />
|
||||||
|
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||||
|
<PropertyGroup Label="C#">
|
||||||
|
<LangVersion>7</LangVersion>
|
||||||
|
</PropertyGroup>
|
||||||
|
<ItemGroup Label="License">
|
||||||
|
<None Include="..\osu.licenseheader">
|
||||||
|
<Link>osu.licenseheader</Link>
|
||||||
|
</None>
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
@ -8,7 +8,6 @@ using System.Collections.Generic;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Game.Beatmaps.ControlPoints;
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
using osu.Game.IO.Serialization;
|
using osu.Game.IO.Serialization;
|
||||||
using osu.Game.Storyboards;
|
|
||||||
|
|
||||||
namespace osu.Game.Beatmaps
|
namespace osu.Game.Beatmaps
|
||||||
{
|
{
|
||||||
@ -41,11 +40,6 @@ namespace osu.Game.Beatmaps
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public double TotalBreakTime => Breaks.Sum(b => b.Duration);
|
public double TotalBreakTime => Breaks.Sum(b => b.Duration);
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The Beatmap's Storyboard.
|
|
||||||
/// </summary>
|
|
||||||
public Storyboard Storyboard = new Storyboard();
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Constructs a new beatmap.
|
/// Constructs a new beatmap.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -57,7 +51,6 @@ namespace osu.Game.Beatmaps
|
|||||||
Breaks = original?.Breaks ?? Breaks;
|
Breaks = original?.Breaks ?? Breaks;
|
||||||
ComboColors = original?.ComboColors ?? ComboColors;
|
ComboColors = original?.ComboColors ?? ComboColors;
|
||||||
HitObjects = original?.HitObjects ?? HitObjects;
|
HitObjects = original?.HitObjects ?? HitObjects;
|
||||||
Storyboard = original?.Storyboard ?? Storyboard;
|
|
||||||
|
|
||||||
if (original == null && Metadata == null)
|
if (original == null && Metadata == null)
|
||||||
{
|
{
|
||||||
|
@ -80,6 +80,7 @@ namespace osu.Game.Beatmaps
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Performs the conversion of a hit object.
|
/// Performs the conversion of a hit object.
|
||||||
|
/// This method is generally executed sequentially for all objects in a beatmap.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="original">The hit object to convert.</param>
|
/// <param name="original">The hit object to convert.</param>
|
||||||
/// <param name="beatmap">The un-converted Beatmap.</param>
|
/// <param name="beatmap">The un-converted Beatmap.</param>
|
||||||
|
@ -115,6 +115,7 @@ namespace osu.Game.Beatmaps
|
|||||||
// Metadata
|
// Metadata
|
||||||
public string Version { get; set; }
|
public string Version { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("difficulty_rating")]
|
||||||
public double StarDifficulty { get; set; }
|
public double StarDifficulty { get; set; }
|
||||||
|
|
||||||
public bool Equals(BeatmapInfo other)
|
public bool Equals(BeatmapInfo other)
|
||||||
|
@ -25,6 +25,7 @@ using osu.Game.Online.API;
|
|||||||
using osu.Game.Online.API.Requests;
|
using osu.Game.Online.API.Requests;
|
||||||
using osu.Game.Overlays.Notifications;
|
using osu.Game.Overlays.Notifications;
|
||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
|
using osu.Game.Storyboards;
|
||||||
|
|
||||||
namespace osu.Game.Beatmaps
|
namespace osu.Game.Beatmaps
|
||||||
{
|
{
|
||||||
@ -494,10 +495,15 @@ namespace osu.Game.Beatmaps
|
|||||||
BeatmapMetadata metadata;
|
BeatmapMetadata metadata;
|
||||||
|
|
||||||
using (var stream = new StreamReader(reader.GetStream(mapName)))
|
using (var stream = new StreamReader(reader.GetStream(mapName)))
|
||||||
metadata = BeatmapDecoder.GetDecoder(stream).Decode(stream).Metadata;
|
metadata = Decoder.GetDecoder(stream).DecodeBeatmap(stream).Metadata;
|
||||||
|
|
||||||
|
|
||||||
// check if a set already exists with the same online id.
|
// check if a set already exists with the same online id.
|
||||||
beatmapSet = beatmaps.BeatmapSets.FirstOrDefault(b => b.OnlineBeatmapSetID == metadata.OnlineBeatmapSetID) ?? new BeatmapSetInfo
|
if (metadata.OnlineBeatmapSetID != null)
|
||||||
|
beatmapSet = beatmaps.BeatmapSets.FirstOrDefault(b => b.OnlineBeatmapSetID == metadata.OnlineBeatmapSetID);
|
||||||
|
|
||||||
|
if (beatmapSet == null)
|
||||||
|
beatmapSet = new BeatmapSetInfo
|
||||||
{
|
{
|
||||||
OnlineBeatmapSetID = metadata.OnlineBeatmapSetID,
|
OnlineBeatmapSetID = metadata.OnlineBeatmapSetID,
|
||||||
Beatmaps = new List<BeatmapInfo>(),
|
Beatmaps = new List<BeatmapInfo>(),
|
||||||
@ -506,6 +512,7 @@ namespace osu.Game.Beatmaps
|
|||||||
Metadata = metadata
|
Metadata = metadata
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
var mapNames = reader.Filenames.Where(f => f.EndsWith(".osu"));
|
var mapNames = reader.Filenames.Where(f => f.EndsWith(".osu"));
|
||||||
|
|
||||||
foreach (var name in mapNames)
|
foreach (var name in mapNames)
|
||||||
@ -517,18 +524,19 @@ namespace osu.Game.Beatmaps
|
|||||||
raw.CopyTo(ms);
|
raw.CopyTo(ms);
|
||||||
ms.Position = 0;
|
ms.Position = 0;
|
||||||
|
|
||||||
var decoder = BeatmapDecoder.GetDecoder(sr);
|
var decoder = Decoder.GetDecoder(sr);
|
||||||
Beatmap beatmap = decoder.Decode(sr);
|
Beatmap beatmap = decoder.DecodeBeatmap(sr);
|
||||||
|
|
||||||
beatmap.BeatmapInfo.Path = name;
|
beatmap.BeatmapInfo.Path = name;
|
||||||
beatmap.BeatmapInfo.Hash = ms.ComputeSHA2Hash();
|
beatmap.BeatmapInfo.Hash = ms.ComputeSHA2Hash();
|
||||||
beatmap.BeatmapInfo.MD5Hash = ms.ComputeMD5Hash();
|
beatmap.BeatmapInfo.MD5Hash = ms.ComputeMD5Hash();
|
||||||
|
|
||||||
var existing = beatmaps.Beatmaps.FirstOrDefault(b => b.Hash == beatmap.BeatmapInfo.Hash || b.OnlineBeatmapID == beatmap.BeatmapInfo.OnlineBeatmapID);
|
var existing = beatmaps.Beatmaps.FirstOrDefault(b => b.Hash == beatmap.BeatmapInfo.Hash || beatmap.BeatmapInfo.OnlineBeatmapID != null && b.OnlineBeatmapID == beatmap.BeatmapInfo.OnlineBeatmapID);
|
||||||
|
|
||||||
if (existing == null)
|
if (existing == null)
|
||||||
{
|
{
|
||||||
// TODO: Diff beatmap metadata with set metadata and leave it here if necessary
|
// Exclude beatmap-metadata if it's equal to beatmapset-metadata
|
||||||
|
if (metadata.Equals(beatmap.Metadata))
|
||||||
beatmap.BeatmapInfo.Metadata = null;
|
beatmap.BeatmapInfo.Metadata = null;
|
||||||
|
|
||||||
RulesetInfo ruleset = rulesets.GetRuleset(beatmap.BeatmapInfo.RulesetID);
|
RulesetInfo ruleset = rulesets.GetRuleset(beatmap.BeatmapInfo.RulesetID);
|
||||||
@ -568,23 +576,11 @@ namespace osu.Game.Beatmaps
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Beatmap beatmap;
|
|
||||||
|
|
||||||
BeatmapDecoder decoder;
|
|
||||||
using (var stream = new StreamReader(store.GetStream(getPathForFile(BeatmapInfo.Path))))
|
using (var stream = new StreamReader(store.GetStream(getPathForFile(BeatmapInfo.Path))))
|
||||||
{
|
{
|
||||||
decoder = BeatmapDecoder.GetDecoder(stream);
|
Decoder decoder = Decoder.GetDecoder(stream);
|
||||||
beatmap = decoder.Decode(stream);
|
return decoder.DecodeBeatmap(stream);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (beatmap == null || BeatmapSetInfo.StoryboardFile == null)
|
|
||||||
return beatmap;
|
|
||||||
|
|
||||||
using (var stream = new StreamReader(store.GetStream(getPathForFile(BeatmapSetInfo.StoryboardFile))))
|
|
||||||
decoder.Decode(stream, beatmap);
|
|
||||||
|
|
||||||
|
|
||||||
return beatmap;
|
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
@ -623,6 +619,28 @@ namespace osu.Game.Beatmaps
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected override Waveform GetWaveform() => new Waveform(store.GetStream(getPathForFile(Metadata.AudioFile)));
|
protected override Waveform GetWaveform() => new Waveform(store.GetStream(getPathForFile(Metadata.AudioFile)));
|
||||||
|
|
||||||
|
protected override Storyboard GetStoryboard()
|
||||||
|
{
|
||||||
|
if (BeatmapInfo?.Path == null && BeatmapSetInfo?.StoryboardFile == null)
|
||||||
|
return new Storyboard();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Decoder decoder;
|
||||||
|
using (var stream = new StreamReader(store.GetStream(getPathForFile(BeatmapInfo?.Path))))
|
||||||
|
decoder = Decoder.GetDecoder(stream);
|
||||||
|
|
||||||
|
// try for .osb first and fall back to .osu
|
||||||
|
string storyboardFile = BeatmapSetInfo.StoryboardFile ?? BeatmapInfo.Path;
|
||||||
|
using (var stream = new StreamReader(store.GetStream(getPathForFile(storyboardFile))))
|
||||||
|
return decoder.GetStoryboardDecoder().DecodeStoryboard(stream);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return new Storyboard();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
|
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
|
||||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||||
|
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel.DataAnnotations.Schema;
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
@ -9,7 +10,7 @@ using osu.Game.Users;
|
|||||||
|
|
||||||
namespace osu.Game.Beatmaps
|
namespace osu.Game.Beatmaps
|
||||||
{
|
{
|
||||||
public class BeatmapMetadata
|
public class BeatmapMetadata : IEquatable<BeatmapMetadata>
|
||||||
{
|
{
|
||||||
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
|
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
|
||||||
public int ID { get; set; }
|
public int ID { get; set; }
|
||||||
@ -66,5 +67,23 @@ namespace osu.Game.Beatmaps
|
|||||||
Source,
|
Source,
|
||||||
Tags
|
Tags
|
||||||
}.Where(s => !string.IsNullOrEmpty(s)).ToArray();
|
}.Where(s => !string.IsNullOrEmpty(s)).ToArray();
|
||||||
|
|
||||||
|
public bool Equals(BeatmapMetadata other)
|
||||||
|
{
|
||||||
|
if (other == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return onlineBeatmapSetID == other.onlineBeatmapSetID
|
||||||
|
&& Title == other.Title
|
||||||
|
&& TitleUnicode == other.TitleUnicode
|
||||||
|
&& Artist == other.Artist
|
||||||
|
&& ArtistUnicode == other.ArtistUnicode
|
||||||
|
&& AuthorString == other.AuthorString
|
||||||
|
&& Source == other.Source
|
||||||
|
&& Tags == other.Tags
|
||||||
|
&& PreviewTime == other.PreviewTime
|
||||||
|
&& AudioFile == other.AudioFile
|
||||||
|
&& BackgroundFile == other.BackgroundFile;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -32,6 +32,18 @@ namespace osu.Game.Beatmaps
|
|||||||
{
|
{
|
||||||
var context = GetContext();
|
var context = GetContext();
|
||||||
|
|
||||||
|
foreach (var beatmap in beatmapSet.Beatmaps.Where(b => b.Metadata != null))
|
||||||
|
{
|
||||||
|
// If we detect a new metadata object it'll be attached to the current context so it can be reused
|
||||||
|
// to prevent duplicate entries when persisting. To accomplish this we look in the cache (.Local)
|
||||||
|
// of the corresponding table (.Set<BeatmapMetadata>()) for matching entries to our criteria.
|
||||||
|
var contextMetadata = context.Set<BeatmapMetadata>().Local.SingleOrDefault(e => e.Equals(beatmap.Metadata));
|
||||||
|
if (contextMetadata != null)
|
||||||
|
beatmap.Metadata = contextMetadata;
|
||||||
|
else
|
||||||
|
context.BeatmapMetadata.Attach(beatmap.Metadata);
|
||||||
|
}
|
||||||
|
|
||||||
context.BeatmapSetInfo.Attach(beatmapSet);
|
context.BeatmapSetInfo.Attach(beatmapSet);
|
||||||
context.SaveChanges();
|
context.SaveChanges();
|
||||||
|
|
||||||
|
@ -14,7 +14,7 @@ namespace osu.Game.Beatmaps
|
|||||||
{
|
{
|
||||||
protected double TimeRate = 1;
|
protected double TimeRate = 1;
|
||||||
|
|
||||||
public abstract double Calculate(Dictionary<string, string> categoryDifficulty = null);
|
public abstract double Calculate(Dictionary<string, double> categoryDifficulty = null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract class DifficultyCalculator<T> : DifficultyCalculator where T : HitObject
|
public abstract class DifficultyCalculator<T> : DifficultyCalculator where T : HitObject
|
||||||
|
@ -11,21 +11,44 @@ namespace osu.Game.Beatmaps.Drawables
|
|||||||
public class BeatmapSetCover : Sprite
|
public class BeatmapSetCover : Sprite
|
||||||
{
|
{
|
||||||
private readonly BeatmapSetInfo set;
|
private readonly BeatmapSetInfo set;
|
||||||
public BeatmapSetCover(BeatmapSetInfo set)
|
private readonly BeatmapSetCoverType type;
|
||||||
|
|
||||||
|
public BeatmapSetCover(BeatmapSetInfo set, BeatmapSetCoverType type = BeatmapSetCoverType.Cover)
|
||||||
{
|
{
|
||||||
if (set == null)
|
if (set == null)
|
||||||
throw new ArgumentNullException(nameof(set));
|
throw new ArgumentNullException(nameof(set));
|
||||||
|
|
||||||
this.set = set;
|
this.set = set;
|
||||||
|
this.type = type;
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(TextureStore textures)
|
private void load(TextureStore textures)
|
||||||
{
|
{
|
||||||
string resource = set.OnlineInfo.Covers.Cover;
|
string resource = null;
|
||||||
|
|
||||||
|
switch (type)
|
||||||
|
{
|
||||||
|
case BeatmapSetCoverType.Cover:
|
||||||
|
resource = set.OnlineInfo.Covers.Cover;
|
||||||
|
break;
|
||||||
|
case BeatmapSetCoverType.Card:
|
||||||
|
resource = set.OnlineInfo.Covers.Card;
|
||||||
|
break;
|
||||||
|
case BeatmapSetCoverType.List:
|
||||||
|
resource = set.OnlineInfo.Covers.List;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
if (resource != null)
|
if (resource != null)
|
||||||
Texture = textures.Get(resource);
|
Texture = textures.Get(resource);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public enum BeatmapSetCoverType
|
||||||
|
{
|
||||||
|
Cover,
|
||||||
|
Card,
|
||||||
|
List,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,12 +3,18 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using osu.Framework;
|
using osu.Framework;
|
||||||
|
using osu.Framework.Audio;
|
||||||
|
using osu.Framework.Audio.Sample;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Input;
|
using osu.Framework.Input;
|
||||||
using OpenTK;
|
using OpenTK;
|
||||||
using OpenTK.Graphics;
|
using OpenTK.Graphics;
|
||||||
using osu.Framework.Extensions.Color4Extensions;
|
using osu.Framework.Extensions.Color4Extensions;
|
||||||
|
using osu.Framework.MathUtils;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Game.Graphics;
|
||||||
|
|
||||||
namespace osu.Game.Beatmaps.Drawables
|
namespace osu.Game.Beatmaps.Drawables
|
||||||
{
|
{
|
||||||
@ -22,6 +28,10 @@ namespace osu.Game.Beatmaps.Drawables
|
|||||||
|
|
||||||
private readonly Container nestedContainer;
|
private readonly Container nestedContainer;
|
||||||
|
|
||||||
|
private readonly Container borderContainer;
|
||||||
|
|
||||||
|
private readonly Box hoverLayer;
|
||||||
|
|
||||||
protected override Container<Drawable> Content => nestedContainer;
|
protected override Container<Drawable> Content => nestedContainer;
|
||||||
|
|
||||||
protected Panel()
|
protected Panel()
|
||||||
@ -29,20 +39,56 @@ namespace osu.Game.Beatmaps.Drawables
|
|||||||
Height = MAX_HEIGHT;
|
Height = MAX_HEIGHT;
|
||||||
RelativeSizeAxes = Axes.X;
|
RelativeSizeAxes = Axes.X;
|
||||||
|
|
||||||
AddInternal(nestedContainer = new Container
|
AddInternal(borderContainer = new Container
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Masking = true,
|
Masking = true,
|
||||||
CornerRadius = 10,
|
CornerRadius = 10,
|
||||||
BorderColour = new Color4(221, 255, 255, 255),
|
BorderColour = new Color4(221, 255, 255, 255),
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
nestedContainer = new Container
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
},
|
||||||
|
hoverLayer = new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Alpha = 0,
|
||||||
|
Blending = BlendingMode.Additive,
|
||||||
|
},
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Alpha = 0;
|
Alpha = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private SampleChannel sampleHover;
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(AudioManager audio, OsuColour colours)
|
||||||
|
{
|
||||||
|
sampleHover = audio.Sample.Get($@"SongSelect/song-ping-variation-{RNG.Next(1, 5)}");
|
||||||
|
hoverLayer.Colour = colours.Blue.Opacity(0.1f);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool OnHover(InputState state)
|
||||||
|
{
|
||||||
|
sampleHover?.Play();
|
||||||
|
|
||||||
|
hoverLayer.FadeIn(100, Easing.OutQuint);
|
||||||
|
return base.OnHover(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnHoverLost(InputState state)
|
||||||
|
{
|
||||||
|
hoverLayer.FadeOut(1000, Easing.OutQuint);
|
||||||
|
base.OnHoverLost(state);
|
||||||
|
}
|
||||||
|
|
||||||
public void SetMultiplicativeAlpha(float alpha)
|
public void SetMultiplicativeAlpha(float alpha)
|
||||||
{
|
{
|
||||||
nestedContainer.Alpha = alpha;
|
borderContainer.Alpha = alpha;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void LoadComplete()
|
protected override void LoadComplete()
|
||||||
@ -94,8 +140,8 @@ namespace osu.Game.Beatmaps.Drawables
|
|||||||
|
|
||||||
protected virtual void Selected()
|
protected virtual void Selected()
|
||||||
{
|
{
|
||||||
nestedContainer.BorderThickness = 2.5f;
|
borderContainer.BorderThickness = 2.5f;
|
||||||
nestedContainer.EdgeEffect = new EdgeEffectParameters
|
borderContainer.EdgeEffect = new EdgeEffectParameters
|
||||||
{
|
{
|
||||||
Type = EdgeEffectType.Glow,
|
Type = EdgeEffectType.Glow,
|
||||||
Colour = new Color4(130, 204, 255, 150),
|
Colour = new Color4(130, 204, 255, 150),
|
||||||
@ -106,8 +152,8 @@ namespace osu.Game.Beatmaps.Drawables
|
|||||||
|
|
||||||
protected virtual void Deselected()
|
protected virtual void Deselected()
|
||||||
{
|
{
|
||||||
nestedContainer.BorderThickness = 0;
|
borderContainer.BorderThickness = 0;
|
||||||
nestedContainer.EdgeEffect = new EdgeEffectParameters
|
borderContainer.EdgeEffect = new EdgeEffectParameters
|
||||||
{
|
{
|
||||||
Type = EdgeEffectType.Shadow,
|
Type = EdgeEffectType.Shadow,
|
||||||
Offset = new Vector2(1),
|
Offset = new Vector2(1),
|
||||||
|
@ -63,6 +63,8 @@ namespace osu.Game.Beatmaps
|
|||||||
|
|
||||||
public override string Description => "dummy";
|
public override string Description => "dummy";
|
||||||
|
|
||||||
|
public override string ShortName => "dummy";
|
||||||
|
|
||||||
public DummyRuleset(RulesetInfo rulesetInfo)
|
public DummyRuleset(RulesetInfo rulesetInfo)
|
||||||
: base(rulesetInfo)
|
: base(rulesetInfo)
|
||||||
{
|
{
|
||||||
|
@ -1,65 +0,0 @@
|
|||||||
// Copyright (c) 2007-2017 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.IO;
|
|
||||||
|
|
||||||
namespace osu.Game.Beatmaps.Formats
|
|
||||||
{
|
|
||||||
public abstract class BeatmapDecoder
|
|
||||||
{
|
|
||||||
private static readonly Dictionary<string, Type> decoders = new Dictionary<string, Type>();
|
|
||||||
|
|
||||||
static BeatmapDecoder()
|
|
||||||
{
|
|
||||||
OsuLegacyDecoder.Register();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static BeatmapDecoder GetDecoder(StreamReader stream)
|
|
||||||
{
|
|
||||||
if (stream == null)
|
|
||||||
throw new ArgumentNullException(nameof(stream));
|
|
||||||
|
|
||||||
string line;
|
|
||||||
do { line = stream.ReadLine()?.Trim(); }
|
|
||||||
while (line != null && line.Length == 0);
|
|
||||||
|
|
||||||
if (line == null || !decoders.ContainsKey(line))
|
|
||||||
throw new IOException(@"Unknown file format");
|
|
||||||
return (BeatmapDecoder)Activator.CreateInstance(decoders[line], line);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected static void AddDecoder<T>(string magic) where T : BeatmapDecoder
|
|
||||||
{
|
|
||||||
decoders[magic] = typeof(T);
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual Beatmap Decode(StreamReader stream)
|
|
||||||
{
|
|
||||||
return ParseFile(stream);
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual void Decode(StreamReader stream, Beatmap beatmap)
|
|
||||||
{
|
|
||||||
ParseFile(stream, beatmap);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected virtual Beatmap ParseFile(StreamReader stream)
|
|
||||||
{
|
|
||||||
var beatmap = new Beatmap
|
|
||||||
{
|
|
||||||
BeatmapInfo = new BeatmapInfo
|
|
||||||
{
|
|
||||||
Metadata = new BeatmapMetadata(),
|
|
||||||
BaseDifficulty = new BeatmapDifficulty(),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
ParseFile(stream, beatmap);
|
|
||||||
return beatmap;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected abstract void ParseFile(StreamReader stream, Beatmap beatmap);
|
|
||||||
}
|
|
||||||
}
|
|
80
osu.Game/Beatmaps/Formats/Decoder.cs
Normal file
80
osu.Game/Beatmaps/Formats/Decoder.cs
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
// Copyright (c) 2007-2017 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.IO;
|
||||||
|
using osu.Game.Storyboards;
|
||||||
|
|
||||||
|
namespace osu.Game.Beatmaps.Formats
|
||||||
|
{
|
||||||
|
public abstract class Decoder
|
||||||
|
{
|
||||||
|
private static readonly Dictionary<string, Type> decoders = new Dictionary<string, Type>();
|
||||||
|
|
||||||
|
static Decoder()
|
||||||
|
{
|
||||||
|
LegacyDecoder.Register();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieves a <see cref="Decoder"/> to parse a <see cref="Beatmap"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="stream">A stream pointing to the <see cref="Beatmap"/>.</param>
|
||||||
|
public static Decoder GetDecoder(StreamReader stream)
|
||||||
|
{
|
||||||
|
if (stream == null)
|
||||||
|
throw new ArgumentNullException(nameof(stream));
|
||||||
|
|
||||||
|
string line;
|
||||||
|
do
|
||||||
|
{ line = stream.ReadLine()?.Trim(); }
|
||||||
|
while (line != null && line.Length == 0);
|
||||||
|
|
||||||
|
if (line == null || !decoders.ContainsKey(line))
|
||||||
|
throw new IOException(@"Unknown file format");
|
||||||
|
return (Decoder)Activator.CreateInstance(decoders[line], line);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds the <see cref="Decoder"/> to the list of <see cref="Beatmap"/> and <see cref="Storyboard"/> decoder.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">Type to decode a <see cref="Beatmap"/> with.</typeparam>
|
||||||
|
/// <param name="version">A string representation of the version.</param>
|
||||||
|
protected static void AddDecoder<T>(string version) where T : Decoder
|
||||||
|
{
|
||||||
|
decoders[version] = typeof(T);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieves a <see cref="Decoder"/> to parse a <see cref="Storyboard"/>
|
||||||
|
/// </summary>
|
||||||
|
public abstract Decoder GetStoryboardDecoder();
|
||||||
|
|
||||||
|
public virtual Beatmap DecodeBeatmap(StreamReader stream)
|
||||||
|
{
|
||||||
|
var beatmap = new Beatmap
|
||||||
|
{
|
||||||
|
BeatmapInfo = new BeatmapInfo
|
||||||
|
{
|
||||||
|
Metadata = new BeatmapMetadata(),
|
||||||
|
BaseDifficulty = new BeatmapDifficulty(),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
ParseBeatmap(stream, beatmap);
|
||||||
|
return beatmap;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract void ParseBeatmap(StreamReader stream, Beatmap beatmap);
|
||||||
|
|
||||||
|
public virtual Storyboard DecodeStoryboard(StreamReader stream)
|
||||||
|
{
|
||||||
|
var storyboard = new Storyboard();
|
||||||
|
ParseStoryboard(stream, storyboard);
|
||||||
|
return storyboard;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract void ParseStoryboard(StreamReader stream, Storyboard storyboard);
|
||||||
|
}
|
||||||
|
}
|
421
osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs
Normal file
421
osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs
Normal file
@ -0,0 +1,421 @@
|
|||||||
|
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
|
||||||
|
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.IO;
|
||||||
|
using OpenTK.Graphics;
|
||||||
|
using osu.Game.Beatmaps.Timing;
|
||||||
|
using osu.Game.Rulesets.Objects.Legacy;
|
||||||
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace osu.Game.Beatmaps.Formats
|
||||||
|
{
|
||||||
|
public class LegacyBeatmapDecoder : LegacyDecoder
|
||||||
|
{
|
||||||
|
private Beatmap beatmap;
|
||||||
|
|
||||||
|
private bool hasCustomColours;
|
||||||
|
private ConvertHitObjectParser parser;
|
||||||
|
|
||||||
|
private LegacySampleBank defaultSampleBank;
|
||||||
|
private int defaultSampleVolume = 100;
|
||||||
|
|
||||||
|
public LegacyBeatmapDecoder()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public LegacyBeatmapDecoder(string header)
|
||||||
|
{
|
||||||
|
BeatmapVersion = int.Parse(header.Substring(17));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void ParseBeatmap(StreamReader stream, Beatmap beatmap)
|
||||||
|
{
|
||||||
|
if (stream == null)
|
||||||
|
throw new ArgumentNullException(nameof(stream));
|
||||||
|
if (beatmap == null)
|
||||||
|
throw new ArgumentNullException(nameof(beatmap));
|
||||||
|
|
||||||
|
this.beatmap = beatmap;
|
||||||
|
this.beatmap.BeatmapInfo.BeatmapVersion = BeatmapVersion;
|
||||||
|
|
||||||
|
ParseContent(stream);
|
||||||
|
|
||||||
|
foreach (var hitObject in this.beatmap.HitObjects)
|
||||||
|
hitObject.ApplyDefaults(this.beatmap.ControlPointInfo, this.beatmap.BeatmapInfo.BaseDifficulty);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool ShouldSkipLine(string line)
|
||||||
|
{
|
||||||
|
if (base.ShouldSkipLine(line) || line.StartsWith(" ") || line.StartsWith("_"))
|
||||||
|
return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void ProcessSection(Section section, string line)
|
||||||
|
{
|
||||||
|
switch (section)
|
||||||
|
{
|
||||||
|
case Section.General:
|
||||||
|
handleGeneral(line);
|
||||||
|
break;
|
||||||
|
case Section.Editor:
|
||||||
|
handleEditor(line);
|
||||||
|
break;
|
||||||
|
case Section.Metadata:
|
||||||
|
handleMetadata(line);
|
||||||
|
break;
|
||||||
|
case Section.Difficulty:
|
||||||
|
handleDifficulty(line);
|
||||||
|
break;
|
||||||
|
case Section.Events:
|
||||||
|
handleEvents(line);
|
||||||
|
break;
|
||||||
|
case Section.TimingPoints:
|
||||||
|
handleTimingPoints(line);
|
||||||
|
break;
|
||||||
|
case Section.Colours:
|
||||||
|
handleColours(line);
|
||||||
|
break;
|
||||||
|
case Section.HitObjects:
|
||||||
|
handleHitObjects(line);
|
||||||
|
break;
|
||||||
|
case Section.Variables:
|
||||||
|
handleVariables(line);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleGeneral(string line)
|
||||||
|
{
|
||||||
|
var pair = splitKeyVal(line, ':');
|
||||||
|
|
||||||
|
var metadata = beatmap.BeatmapInfo.Metadata;
|
||||||
|
switch (pair.Key)
|
||||||
|
{
|
||||||
|
case @"AudioFilename":
|
||||||
|
metadata.AudioFile = pair.Value;
|
||||||
|
break;
|
||||||
|
case @"AudioLeadIn":
|
||||||
|
beatmap.BeatmapInfo.AudioLeadIn = int.Parse(pair.Value);
|
||||||
|
break;
|
||||||
|
case @"PreviewTime":
|
||||||
|
metadata.PreviewTime = int.Parse(pair.Value);
|
||||||
|
break;
|
||||||
|
case @"Countdown":
|
||||||
|
beatmap.BeatmapInfo.Countdown = int.Parse(pair.Value) == 1;
|
||||||
|
break;
|
||||||
|
case @"SampleSet":
|
||||||
|
defaultSampleBank = (LegacySampleBank)Enum.Parse(typeof(LegacySampleBank), pair.Value);
|
||||||
|
break;
|
||||||
|
case @"SampleVolume":
|
||||||
|
defaultSampleVolume = int.Parse(pair.Value);
|
||||||
|
break;
|
||||||
|
case @"StackLeniency":
|
||||||
|
beatmap.BeatmapInfo.StackLeniency = float.Parse(pair.Value, NumberFormatInfo.InvariantInfo);
|
||||||
|
break;
|
||||||
|
case @"Mode":
|
||||||
|
beatmap.BeatmapInfo.RulesetID = int.Parse(pair.Value);
|
||||||
|
|
||||||
|
switch (beatmap.BeatmapInfo.RulesetID)
|
||||||
|
{
|
||||||
|
case 0:
|
||||||
|
parser = new Rulesets.Objects.Legacy.Osu.ConvertHitObjectParser();
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
parser = new Rulesets.Objects.Legacy.Taiko.ConvertHitObjectParser();
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
parser = new Rulesets.Objects.Legacy.Catch.ConvertHitObjectParser();
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
parser = new Rulesets.Objects.Legacy.Mania.ConvertHitObjectParser();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case @"LetterboxInBreaks":
|
||||||
|
beatmap.BeatmapInfo.LetterboxInBreaks = int.Parse(pair.Value) == 1;
|
||||||
|
break;
|
||||||
|
case @"SpecialStyle":
|
||||||
|
beatmap.BeatmapInfo.SpecialStyle = int.Parse(pair.Value) == 1;
|
||||||
|
break;
|
||||||
|
case @"WidescreenStoryboard":
|
||||||
|
beatmap.BeatmapInfo.WidescreenStoryboard = int.Parse(pair.Value) == 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleEditor(string line)
|
||||||
|
{
|
||||||
|
var pair = splitKeyVal(line, ':');
|
||||||
|
|
||||||
|
switch (pair.Key)
|
||||||
|
{
|
||||||
|
case @"Bookmarks":
|
||||||
|
beatmap.BeatmapInfo.StoredBookmarks = pair.Value;
|
||||||
|
break;
|
||||||
|
case @"DistanceSpacing":
|
||||||
|
beatmap.BeatmapInfo.DistanceSpacing = double.Parse(pair.Value, NumberFormatInfo.InvariantInfo);
|
||||||
|
break;
|
||||||
|
case @"BeatDivisor":
|
||||||
|
beatmap.BeatmapInfo.BeatDivisor = int.Parse(pair.Value);
|
||||||
|
break;
|
||||||
|
case @"GridSize":
|
||||||
|
beatmap.BeatmapInfo.GridSize = int.Parse(pair.Value);
|
||||||
|
break;
|
||||||
|
case @"TimelineZoom":
|
||||||
|
beatmap.BeatmapInfo.TimelineZoom = double.Parse(pair.Value, NumberFormatInfo.InvariantInfo);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleMetadata(string line)
|
||||||
|
{
|
||||||
|
var pair = splitKeyVal(line, ':');
|
||||||
|
|
||||||
|
var metadata = beatmap.BeatmapInfo.Metadata;
|
||||||
|
switch (pair.Key)
|
||||||
|
{
|
||||||
|
case @"Title":
|
||||||
|
metadata.Title = pair.Value;
|
||||||
|
break;
|
||||||
|
case @"TitleUnicode":
|
||||||
|
metadata.TitleUnicode = pair.Value;
|
||||||
|
break;
|
||||||
|
case @"Artist":
|
||||||
|
metadata.Artist = pair.Value;
|
||||||
|
break;
|
||||||
|
case @"ArtistUnicode":
|
||||||
|
metadata.ArtistUnicode = pair.Value;
|
||||||
|
break;
|
||||||
|
case @"Creator":
|
||||||
|
metadata.AuthorString = pair.Value;
|
||||||
|
break;
|
||||||
|
case @"Version":
|
||||||
|
beatmap.BeatmapInfo.Version = pair.Value;
|
||||||
|
break;
|
||||||
|
case @"Source":
|
||||||
|
beatmap.BeatmapInfo.Metadata.Source = pair.Value;
|
||||||
|
break;
|
||||||
|
case @"Tags":
|
||||||
|
beatmap.BeatmapInfo.Metadata.Tags = pair.Value;
|
||||||
|
break;
|
||||||
|
case @"BeatmapID":
|
||||||
|
beatmap.BeatmapInfo.OnlineBeatmapID = int.Parse(pair.Value);
|
||||||
|
break;
|
||||||
|
case @"BeatmapSetID":
|
||||||
|
beatmap.BeatmapInfo.OnlineBeatmapSetID = int.Parse(pair.Value);
|
||||||
|
metadata.OnlineBeatmapSetID = int.Parse(pair.Value);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleDifficulty(string line)
|
||||||
|
{
|
||||||
|
var pair = splitKeyVal(line, ':');
|
||||||
|
|
||||||
|
var difficulty = beatmap.BeatmapInfo.BaseDifficulty;
|
||||||
|
switch (pair.Key)
|
||||||
|
{
|
||||||
|
case @"HPDrainRate":
|
||||||
|
difficulty.DrainRate = float.Parse(pair.Value, NumberFormatInfo.InvariantInfo);
|
||||||
|
break;
|
||||||
|
case @"CircleSize":
|
||||||
|
difficulty.CircleSize = float.Parse(pair.Value, NumberFormatInfo.InvariantInfo);
|
||||||
|
break;
|
||||||
|
case @"OverallDifficulty":
|
||||||
|
difficulty.OverallDifficulty = float.Parse(pair.Value, NumberFormatInfo.InvariantInfo);
|
||||||
|
break;
|
||||||
|
case @"ApproachRate":
|
||||||
|
difficulty.ApproachRate = float.Parse(pair.Value, NumberFormatInfo.InvariantInfo);
|
||||||
|
break;
|
||||||
|
case @"SliderMultiplier":
|
||||||
|
difficulty.SliderMultiplier = float.Parse(pair.Value, NumberFormatInfo.InvariantInfo);
|
||||||
|
break;
|
||||||
|
case @"SliderTickRate":
|
||||||
|
difficulty.SliderTickRate = float.Parse(pair.Value, NumberFormatInfo.InvariantInfo);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleEvents(string line)
|
||||||
|
{
|
||||||
|
DecodeVariables(ref line);
|
||||||
|
|
||||||
|
string[] split = line.Split(',');
|
||||||
|
|
||||||
|
EventType type;
|
||||||
|
if (!Enum.TryParse(split[0], out type))
|
||||||
|
throw new InvalidDataException($@"Unknown event type {split[0]}");
|
||||||
|
|
||||||
|
switch (type)
|
||||||
|
{
|
||||||
|
case EventType.Background:
|
||||||
|
string filename = split[2].Trim('"');
|
||||||
|
beatmap.BeatmapInfo.Metadata.BackgroundFile = filename;
|
||||||
|
break;
|
||||||
|
case EventType.Break:
|
||||||
|
var breakEvent = new BreakPeriod
|
||||||
|
{
|
||||||
|
StartTime = double.Parse(split[1], NumberFormatInfo.InvariantInfo),
|
||||||
|
EndTime = double.Parse(split[2], NumberFormatInfo.InvariantInfo)
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!breakEvent.HasEffect)
|
||||||
|
return;
|
||||||
|
|
||||||
|
beatmap.Breaks.Add(breakEvent);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleTimingPoints(string line)
|
||||||
|
{
|
||||||
|
string[] split = line.Split(',');
|
||||||
|
|
||||||
|
double time = double.Parse(split[0].Trim(), NumberFormatInfo.InvariantInfo);
|
||||||
|
double beatLength = double.Parse(split[1].Trim(), NumberFormatInfo.InvariantInfo);
|
||||||
|
double speedMultiplier = beatLength < 0 ? 100.0 / -beatLength : 1;
|
||||||
|
|
||||||
|
TimeSignatures timeSignature = TimeSignatures.SimpleQuadruple;
|
||||||
|
if (split.Length >= 3)
|
||||||
|
timeSignature = split[2][0] == '0' ? TimeSignatures.SimpleQuadruple : (TimeSignatures)int.Parse(split[2]);
|
||||||
|
|
||||||
|
LegacySampleBank sampleSet = defaultSampleBank;
|
||||||
|
if (split.Length >= 4)
|
||||||
|
sampleSet = (LegacySampleBank)int.Parse(split[3]);
|
||||||
|
|
||||||
|
//SampleBank sampleBank = SampleBank.Default;
|
||||||
|
//if (split.Length >= 5)
|
||||||
|
// sampleBank = (SampleBank)int.Parse(split[4]);
|
||||||
|
|
||||||
|
int sampleVolume = defaultSampleVolume;
|
||||||
|
if (split.Length >= 6)
|
||||||
|
sampleVolume = int.Parse(split[5]);
|
||||||
|
|
||||||
|
bool timingChange = true;
|
||||||
|
if (split.Length >= 7)
|
||||||
|
timingChange = split[6][0] == '1';
|
||||||
|
|
||||||
|
bool kiaiMode = false;
|
||||||
|
bool omitFirstBarSignature = false;
|
||||||
|
if (split.Length >= 8)
|
||||||
|
{
|
||||||
|
int effectFlags = int.Parse(split[7]);
|
||||||
|
kiaiMode = (effectFlags & 1) > 0;
|
||||||
|
omitFirstBarSignature = (effectFlags & 8) > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
string stringSampleSet = sampleSet.ToString().ToLower();
|
||||||
|
if (stringSampleSet == @"none")
|
||||||
|
stringSampleSet = @"normal";
|
||||||
|
|
||||||
|
DifficultyControlPoint difficultyPoint = beatmap.ControlPointInfo.DifficultyPointAt(time);
|
||||||
|
SoundControlPoint soundPoint = beatmap.ControlPointInfo.SoundPointAt(time);
|
||||||
|
EffectControlPoint effectPoint = beatmap.ControlPointInfo.EffectPointAt(time);
|
||||||
|
|
||||||
|
if (timingChange)
|
||||||
|
{
|
||||||
|
beatmap.ControlPointInfo.TimingPoints.Add(new TimingControlPoint
|
||||||
|
{
|
||||||
|
Time = time,
|
||||||
|
BeatLength = beatLength,
|
||||||
|
TimeSignature = timeSignature
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (speedMultiplier != difficultyPoint.SpeedMultiplier)
|
||||||
|
{
|
||||||
|
beatmap.ControlPointInfo.DifficultyPoints.RemoveAll(x => x.Time == time);
|
||||||
|
beatmap.ControlPointInfo.DifficultyPoints.Add(new DifficultyControlPoint
|
||||||
|
{
|
||||||
|
Time = time,
|
||||||
|
SpeedMultiplier = speedMultiplier
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stringSampleSet != soundPoint.SampleBank || sampleVolume != soundPoint.SampleVolume)
|
||||||
|
{
|
||||||
|
beatmap.ControlPointInfo.SoundPoints.Add(new SoundControlPoint
|
||||||
|
{
|
||||||
|
Time = time,
|
||||||
|
SampleBank = stringSampleSet,
|
||||||
|
SampleVolume = sampleVolume
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (kiaiMode != effectPoint.KiaiMode || omitFirstBarSignature != effectPoint.OmitFirstBarLine)
|
||||||
|
{
|
||||||
|
beatmap.ControlPointInfo.EffectPoints.Add(new EffectControlPoint
|
||||||
|
{
|
||||||
|
Time = time,
|
||||||
|
KiaiMode = kiaiMode,
|
||||||
|
OmitFirstBarLine = omitFirstBarSignature
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleColours(string line)
|
||||||
|
{
|
||||||
|
var pair = splitKeyVal(line, ':');
|
||||||
|
|
||||||
|
string[] split = pair.Value.Split(',');
|
||||||
|
|
||||||
|
if (split.Length != 3)
|
||||||
|
throw new InvalidOperationException($@"Color specified in incorrect format (should be R,G,B): {pair.Value}");
|
||||||
|
|
||||||
|
byte r, g, b;
|
||||||
|
if (!byte.TryParse(split[0], out r) || !byte.TryParse(split[1], out g) || !byte.TryParse(split[2], out b))
|
||||||
|
throw new InvalidOperationException(@"Color must be specified with 8-bit integer components");
|
||||||
|
|
||||||
|
if (!hasCustomColours)
|
||||||
|
{
|
||||||
|
beatmap.ComboColors.Clear();
|
||||||
|
hasCustomColours = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note: the combo index specified in the beatmap is discarded
|
||||||
|
if (pair.Key.StartsWith(@"Combo"))
|
||||||
|
{
|
||||||
|
beatmap.ComboColors.Add(new Color4
|
||||||
|
{
|
||||||
|
R = r / 255f,
|
||||||
|
G = g / 255f,
|
||||||
|
B = b / 255f,
|
||||||
|
A = 1f,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleHitObjects(string line)
|
||||||
|
{
|
||||||
|
// If the ruleset wasn't specified, assume the osu!standard ruleset.
|
||||||
|
if (parser == null)
|
||||||
|
parser = new Rulesets.Objects.Legacy.Osu.ConvertHitObjectParser();
|
||||||
|
|
||||||
|
var obj = parser.Parse(line);
|
||||||
|
|
||||||
|
if (obj != null)
|
||||||
|
beatmap.HitObjects.Add(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleVariables(string line)
|
||||||
|
{
|
||||||
|
var pair = splitKeyVal(line, '=');
|
||||||
|
Variables[pair.Key] = pair.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private KeyValuePair<string, string> splitKeyVal(string line, char separator)
|
||||||
|
{
|
||||||
|
var split = line.Trim().Split(new[] { separator }, 2);
|
||||||
|
|
||||||
|
return new KeyValuePair<string, string>
|
||||||
|
(
|
||||||
|
split[0].Trim(),
|
||||||
|
split.Length > 1 ? split[1].Trim() : string.Empty
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
163
osu.Game/Beatmaps/Formats/LegacyDecoder.cs
Normal file
163
osu.Game/Beatmaps/Formats/LegacyDecoder.cs
Normal file
@ -0,0 +1,163 @@
|
|||||||
|
// Copyright (c) 2007-2017 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.IO;
|
||||||
|
using osu.Game.Beatmaps.Legacy;
|
||||||
|
using osu.Game.Storyboards;
|
||||||
|
|
||||||
|
namespace osu.Game.Beatmaps.Formats
|
||||||
|
{
|
||||||
|
public abstract class LegacyDecoder : Decoder
|
||||||
|
{
|
||||||
|
public static void Register()
|
||||||
|
{
|
||||||
|
AddDecoder<LegacyBeatmapDecoder>(@"osu file format v14");
|
||||||
|
AddDecoder<LegacyBeatmapDecoder>(@"osu file format v13");
|
||||||
|
AddDecoder<LegacyBeatmapDecoder>(@"osu file format v12");
|
||||||
|
AddDecoder<LegacyBeatmapDecoder>(@"osu file format v11");
|
||||||
|
AddDecoder<LegacyBeatmapDecoder>(@"osu file format v10");
|
||||||
|
AddDecoder<LegacyBeatmapDecoder>(@"osu file format v9");
|
||||||
|
AddDecoder<LegacyBeatmapDecoder>(@"osu file format v8");
|
||||||
|
AddDecoder<LegacyBeatmapDecoder>(@"osu file format v7");
|
||||||
|
AddDecoder<LegacyBeatmapDecoder>(@"osu file format v6");
|
||||||
|
AddDecoder<LegacyBeatmapDecoder>(@"osu file format v5");
|
||||||
|
AddDecoder<LegacyBeatmapDecoder>(@"osu file format v4");
|
||||||
|
AddDecoder<LegacyBeatmapDecoder>(@"osu file format v3");
|
||||||
|
// TODO: differences between versions
|
||||||
|
}
|
||||||
|
|
||||||
|
protected int BeatmapVersion;
|
||||||
|
protected readonly Dictionary<string, string> Variables = new Dictionary<string, string>();
|
||||||
|
|
||||||
|
public override Decoder GetStoryboardDecoder() => new LegacyStoryboardDecoder(BeatmapVersion);
|
||||||
|
|
||||||
|
public override Beatmap DecodeBeatmap(StreamReader stream) => new LegacyBeatmap(base.DecodeBeatmap(stream));
|
||||||
|
|
||||||
|
protected override void ParseBeatmap(StreamReader stream, Beatmap beatmap)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void ParseStoryboard(StreamReader stream, Storyboard storyboard)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void ParseContent(StreamReader stream)
|
||||||
|
{
|
||||||
|
Section section = Section.None;
|
||||||
|
|
||||||
|
string line;
|
||||||
|
while ((line = stream.ReadLine()) != null)
|
||||||
|
{
|
||||||
|
if (ShouldSkipLine(line))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// It's already set in ParseBeatmap... why do it again?
|
||||||
|
//if (line.StartsWith(@"osu file format v"))
|
||||||
|
//{
|
||||||
|
// Beatmap.BeatmapInfo.BeatmapVersion = int.Parse(line.Substring(17));
|
||||||
|
// continue;
|
||||||
|
//}
|
||||||
|
|
||||||
|
if (line.StartsWith(@"[") && line.EndsWith(@"]"))
|
||||||
|
{
|
||||||
|
if (!Enum.TryParse(line.Substring(1, line.Length - 2), out section))
|
||||||
|
throw new InvalidDataException($@"Unknown osu section {line}");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
ProcessSection(section, line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual bool ShouldSkipLine(string line)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(line) || line.StartsWith("//"))
|
||||||
|
return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract void ProcessSection(Section section, string line);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Decodes any beatmap variables present in a line into their real values.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="line">The line which may contains variables.</param>
|
||||||
|
protected void DecodeVariables(ref string line)
|
||||||
|
{
|
||||||
|
while (line.IndexOf('$') >= 0)
|
||||||
|
{
|
||||||
|
string origLine = line;
|
||||||
|
string[] split = line.Split(',');
|
||||||
|
for (int i = 0; i < split.Length; i++)
|
||||||
|
{
|
||||||
|
var item = split[i];
|
||||||
|
if (item.StartsWith("$") && Variables.ContainsKey(item))
|
||||||
|
split[i] = Variables[item];
|
||||||
|
}
|
||||||
|
|
||||||
|
line = string.Join(",", split);
|
||||||
|
if (line == origLine)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected enum Section
|
||||||
|
{
|
||||||
|
None,
|
||||||
|
General,
|
||||||
|
Editor,
|
||||||
|
Metadata,
|
||||||
|
Difficulty,
|
||||||
|
Events,
|
||||||
|
TimingPoints,
|
||||||
|
Colours,
|
||||||
|
HitObjects,
|
||||||
|
Variables,
|
||||||
|
}
|
||||||
|
|
||||||
|
internal enum LegacySampleBank
|
||||||
|
{
|
||||||
|
None = 0,
|
||||||
|
Normal = 1,
|
||||||
|
Soft = 2,
|
||||||
|
Drum = 3
|
||||||
|
}
|
||||||
|
|
||||||
|
internal enum EventType
|
||||||
|
{
|
||||||
|
Background = 0,
|
||||||
|
Video = 1,
|
||||||
|
Break = 2,
|
||||||
|
Colour = 3,
|
||||||
|
Sprite = 4,
|
||||||
|
Sample = 5,
|
||||||
|
Animation = 6
|
||||||
|
}
|
||||||
|
|
||||||
|
internal enum LegacyOrigins
|
||||||
|
{
|
||||||
|
TopLeft,
|
||||||
|
Centre,
|
||||||
|
CentreLeft,
|
||||||
|
TopRight,
|
||||||
|
BottomCentre,
|
||||||
|
TopCentre,
|
||||||
|
Custom,
|
||||||
|
CentreRight,
|
||||||
|
BottomLeft,
|
||||||
|
BottomRight
|
||||||
|
};
|
||||||
|
|
||||||
|
internal enum StoryLayer
|
||||||
|
{
|
||||||
|
Background = 0,
|
||||||
|
Fail = 1,
|
||||||
|
Pass = 2,
|
||||||
|
Foreground = 3
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
271
osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs
Normal file
271
osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs
Normal file
@ -0,0 +1,271 @@
|
|||||||
|
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
|
||||||
|
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.IO;
|
||||||
|
using OpenTK;
|
||||||
|
using OpenTK.Graphics;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.IO.File;
|
||||||
|
using osu.Game.Storyboards;
|
||||||
|
|
||||||
|
namespace osu.Game.Beatmaps.Formats
|
||||||
|
{
|
||||||
|
public class LegacyStoryboardDecoder : LegacyDecoder
|
||||||
|
{
|
||||||
|
private Storyboard storyboard;
|
||||||
|
|
||||||
|
private StoryboardSprite storyboardSprite;
|
||||||
|
private CommandTimelineGroup timelineGroup;
|
||||||
|
|
||||||
|
public LegacyStoryboardDecoder()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public LegacyStoryboardDecoder(int beatmapVersion)
|
||||||
|
{
|
||||||
|
BeatmapVersion = beatmapVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void ParseStoryboard(StreamReader stream, Storyboard storyboard)
|
||||||
|
{
|
||||||
|
if (stream == null)
|
||||||
|
throw new ArgumentNullException(nameof(stream));
|
||||||
|
if (storyboard == null)
|
||||||
|
throw new ArgumentNullException(nameof(storyboard));
|
||||||
|
|
||||||
|
this.storyboard = storyboard;
|
||||||
|
|
||||||
|
ParseContent(stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void ProcessSection(Section section, string line)
|
||||||
|
{
|
||||||
|
switch (section)
|
||||||
|
{
|
||||||
|
case Section.Events:
|
||||||
|
handleEvents(line);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleEvents(string line)
|
||||||
|
{
|
||||||
|
var depth = 0;
|
||||||
|
while (line.StartsWith(" ") || line.StartsWith("_"))
|
||||||
|
{
|
||||||
|
++depth;
|
||||||
|
line = line.Substring(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
DecodeVariables(ref line);
|
||||||
|
|
||||||
|
string[] split = line.Split(',');
|
||||||
|
|
||||||
|
if (depth == 0)
|
||||||
|
{
|
||||||
|
storyboardSprite = null;
|
||||||
|
|
||||||
|
EventType type;
|
||||||
|
if (!Enum.TryParse(split[0], out type))
|
||||||
|
throw new InvalidDataException($@"Unknown event type {split[0]}");
|
||||||
|
|
||||||
|
switch (type)
|
||||||
|
{
|
||||||
|
case EventType.Sprite:
|
||||||
|
{
|
||||||
|
var layer = parseLayer(split[1]);
|
||||||
|
var origin = parseOrigin(split[2]);
|
||||||
|
var path = cleanFilename(split[3]);
|
||||||
|
var x = float.Parse(split[4], NumberFormatInfo.InvariantInfo);
|
||||||
|
var y = float.Parse(split[5], NumberFormatInfo.InvariantInfo);
|
||||||
|
storyboardSprite = new StoryboardSprite(path, origin, new Vector2(x, y));
|
||||||
|
storyboard.GetLayer(layer).Add(storyboardSprite);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case EventType.Animation:
|
||||||
|
{
|
||||||
|
var layer = parseLayer(split[1]);
|
||||||
|
var origin = parseOrigin(split[2]);
|
||||||
|
var path = cleanFilename(split[3]);
|
||||||
|
var x = float.Parse(split[4], NumberFormatInfo.InvariantInfo);
|
||||||
|
var y = float.Parse(split[5], NumberFormatInfo.InvariantInfo);
|
||||||
|
var frameCount = int.Parse(split[6]);
|
||||||
|
var frameDelay = double.Parse(split[7], NumberFormatInfo.InvariantInfo);
|
||||||
|
var loopType = split.Length > 8 ? (AnimationLoopType)Enum.Parse(typeof(AnimationLoopType), split[8]) : AnimationLoopType.LoopForever;
|
||||||
|
storyboardSprite = new StoryboardAnimation(path, origin, new Vector2(x, y), frameCount, frameDelay, loopType);
|
||||||
|
storyboard.GetLayer(layer).Add(storyboardSprite);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case EventType.Sample:
|
||||||
|
{
|
||||||
|
var time = double.Parse(split[1], CultureInfo.InvariantCulture);
|
||||||
|
var layer = parseLayer(split[2]);
|
||||||
|
var path = cleanFilename(split[3]);
|
||||||
|
var volume = split.Length > 4 ? float.Parse(split[4], CultureInfo.InvariantCulture) : 100;
|
||||||
|
storyboard.GetLayer(layer).Add(new StoryboardSample(path, time, volume));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (depth < 2)
|
||||||
|
timelineGroup = storyboardSprite?.TimelineGroup;
|
||||||
|
|
||||||
|
var commandType = split[0];
|
||||||
|
switch (commandType)
|
||||||
|
{
|
||||||
|
case "T":
|
||||||
|
{
|
||||||
|
var triggerName = split[1];
|
||||||
|
var startTime = split.Length > 2 ? double.Parse(split[2], CultureInfo.InvariantCulture) : double.MinValue;
|
||||||
|
var endTime = split.Length > 3 ? double.Parse(split[3], CultureInfo.InvariantCulture) : double.MaxValue;
|
||||||
|
var groupNumber = split.Length > 4 ? int.Parse(split[4]) : 0;
|
||||||
|
timelineGroup = storyboardSprite?.AddTrigger(triggerName, startTime, endTime, groupNumber);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "L":
|
||||||
|
{
|
||||||
|
var startTime = double.Parse(split[1], CultureInfo.InvariantCulture);
|
||||||
|
var loopCount = int.Parse(split[2]);
|
||||||
|
timelineGroup = storyboardSprite?.AddLoop(startTime, loopCount);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(split[3]))
|
||||||
|
split[3] = split[2];
|
||||||
|
|
||||||
|
var easing = (Easing)int.Parse(split[1]);
|
||||||
|
var startTime = double.Parse(split[2], CultureInfo.InvariantCulture);
|
||||||
|
var endTime = double.Parse(split[3], CultureInfo.InvariantCulture);
|
||||||
|
|
||||||
|
switch (commandType)
|
||||||
|
{
|
||||||
|
case "F":
|
||||||
|
{
|
||||||
|
var startValue = float.Parse(split[4], CultureInfo.InvariantCulture);
|
||||||
|
var endValue = split.Length > 5 ? float.Parse(split[5], CultureInfo.InvariantCulture) : startValue;
|
||||||
|
timelineGroup?.Alpha.Add(easing, startTime, endTime, startValue, endValue);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "S":
|
||||||
|
{
|
||||||
|
var startValue = float.Parse(split[4], CultureInfo.InvariantCulture);
|
||||||
|
var endValue = split.Length > 5 ? float.Parse(split[5], CultureInfo.InvariantCulture) : startValue;
|
||||||
|
timelineGroup?.Scale.Add(easing, startTime, endTime, new Vector2(startValue), new Vector2(endValue));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "V":
|
||||||
|
{
|
||||||
|
var startX = float.Parse(split[4], CultureInfo.InvariantCulture);
|
||||||
|
var startY = float.Parse(split[5], CultureInfo.InvariantCulture);
|
||||||
|
var endX = split.Length > 6 ? float.Parse(split[6], CultureInfo.InvariantCulture) : startX;
|
||||||
|
var endY = split.Length > 7 ? float.Parse(split[7], CultureInfo.InvariantCulture) : startY;
|
||||||
|
timelineGroup?.Scale.Add(easing, startTime, endTime, new Vector2(startX, startY), new Vector2(endX, endY));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "R":
|
||||||
|
{
|
||||||
|
var startValue = float.Parse(split[4], CultureInfo.InvariantCulture);
|
||||||
|
var endValue = split.Length > 5 ? float.Parse(split[5], CultureInfo.InvariantCulture) : startValue;
|
||||||
|
timelineGroup?.Rotation.Add(easing, startTime, endTime, MathHelper.RadiansToDegrees(startValue), MathHelper.RadiansToDegrees(endValue));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "M":
|
||||||
|
{
|
||||||
|
var startX = float.Parse(split[4], CultureInfo.InvariantCulture);
|
||||||
|
var startY = float.Parse(split[5], CultureInfo.InvariantCulture);
|
||||||
|
var endX = split.Length > 6 ? float.Parse(split[6], CultureInfo.InvariantCulture) : startX;
|
||||||
|
var endY = split.Length > 7 ? float.Parse(split[7], CultureInfo.InvariantCulture) : startY;
|
||||||
|
timelineGroup?.X.Add(easing, startTime, endTime, startX, endX);
|
||||||
|
timelineGroup?.Y.Add(easing, startTime, endTime, startY, endY);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "MX":
|
||||||
|
{
|
||||||
|
var startValue = float.Parse(split[4], CultureInfo.InvariantCulture);
|
||||||
|
var endValue = split.Length > 5 ? float.Parse(split[5], CultureInfo.InvariantCulture) : startValue;
|
||||||
|
timelineGroup?.X.Add(easing, startTime, endTime, startValue, endValue);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "MY":
|
||||||
|
{
|
||||||
|
var startValue = float.Parse(split[4], CultureInfo.InvariantCulture);
|
||||||
|
var endValue = split.Length > 5 ? float.Parse(split[5], CultureInfo.InvariantCulture) : startValue;
|
||||||
|
timelineGroup?.Y.Add(easing, startTime, endTime, startValue, endValue);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "C":
|
||||||
|
{
|
||||||
|
var startRed = float.Parse(split[4], CultureInfo.InvariantCulture);
|
||||||
|
var startGreen = float.Parse(split[5], CultureInfo.InvariantCulture);
|
||||||
|
var startBlue = float.Parse(split[6], CultureInfo.InvariantCulture);
|
||||||
|
var endRed = split.Length > 7 ? float.Parse(split[7], CultureInfo.InvariantCulture) : startRed;
|
||||||
|
var endGreen = split.Length > 8 ? float.Parse(split[8], CultureInfo.InvariantCulture) : startGreen;
|
||||||
|
var endBlue = split.Length > 9 ? float.Parse(split[9], CultureInfo.InvariantCulture) : startBlue;
|
||||||
|
timelineGroup?.Colour.Add(easing, startTime, endTime,
|
||||||
|
new Color4(startRed / 255f, startGreen / 255f, startBlue / 255f, 1),
|
||||||
|
new Color4(endRed / 255f, endGreen / 255f, endBlue / 255f, 1));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "P":
|
||||||
|
{
|
||||||
|
var type = split[4];
|
||||||
|
switch (type)
|
||||||
|
{
|
||||||
|
case "A":
|
||||||
|
timelineGroup?.BlendingMode.Add(easing, startTime, endTime, BlendingMode.Additive, startTime == endTime ? BlendingMode.Additive : BlendingMode.Inherit);
|
||||||
|
break;
|
||||||
|
case "H":
|
||||||
|
timelineGroup?.FlipH.Add(easing, startTime, endTime, true, startTime == endTime);
|
||||||
|
break;
|
||||||
|
case "V":
|
||||||
|
timelineGroup?.FlipV.Add(easing, startTime, endTime, true, startTime == endTime);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new InvalidDataException($@"Unknown command type: {commandType}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private string parseLayer(string value) => Enum.Parse(typeof(StoryLayer), value).ToString();
|
||||||
|
|
||||||
|
private Anchor parseOrigin(string value)
|
||||||
|
{
|
||||||
|
var origin = (LegacyOrigins)Enum.Parse(typeof(LegacyOrigins), value);
|
||||||
|
switch (origin)
|
||||||
|
{
|
||||||
|
case LegacyOrigins.TopLeft:
|
||||||
|
return Anchor.TopLeft;
|
||||||
|
case LegacyOrigins.TopCentre:
|
||||||
|
return Anchor.TopCentre;
|
||||||
|
case LegacyOrigins.TopRight:
|
||||||
|
return Anchor.TopRight;
|
||||||
|
case LegacyOrigins.CentreLeft:
|
||||||
|
return Anchor.CentreLeft;
|
||||||
|
case LegacyOrigins.Centre:
|
||||||
|
return Anchor.Centre;
|
||||||
|
case LegacyOrigins.CentreRight:
|
||||||
|
return Anchor.CentreRight;
|
||||||
|
case LegacyOrigins.BottomLeft:
|
||||||
|
return Anchor.BottomLeft;
|
||||||
|
case LegacyOrigins.BottomCentre:
|
||||||
|
return Anchor.BottomCentre;
|
||||||
|
case LegacyOrigins.BottomRight:
|
||||||
|
return Anchor.BottomRight;
|
||||||
|
}
|
||||||
|
throw new InvalidDataException($@"Unknown origin: {value}");
|
||||||
|
}
|
||||||
|
|
||||||
|
private string cleanFilename(string path) => FileSafety.PathStandardise(path.Trim('\"'));
|
||||||
|
}
|
||||||
|
}
|
@ -1,781 +0,0 @@
|
|||||||
// Copyright (c) 2007-2017 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.Globalization;
|
|
||||||
using System.IO;
|
|
||||||
using OpenTK.Graphics;
|
|
||||||
using osu.Game.Beatmaps.Timing;
|
|
||||||
using osu.Game.Beatmaps.Legacy;
|
|
||||||
using osu.Game.Rulesets.Objects.Legacy;
|
|
||||||
using osu.Game.Beatmaps.ControlPoints;
|
|
||||||
using osu.Game.Storyboards;
|
|
||||||
using OpenTK;
|
|
||||||
using osu.Framework.Graphics;
|
|
||||||
using osu.Framework.IO.File;
|
|
||||||
|
|
||||||
namespace osu.Game.Beatmaps.Formats
|
|
||||||
{
|
|
||||||
public class OsuLegacyDecoder : BeatmapDecoder
|
|
||||||
{
|
|
||||||
public static void Register()
|
|
||||||
{
|
|
||||||
AddDecoder<OsuLegacyDecoder>(@"osu file format v14");
|
|
||||||
AddDecoder<OsuLegacyDecoder>(@"osu file format v13");
|
|
||||||
AddDecoder<OsuLegacyDecoder>(@"osu file format v12");
|
|
||||||
AddDecoder<OsuLegacyDecoder>(@"osu file format v11");
|
|
||||||
AddDecoder<OsuLegacyDecoder>(@"osu file format v10");
|
|
||||||
AddDecoder<OsuLegacyDecoder>(@"osu file format v9");
|
|
||||||
AddDecoder<OsuLegacyDecoder>(@"osu file format v8");
|
|
||||||
AddDecoder<OsuLegacyDecoder>(@"osu file format v7");
|
|
||||||
AddDecoder<OsuLegacyDecoder>(@"osu file format v6");
|
|
||||||
AddDecoder<OsuLegacyDecoder>(@"osu file format v5");
|
|
||||||
AddDecoder<OsuLegacyDecoder>(@"osu file format v4");
|
|
||||||
AddDecoder<OsuLegacyDecoder>(@"osu file format v3");
|
|
||||||
// TODO: differences between versions
|
|
||||||
}
|
|
||||||
|
|
||||||
private ConvertHitObjectParser parser;
|
|
||||||
|
|
||||||
private readonly Dictionary<string, string> variables = new Dictionary<string, string>();
|
|
||||||
|
|
||||||
private LegacySampleBank defaultSampleBank;
|
|
||||||
private int defaultSampleVolume = 100;
|
|
||||||
|
|
||||||
private readonly int beatmapVersion;
|
|
||||||
|
|
||||||
public OsuLegacyDecoder()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public OsuLegacyDecoder(string header)
|
|
||||||
{
|
|
||||||
beatmapVersion = int.Parse(header.Substring(17));
|
|
||||||
}
|
|
||||||
|
|
||||||
private enum Section
|
|
||||||
{
|
|
||||||
None,
|
|
||||||
General,
|
|
||||||
Editor,
|
|
||||||
Metadata,
|
|
||||||
Difficulty,
|
|
||||||
Events,
|
|
||||||
TimingPoints,
|
|
||||||
Colours,
|
|
||||||
HitObjects,
|
|
||||||
Variables,
|
|
||||||
}
|
|
||||||
|
|
||||||
private void handleGeneral(Beatmap beatmap, string line)
|
|
||||||
{
|
|
||||||
if (beatmap == null)
|
|
||||||
throw new ArgumentNullException(nameof(beatmap));
|
|
||||||
if (line == null)
|
|
||||||
throw new ArgumentNullException(nameof(line));
|
|
||||||
|
|
||||||
var pair = splitKeyVal(line, ':');
|
|
||||||
|
|
||||||
var metadata = beatmap.BeatmapInfo.Metadata;
|
|
||||||
switch (pair.Key)
|
|
||||||
{
|
|
||||||
case @"AudioFilename":
|
|
||||||
metadata.AudioFile = pair.Value;
|
|
||||||
break;
|
|
||||||
case @"AudioLeadIn":
|
|
||||||
beatmap.BeatmapInfo.AudioLeadIn = int.Parse(pair.Value);
|
|
||||||
break;
|
|
||||||
case @"PreviewTime":
|
|
||||||
metadata.PreviewTime = int.Parse(pair.Value);
|
|
||||||
break;
|
|
||||||
case @"Countdown":
|
|
||||||
beatmap.BeatmapInfo.Countdown = int.Parse(pair.Value) == 1;
|
|
||||||
break;
|
|
||||||
case @"SampleSet":
|
|
||||||
defaultSampleBank = (LegacySampleBank)Enum.Parse(typeof(LegacySampleBank), pair.Value);
|
|
||||||
break;
|
|
||||||
case @"SampleVolume":
|
|
||||||
defaultSampleVolume = int.Parse(pair.Value);
|
|
||||||
break;
|
|
||||||
case @"StackLeniency":
|
|
||||||
beatmap.BeatmapInfo.StackLeniency = float.Parse(pair.Value, NumberFormatInfo.InvariantInfo);
|
|
||||||
break;
|
|
||||||
case @"Mode":
|
|
||||||
beatmap.BeatmapInfo.RulesetID = int.Parse(pair.Value);
|
|
||||||
|
|
||||||
switch (beatmap.BeatmapInfo.RulesetID)
|
|
||||||
{
|
|
||||||
case 0:
|
|
||||||
parser = new Rulesets.Objects.Legacy.Osu.ConvertHitObjectParser();
|
|
||||||
break;
|
|
||||||
case 1:
|
|
||||||
parser = new Rulesets.Objects.Legacy.Taiko.ConvertHitObjectParser();
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
parser = new Rulesets.Objects.Legacy.Catch.ConvertHitObjectParser();
|
|
||||||
break;
|
|
||||||
case 3:
|
|
||||||
parser = new Rulesets.Objects.Legacy.Mania.ConvertHitObjectParser();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case @"LetterboxInBreaks":
|
|
||||||
beatmap.BeatmapInfo.LetterboxInBreaks = int.Parse(pair.Value) == 1;
|
|
||||||
break;
|
|
||||||
case @"SpecialStyle":
|
|
||||||
beatmap.BeatmapInfo.SpecialStyle = int.Parse(pair.Value) == 1;
|
|
||||||
break;
|
|
||||||
case @"WidescreenStoryboard":
|
|
||||||
beatmap.BeatmapInfo.WidescreenStoryboard = int.Parse(pair.Value) == 1;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void handleEditor(Beatmap beatmap, string line)
|
|
||||||
{
|
|
||||||
if (beatmap == null)
|
|
||||||
throw new ArgumentNullException(nameof(beatmap));
|
|
||||||
if (line == null)
|
|
||||||
throw new ArgumentNullException(nameof(line));
|
|
||||||
|
|
||||||
var pair = splitKeyVal(line, ':');
|
|
||||||
|
|
||||||
switch (pair.Key)
|
|
||||||
{
|
|
||||||
case @"Bookmarks":
|
|
||||||
beatmap.BeatmapInfo.StoredBookmarks = pair.Value;
|
|
||||||
break;
|
|
||||||
case @"DistanceSpacing":
|
|
||||||
beatmap.BeatmapInfo.DistanceSpacing = double.Parse(pair.Value, NumberFormatInfo.InvariantInfo);
|
|
||||||
break;
|
|
||||||
case @"BeatDivisor":
|
|
||||||
beatmap.BeatmapInfo.BeatDivisor = int.Parse(pair.Value);
|
|
||||||
break;
|
|
||||||
case @"GridSize":
|
|
||||||
beatmap.BeatmapInfo.GridSize = int.Parse(pair.Value);
|
|
||||||
break;
|
|
||||||
case @"TimelineZoom":
|
|
||||||
beatmap.BeatmapInfo.TimelineZoom = double.Parse(pair.Value, NumberFormatInfo.InvariantInfo);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void handleMetadata(Beatmap beatmap, string line)
|
|
||||||
{
|
|
||||||
if (beatmap == null)
|
|
||||||
throw new ArgumentNullException(nameof(beatmap));
|
|
||||||
if (line == null)
|
|
||||||
throw new ArgumentNullException(nameof(line));
|
|
||||||
|
|
||||||
var pair = splitKeyVal(line, ':');
|
|
||||||
|
|
||||||
var metadata = beatmap.BeatmapInfo.Metadata;
|
|
||||||
switch (pair.Key)
|
|
||||||
{
|
|
||||||
case @"Title":
|
|
||||||
metadata.Title = pair.Value;
|
|
||||||
break;
|
|
||||||
case @"TitleUnicode":
|
|
||||||
metadata.TitleUnicode = pair.Value;
|
|
||||||
break;
|
|
||||||
case @"Artist":
|
|
||||||
metadata.Artist = pair.Value;
|
|
||||||
break;
|
|
||||||
case @"ArtistUnicode":
|
|
||||||
metadata.ArtistUnicode = pair.Value;
|
|
||||||
break;
|
|
||||||
case @"Creator":
|
|
||||||
metadata.AuthorString = pair.Value;
|
|
||||||
break;
|
|
||||||
case @"Version":
|
|
||||||
beatmap.BeatmapInfo.Version = pair.Value;
|
|
||||||
break;
|
|
||||||
case @"Source":
|
|
||||||
beatmap.BeatmapInfo.Metadata.Source = pair.Value;
|
|
||||||
break;
|
|
||||||
case @"Tags":
|
|
||||||
beatmap.BeatmapInfo.Metadata.Tags = pair.Value;
|
|
||||||
break;
|
|
||||||
case @"BeatmapID":
|
|
||||||
beatmap.BeatmapInfo.OnlineBeatmapID = int.Parse(pair.Value);
|
|
||||||
break;
|
|
||||||
case @"BeatmapSetID":
|
|
||||||
beatmap.BeatmapInfo.OnlineBeatmapSetID = int.Parse(pair.Value);
|
|
||||||
metadata.OnlineBeatmapSetID = int.Parse(pair.Value);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void handleDifficulty(Beatmap beatmap, string line)
|
|
||||||
{
|
|
||||||
if (beatmap == null)
|
|
||||||
throw new ArgumentNullException(nameof(beatmap));
|
|
||||||
if (line == null)
|
|
||||||
throw new ArgumentNullException(nameof(line));
|
|
||||||
|
|
||||||
var pair = splitKeyVal(line, ':');
|
|
||||||
|
|
||||||
var difficulty = beatmap.BeatmapInfo.BaseDifficulty;
|
|
||||||
switch (pair.Key)
|
|
||||||
{
|
|
||||||
case @"HPDrainRate":
|
|
||||||
difficulty.DrainRate = float.Parse(pair.Value, NumberFormatInfo.InvariantInfo);
|
|
||||||
break;
|
|
||||||
case @"CircleSize":
|
|
||||||
difficulty.CircleSize = float.Parse(pair.Value, NumberFormatInfo.InvariantInfo);
|
|
||||||
break;
|
|
||||||
case @"OverallDifficulty":
|
|
||||||
difficulty.OverallDifficulty = float.Parse(pair.Value, NumberFormatInfo.InvariantInfo);
|
|
||||||
break;
|
|
||||||
case @"ApproachRate":
|
|
||||||
difficulty.ApproachRate = float.Parse(pair.Value, NumberFormatInfo.InvariantInfo);
|
|
||||||
break;
|
|
||||||
case @"SliderMultiplier":
|
|
||||||
difficulty.SliderMultiplier = float.Parse(pair.Value, NumberFormatInfo.InvariantInfo);
|
|
||||||
break;
|
|
||||||
case @"SliderTickRate":
|
|
||||||
difficulty.SliderTickRate = float.Parse(pair.Value, NumberFormatInfo.InvariantInfo);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Decodes any beatmap variables present in a line into their real values.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="line">The line which may contains variables.</param>
|
|
||||||
private void decodeVariables(ref string line)
|
|
||||||
{
|
|
||||||
if (line == null)
|
|
||||||
throw new ArgumentNullException(nameof(line));
|
|
||||||
|
|
||||||
while (line.IndexOf('$') >= 0)
|
|
||||||
{
|
|
||||||
string origLine = line;
|
|
||||||
string[] split = line.Split(',');
|
|
||||||
for (int i = 0; i < split.Length; i++)
|
|
||||||
{
|
|
||||||
var item = split[i];
|
|
||||||
if (item.StartsWith("$") && variables.ContainsKey(item))
|
|
||||||
split[i] = variables[item];
|
|
||||||
}
|
|
||||||
|
|
||||||
line = string.Join(",", split);
|
|
||||||
if (line == origLine) break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void handleEvents(Beatmap beatmap, string line, ref StoryboardSprite storyboardSprite, ref CommandTimelineGroup timelineGroup)
|
|
||||||
{
|
|
||||||
if (line == null)
|
|
||||||
throw new ArgumentNullException(nameof(line));
|
|
||||||
if (beatmap == null)
|
|
||||||
throw new ArgumentNullException(nameof(beatmap));
|
|
||||||
|
|
||||||
var depth = 0;
|
|
||||||
while (line.StartsWith(" ") || line.StartsWith("_"))
|
|
||||||
{
|
|
||||||
++depth;
|
|
||||||
line = line.Substring(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
decodeVariables(ref line);
|
|
||||||
|
|
||||||
string[] split = line.Split(',');
|
|
||||||
|
|
||||||
if (depth == 0)
|
|
||||||
{
|
|
||||||
storyboardSprite = null;
|
|
||||||
|
|
||||||
EventType type;
|
|
||||||
if (!Enum.TryParse(split[0], out type))
|
|
||||||
throw new InvalidDataException($@"Unknown event type {split[0]}");
|
|
||||||
|
|
||||||
switch (type)
|
|
||||||
{
|
|
||||||
case EventType.Video:
|
|
||||||
case EventType.Background:
|
|
||||||
string filename = split[2].Trim('"');
|
|
||||||
|
|
||||||
if (type == EventType.Background)
|
|
||||||
beatmap.BeatmapInfo.Metadata.BackgroundFile = filename;
|
|
||||||
|
|
||||||
break;
|
|
||||||
case EventType.Break:
|
|
||||||
var breakEvent = new BreakPeriod
|
|
||||||
{
|
|
||||||
StartTime = double.Parse(split[1], NumberFormatInfo.InvariantInfo),
|
|
||||||
EndTime = double.Parse(split[2], NumberFormatInfo.InvariantInfo)
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!breakEvent.HasEffect)
|
|
||||||
return;
|
|
||||||
|
|
||||||
beatmap.Breaks.Add(breakEvent);
|
|
||||||
break;
|
|
||||||
case EventType.Sprite:
|
|
||||||
{
|
|
||||||
var layer = parseLayer(split[1]);
|
|
||||||
var origin = parseOrigin(split[2]);
|
|
||||||
var path = cleanFilename(split[3]);
|
|
||||||
var x = float.Parse(split[4], NumberFormatInfo.InvariantInfo);
|
|
||||||
var y = float.Parse(split[5], NumberFormatInfo.InvariantInfo);
|
|
||||||
storyboardSprite = new StoryboardSprite(path, origin, new Vector2(x, y));
|
|
||||||
beatmap.Storyboard.GetLayer(layer).Add(storyboardSprite);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case EventType.Animation:
|
|
||||||
{
|
|
||||||
var layer = parseLayer(split[1]);
|
|
||||||
var origin = parseOrigin(split[2]);
|
|
||||||
var path = cleanFilename(split[3]);
|
|
||||||
var x = float.Parse(split[4], NumberFormatInfo.InvariantInfo);
|
|
||||||
var y = float.Parse(split[5], NumberFormatInfo.InvariantInfo);
|
|
||||||
var frameCount = int.Parse(split[6]);
|
|
||||||
var frameDelay = double.Parse(split[7], NumberFormatInfo.InvariantInfo);
|
|
||||||
var loopType = split.Length > 8 ? (AnimationLoopType)Enum.Parse(typeof(AnimationLoopType), split[8]) : AnimationLoopType.LoopForever;
|
|
||||||
storyboardSprite = new StoryboardAnimation(path, origin, new Vector2(x, y), frameCount, frameDelay, loopType);
|
|
||||||
beatmap.Storyboard.GetLayer(layer).Add(storyboardSprite);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case EventType.Sample:
|
|
||||||
{
|
|
||||||
var time = double.Parse(split[1], CultureInfo.InvariantCulture);
|
|
||||||
var layer = parseLayer(split[2]);
|
|
||||||
var path = cleanFilename(split[3]);
|
|
||||||
var volume = split.Length > 4 ? float.Parse(split[4], CultureInfo.InvariantCulture) : 100;
|
|
||||||
beatmap.Storyboard.GetLayer(layer).Add(new StoryboardSample(path, time, volume));
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (depth < 2)
|
|
||||||
timelineGroup = storyboardSprite?.TimelineGroup;
|
|
||||||
|
|
||||||
var commandType = split[0];
|
|
||||||
switch (commandType)
|
|
||||||
{
|
|
||||||
case "T":
|
|
||||||
{
|
|
||||||
var triggerName = split[1];
|
|
||||||
var startTime = split.Length > 2 ? double.Parse(split[2], CultureInfo.InvariantCulture) : double.MinValue;
|
|
||||||
var endTime = split.Length > 3 ? double.Parse(split[3], CultureInfo.InvariantCulture) : double.MaxValue;
|
|
||||||
var groupNumber = split.Length > 4 ? int.Parse(split[4]) : 0;
|
|
||||||
timelineGroup = storyboardSprite?.AddTrigger(triggerName, startTime, endTime, groupNumber);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case "L":
|
|
||||||
{
|
|
||||||
var startTime = double.Parse(split[1], CultureInfo.InvariantCulture);
|
|
||||||
var loopCount = int.Parse(split[2]);
|
|
||||||
timelineGroup = storyboardSprite?.AddLoop(startTime, loopCount);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
{
|
|
||||||
if (string.IsNullOrEmpty(split[3]))
|
|
||||||
split[3] = split[2];
|
|
||||||
|
|
||||||
var easing = (Easing)int.Parse(split[1]);
|
|
||||||
var startTime = double.Parse(split[2], CultureInfo.InvariantCulture);
|
|
||||||
var endTime = double.Parse(split[3], CultureInfo.InvariantCulture);
|
|
||||||
|
|
||||||
switch (commandType)
|
|
||||||
{
|
|
||||||
case "F":
|
|
||||||
{
|
|
||||||
var startValue = float.Parse(split[4], CultureInfo.InvariantCulture);
|
|
||||||
var endValue = split.Length > 5 ? float.Parse(split[5], CultureInfo.InvariantCulture) : startValue;
|
|
||||||
timelineGroup?.Alpha.Add(easing, startTime, endTime, startValue, endValue);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case "S":
|
|
||||||
{
|
|
||||||
var startValue = float.Parse(split[4], CultureInfo.InvariantCulture);
|
|
||||||
var endValue = split.Length > 5 ? float.Parse(split[5], CultureInfo.InvariantCulture) : startValue;
|
|
||||||
timelineGroup?.Scale.Add(easing, startTime, endTime, new Vector2(startValue), new Vector2(endValue));
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case "V":
|
|
||||||
{
|
|
||||||
var startX = float.Parse(split[4], CultureInfo.InvariantCulture);
|
|
||||||
var startY = float.Parse(split[5], CultureInfo.InvariantCulture);
|
|
||||||
var endX = split.Length > 6 ? float.Parse(split[6], CultureInfo.InvariantCulture) : startX;
|
|
||||||
var endY = split.Length > 7 ? float.Parse(split[7], CultureInfo.InvariantCulture) : startY;
|
|
||||||
timelineGroup?.Scale.Add(easing, startTime, endTime, new Vector2(startX, startY), new Vector2(endX, endY));
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case "R":
|
|
||||||
{
|
|
||||||
var startValue = float.Parse(split[4], CultureInfo.InvariantCulture);
|
|
||||||
var endValue = split.Length > 5 ? float.Parse(split[5], CultureInfo.InvariantCulture) : startValue;
|
|
||||||
timelineGroup?.Rotation.Add(easing, startTime, endTime, MathHelper.RadiansToDegrees(startValue), MathHelper.RadiansToDegrees(endValue));
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case "M":
|
|
||||||
{
|
|
||||||
var startX = float.Parse(split[4], CultureInfo.InvariantCulture);
|
|
||||||
var startY = float.Parse(split[5], CultureInfo.InvariantCulture);
|
|
||||||
var endX = split.Length > 6 ? float.Parse(split[6], CultureInfo.InvariantCulture) : startX;
|
|
||||||
var endY = split.Length > 7 ? float.Parse(split[7], CultureInfo.InvariantCulture) : startY;
|
|
||||||
timelineGroup?.X.Add(easing, startTime, endTime, startX, endX);
|
|
||||||
timelineGroup?.Y.Add(easing, startTime, endTime, startY, endY);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case "MX":
|
|
||||||
{
|
|
||||||
var startValue = float.Parse(split[4], CultureInfo.InvariantCulture);
|
|
||||||
var endValue = split.Length > 5 ? float.Parse(split[5], CultureInfo.InvariantCulture) : startValue;
|
|
||||||
timelineGroup?.X.Add(easing, startTime, endTime, startValue, endValue);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case "MY":
|
|
||||||
{
|
|
||||||
var startValue = float.Parse(split[4], CultureInfo.InvariantCulture);
|
|
||||||
var endValue = split.Length > 5 ? float.Parse(split[5], CultureInfo.InvariantCulture) : startValue;
|
|
||||||
timelineGroup?.Y.Add(easing, startTime, endTime, startValue, endValue);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case "C":
|
|
||||||
{
|
|
||||||
var startRed = float.Parse(split[4], CultureInfo.InvariantCulture);
|
|
||||||
var startGreen = float.Parse(split[5], CultureInfo.InvariantCulture);
|
|
||||||
var startBlue = float.Parse(split[6], CultureInfo.InvariantCulture);
|
|
||||||
var endRed = split.Length > 7 ? float.Parse(split[7], CultureInfo.InvariantCulture) : startRed;
|
|
||||||
var endGreen = split.Length > 8 ? float.Parse(split[8], CultureInfo.InvariantCulture) : startGreen;
|
|
||||||
var endBlue = split.Length > 9 ? float.Parse(split[9], CultureInfo.InvariantCulture) : startBlue;
|
|
||||||
timelineGroup?.Colour.Add(easing, startTime, endTime,
|
|
||||||
new Color4(startRed / 255f, startGreen / 255f, startBlue / 255f, 1),
|
|
||||||
new Color4(endRed / 255f, endGreen / 255f, endBlue / 255f, 1));
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case "P":
|
|
||||||
{
|
|
||||||
var type = split[4];
|
|
||||||
switch (type)
|
|
||||||
{
|
|
||||||
case "A": timelineGroup?.BlendingMode.Add(easing, startTime, endTime, BlendingMode.Additive, startTime == endTime ? BlendingMode.Additive : BlendingMode.Inherit); break;
|
|
||||||
case "H": timelineGroup?.FlipH.Add(easing, startTime, endTime, true, startTime == endTime); break;
|
|
||||||
case "V": timelineGroup?.FlipV.Add(easing, startTime, endTime, true, startTime == endTime); break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new InvalidDataException($@"Unknown command type: {commandType}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string cleanFilename(string path)
|
|
||||||
=> FileSafety.PathStandardise(path.Trim('\"'));
|
|
||||||
|
|
||||||
private static Anchor parseOrigin(string value)
|
|
||||||
{
|
|
||||||
var origin = (LegacyOrigins)Enum.Parse(typeof(LegacyOrigins), value);
|
|
||||||
switch (origin)
|
|
||||||
{
|
|
||||||
case LegacyOrigins.TopLeft: return Anchor.TopLeft;
|
|
||||||
case LegacyOrigins.TopCentre: return Anchor.TopCentre;
|
|
||||||
case LegacyOrigins.TopRight: return Anchor.TopRight;
|
|
||||||
case LegacyOrigins.CentreLeft: return Anchor.CentreLeft;
|
|
||||||
case LegacyOrigins.Centre: return Anchor.Centre;
|
|
||||||
case LegacyOrigins.CentreRight: return Anchor.CentreRight;
|
|
||||||
case LegacyOrigins.BottomLeft: return Anchor.BottomLeft;
|
|
||||||
case LegacyOrigins.BottomCentre: return Anchor.BottomCentre;
|
|
||||||
case LegacyOrigins.BottomRight: return Anchor.BottomRight;
|
|
||||||
}
|
|
||||||
throw new InvalidDataException($@"Unknown origin: {value}");
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string parseLayer(string value)
|
|
||||||
=> Enum.Parse(typeof(StoryLayer), value).ToString();
|
|
||||||
|
|
||||||
private void handleTimingPoints(Beatmap beatmap, string line)
|
|
||||||
{
|
|
||||||
if (beatmap == null)
|
|
||||||
throw new ArgumentNullException(nameof(beatmap));
|
|
||||||
if (line == null)
|
|
||||||
throw new ArgumentNullException(nameof(line));
|
|
||||||
|
|
||||||
string[] split = line.Split(',');
|
|
||||||
|
|
||||||
double time = double.Parse(split[0].Trim(), NumberFormatInfo.InvariantInfo);
|
|
||||||
double beatLength = double.Parse(split[1].Trim(), NumberFormatInfo.InvariantInfo);
|
|
||||||
double speedMultiplier = beatLength < 0 ? 100.0 / -beatLength : 1;
|
|
||||||
|
|
||||||
TimeSignatures timeSignature = TimeSignatures.SimpleQuadruple;
|
|
||||||
if (split.Length >= 3)
|
|
||||||
timeSignature = split[2][0] == '0' ? TimeSignatures.SimpleQuadruple : (TimeSignatures)int.Parse(split[2]);
|
|
||||||
|
|
||||||
LegacySampleBank sampleSet = defaultSampleBank;
|
|
||||||
if (split.Length >= 4)
|
|
||||||
sampleSet = (LegacySampleBank)int.Parse(split[3]);
|
|
||||||
|
|
||||||
//SampleBank sampleBank = SampleBank.Default;
|
|
||||||
//if (split.Length >= 5)
|
|
||||||
// sampleBank = (SampleBank)int.Parse(split[4]);
|
|
||||||
|
|
||||||
int sampleVolume = defaultSampleVolume;
|
|
||||||
if (split.Length >= 6)
|
|
||||||
sampleVolume = int.Parse(split[5]);
|
|
||||||
|
|
||||||
bool timingChange = true;
|
|
||||||
if (split.Length >= 7)
|
|
||||||
timingChange = split[6][0] == '1';
|
|
||||||
|
|
||||||
bool kiaiMode = false;
|
|
||||||
bool omitFirstBarSignature = false;
|
|
||||||
if (split.Length >= 8)
|
|
||||||
{
|
|
||||||
int effectFlags = int.Parse(split[7]);
|
|
||||||
kiaiMode = (effectFlags & 1) > 0;
|
|
||||||
omitFirstBarSignature = (effectFlags & 8) > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
string stringSampleSet = sampleSet.ToString().ToLower();
|
|
||||||
if (stringSampleSet == @"none")
|
|
||||||
stringSampleSet = @"normal";
|
|
||||||
|
|
||||||
DifficultyControlPoint difficultyPoint = beatmap.ControlPointInfo.DifficultyPointAt(time);
|
|
||||||
SoundControlPoint soundPoint = beatmap.ControlPointInfo.SoundPointAt(time);
|
|
||||||
EffectControlPoint effectPoint = beatmap.ControlPointInfo.EffectPointAt(time);
|
|
||||||
|
|
||||||
if (timingChange)
|
|
||||||
{
|
|
||||||
beatmap.ControlPointInfo.TimingPoints.Add(new TimingControlPoint
|
|
||||||
{
|
|
||||||
Time = time,
|
|
||||||
BeatLength = beatLength,
|
|
||||||
TimeSignature = timeSignature
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (speedMultiplier != difficultyPoint.SpeedMultiplier)
|
|
||||||
{
|
|
||||||
beatmap.ControlPointInfo.DifficultyPoints.RemoveAll(x => x.Time == time);
|
|
||||||
beatmap.ControlPointInfo.DifficultyPoints.Add(new DifficultyControlPoint
|
|
||||||
{
|
|
||||||
Time = time,
|
|
||||||
SpeedMultiplier = speedMultiplier
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (stringSampleSet != soundPoint.SampleBank || sampleVolume != soundPoint.SampleVolume)
|
|
||||||
{
|
|
||||||
beatmap.ControlPointInfo.SoundPoints.Add(new SoundControlPoint
|
|
||||||
{
|
|
||||||
Time = time,
|
|
||||||
SampleBank = stringSampleSet,
|
|
||||||
SampleVolume = sampleVolume
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (kiaiMode != effectPoint.KiaiMode || omitFirstBarSignature != effectPoint.OmitFirstBarLine)
|
|
||||||
{
|
|
||||||
beatmap.ControlPointInfo.EffectPoints.Add(new EffectControlPoint
|
|
||||||
{
|
|
||||||
Time = time,
|
|
||||||
KiaiMode = kiaiMode,
|
|
||||||
OmitFirstBarLine = omitFirstBarSignature
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void handleColours(Beatmap beatmap, string line, ref bool hasCustomColours)
|
|
||||||
{
|
|
||||||
if (beatmap == null)
|
|
||||||
throw new ArgumentNullException(nameof(beatmap));
|
|
||||||
if (line == null)
|
|
||||||
throw new ArgumentNullException(nameof(line));
|
|
||||||
|
|
||||||
var pair = splitKeyVal(line, ':');
|
|
||||||
|
|
||||||
string[] split = pair.Value.Split(',');
|
|
||||||
|
|
||||||
if (split.Length != 3)
|
|
||||||
throw new InvalidOperationException($@"Color specified in incorrect format (should be R,G,B): {pair.Value}");
|
|
||||||
|
|
||||||
byte r, g, b;
|
|
||||||
if (!byte.TryParse(split[0], out r) || !byte.TryParse(split[1], out g) || !byte.TryParse(split[2], out b))
|
|
||||||
throw new InvalidOperationException(@"Color must be specified with 8-bit integer components");
|
|
||||||
|
|
||||||
if (!hasCustomColours)
|
|
||||||
{
|
|
||||||
beatmap.ComboColors.Clear();
|
|
||||||
hasCustomColours = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Note: the combo index specified in the beatmap is discarded
|
|
||||||
if (pair.Key.StartsWith(@"Combo"))
|
|
||||||
{
|
|
||||||
beatmap.ComboColors.Add(new Color4
|
|
||||||
{
|
|
||||||
R = r / 255f,
|
|
||||||
G = g / 255f,
|
|
||||||
B = b / 255f,
|
|
||||||
A = 1f,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void handleVariables(string line)
|
|
||||||
{
|
|
||||||
if (line == null)
|
|
||||||
throw new ArgumentNullException(nameof(line));
|
|
||||||
|
|
||||||
var pair = splitKeyVal(line, '=');
|
|
||||||
variables[pair.Key] = pair.Value;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override Beatmap ParseFile(StreamReader stream)
|
|
||||||
{
|
|
||||||
return new LegacyBeatmap(base.ParseFile(stream));
|
|
||||||
}
|
|
||||||
|
|
||||||
public override Beatmap Decode(StreamReader stream)
|
|
||||||
{
|
|
||||||
return new LegacyBeatmap(base.Decode(stream));
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void ParseFile(StreamReader stream, Beatmap beatmap)
|
|
||||||
{
|
|
||||||
if (beatmap == null)
|
|
||||||
throw new ArgumentNullException(nameof(beatmap));
|
|
||||||
if (stream == null)
|
|
||||||
throw new ArgumentNullException(nameof(stream));
|
|
||||||
|
|
||||||
beatmap.BeatmapInfo.BeatmapVersion = beatmapVersion;
|
|
||||||
|
|
||||||
Section section = Section.None;
|
|
||||||
bool hasCustomColours = false;
|
|
||||||
StoryboardSprite storyboardSprite = null;
|
|
||||||
CommandTimelineGroup timelineGroup = null;
|
|
||||||
|
|
||||||
string line;
|
|
||||||
while ((line = stream.ReadLine()) != null)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrWhiteSpace(line))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (line.StartsWith("//"))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (line.StartsWith(@"osu file format v"))
|
|
||||||
{
|
|
||||||
beatmap.BeatmapInfo.BeatmapVersion = int.Parse(line.Substring(17));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (line.StartsWith(@"[") && line.EndsWith(@"]"))
|
|
||||||
{
|
|
||||||
if (!Enum.TryParse(line.Substring(1, line.Length - 2), out section))
|
|
||||||
throw new InvalidDataException($@"Unknown osu section {line}");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (section)
|
|
||||||
{
|
|
||||||
case Section.General:
|
|
||||||
handleGeneral(beatmap, line);
|
|
||||||
break;
|
|
||||||
case Section.Editor:
|
|
||||||
handleEditor(beatmap, line);
|
|
||||||
break;
|
|
||||||
case Section.Metadata:
|
|
||||||
handleMetadata(beatmap, line);
|
|
||||||
break;
|
|
||||||
case Section.Difficulty:
|
|
||||||
handleDifficulty(beatmap, line);
|
|
||||||
break;
|
|
||||||
case Section.Events:
|
|
||||||
handleEvents(beatmap, line, ref storyboardSprite, ref timelineGroup);
|
|
||||||
break;
|
|
||||||
case Section.TimingPoints:
|
|
||||||
handleTimingPoints(beatmap, line);
|
|
||||||
break;
|
|
||||||
case Section.Colours:
|
|
||||||
handleColours(beatmap, line, ref hasCustomColours);
|
|
||||||
break;
|
|
||||||
case Section.HitObjects:
|
|
||||||
|
|
||||||
// If the ruleset wasn't specified, assume the osu!standard ruleset.
|
|
||||||
if (parser == null)
|
|
||||||
parser = new Rulesets.Objects.Legacy.Osu.ConvertHitObjectParser();
|
|
||||||
|
|
||||||
var obj = parser.Parse(line);
|
|
||||||
|
|
||||||
if (obj != null)
|
|
||||||
beatmap.HitObjects.Add(obj);
|
|
||||||
|
|
||||||
break;
|
|
||||||
case Section.Variables:
|
|
||||||
handleVariables(line);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var hitObject in beatmap.HitObjects)
|
|
||||||
hitObject.ApplyDefaults(beatmap.ControlPointInfo, beatmap.BeatmapInfo.BaseDifficulty);
|
|
||||||
}
|
|
||||||
|
|
||||||
private KeyValuePair<string, string> splitKeyVal(string line, char separator)
|
|
||||||
{
|
|
||||||
if (line == null)
|
|
||||||
throw new ArgumentNullException(nameof(line));
|
|
||||||
|
|
||||||
var split = line.Trim().Split(new[] { separator }, 2);
|
|
||||||
|
|
||||||
return new KeyValuePair<string, string>
|
|
||||||
(
|
|
||||||
split[0].Trim(),
|
|
||||||
split.Length > 1 ? split[1].Trim() : string.Empty
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
internal enum LegacySampleBank
|
|
||||||
{
|
|
||||||
None = 0,
|
|
||||||
Normal = 1,
|
|
||||||
Soft = 2,
|
|
||||||
Drum = 3
|
|
||||||
}
|
|
||||||
|
|
||||||
internal enum EventType
|
|
||||||
{
|
|
||||||
Background = 0,
|
|
||||||
Video = 1,
|
|
||||||
Break = 2,
|
|
||||||
Colour = 3,
|
|
||||||
Sprite = 4,
|
|
||||||
Sample = 5,
|
|
||||||
Animation = 6
|
|
||||||
}
|
|
||||||
|
|
||||||
internal enum LegacyOrigins
|
|
||||||
{
|
|
||||||
TopLeft,
|
|
||||||
Centre,
|
|
||||||
CentreLeft,
|
|
||||||
TopRight,
|
|
||||||
BottomCentre,
|
|
||||||
TopCentre,
|
|
||||||
Custom,
|
|
||||||
CentreRight,
|
|
||||||
BottomLeft,
|
|
||||||
BottomRight
|
|
||||||
};
|
|
||||||
|
|
||||||
internal enum StoryLayer
|
|
||||||
{
|
|
||||||
Background = 0,
|
|
||||||
Fail = 1,
|
|
||||||
Pass = 2,
|
|
||||||
Foreground = 3
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -9,6 +9,7 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using osu.Game.Storyboards;
|
||||||
|
|
||||||
namespace osu.Game.Beatmaps
|
namespace osu.Game.Beatmaps
|
||||||
{
|
{
|
||||||
@ -31,17 +32,19 @@ namespace osu.Game.Beatmaps
|
|||||||
Mods.ValueChanged += mods => applyRateAdjustments();
|
Mods.ValueChanged += mods => applyRateAdjustments();
|
||||||
|
|
||||||
beatmap = new AsyncLazy<Beatmap>(populateBeatmap);
|
beatmap = new AsyncLazy<Beatmap>(populateBeatmap);
|
||||||
background = new AsyncLazy<Texture>(populateBackground);
|
background = new AsyncLazy<Texture>(populateBackground, b => b == null || !b.IsDisposed);
|
||||||
track = new AsyncLazy<Track>(populateTrack);
|
track = new AsyncLazy<Track>(populateTrack);
|
||||||
waveform = new AsyncLazy<Waveform>(populateWaveform);
|
waveform = new AsyncLazy<Waveform>(populateWaveform);
|
||||||
|
storyboard = new AsyncLazy<Storyboard>(populateStoryboard);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract Beatmap GetBeatmap();
|
protected abstract Beatmap GetBeatmap();
|
||||||
protected abstract Texture GetBackground();
|
protected abstract Texture GetBackground();
|
||||||
protected abstract Track GetTrack();
|
protected abstract Track GetTrack();
|
||||||
protected virtual Waveform GetWaveform() => new Waveform();
|
protected virtual Waveform GetWaveform() => new Waveform();
|
||||||
|
protected virtual Storyboard GetStoryboard() => new Storyboard();
|
||||||
|
|
||||||
public bool BeatmapLoaded => beatmap.IsValueCreated;
|
public bool BeatmapLoaded => beatmap.IsResultAvailable;
|
||||||
public Beatmap Beatmap => beatmap.Value.Result;
|
public Beatmap Beatmap => beatmap.Value.Result;
|
||||||
public async Task<Beatmap> GetBeatmapAsync() => await beatmap.Value;
|
public async Task<Beatmap> GetBeatmapAsync() => await beatmap.Value;
|
||||||
|
|
||||||
@ -57,14 +60,14 @@ namespace osu.Game.Beatmaps
|
|||||||
return b;
|
return b;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool BackgroundLoaded => background.IsValueCreated;
|
public bool BackgroundLoaded => background.IsResultAvailable;
|
||||||
public Texture Background => background.Value.Result;
|
public Texture Background => background.Value.Result;
|
||||||
public async Task<Texture> GetBackgroundAsync() => await background.Value;
|
public async Task<Texture> GetBackgroundAsync() => await background.Value;
|
||||||
private AsyncLazy<Texture> background;
|
private AsyncLazy<Texture> background;
|
||||||
|
|
||||||
private Texture populateBackground() => GetBackground();
|
private Texture populateBackground() => GetBackground();
|
||||||
|
|
||||||
public bool TrackLoaded => track.IsValueCreated;
|
public bool TrackLoaded => track.IsResultAvailable;
|
||||||
public Track Track => track.Value.Result;
|
public Track Track => track.Value.Result;
|
||||||
public async Task<Track> GetTrackAsync() => await track.Value;
|
public async Task<Track> GetTrackAsync() => await track.Value;
|
||||||
private AsyncLazy<Track> track;
|
private AsyncLazy<Track> track;
|
||||||
@ -77,19 +80,26 @@ namespace osu.Game.Beatmaps
|
|||||||
return t;
|
return t;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool WaveformLoaded => waveform.IsValueCreated;
|
public bool WaveformLoaded => waveform.IsResultAvailable;
|
||||||
public Waveform Waveform => waveform.Value.Result;
|
public Waveform Waveform => waveform.Value.Result;
|
||||||
public async Task<Waveform> GetWaveformAsync() => await waveform.Value;
|
public async Task<Waveform> GetWaveformAsync() => await waveform.Value;
|
||||||
private readonly AsyncLazy<Waveform> waveform;
|
private readonly AsyncLazy<Waveform> waveform;
|
||||||
|
|
||||||
private Waveform populateWaveform() => GetWaveform();
|
private Waveform populateWaveform() => GetWaveform();
|
||||||
|
|
||||||
|
public bool StoryboardLoaded => storyboard.IsResultAvailable;
|
||||||
|
public Storyboard Storyboard => storyboard.Value.Result;
|
||||||
|
public async Task<Storyboard> GetStoryboardAsync() => await storyboard.Value;
|
||||||
|
private readonly AsyncLazy<Storyboard> storyboard;
|
||||||
|
|
||||||
|
private Storyboard populateStoryboard() => GetStoryboard();
|
||||||
|
|
||||||
public void TransferTo(WorkingBeatmap other)
|
public void TransferTo(WorkingBeatmap other)
|
||||||
{
|
{
|
||||||
if (track.IsValueCreated && Track != null && BeatmapInfo.AudioEquals(other.BeatmapInfo))
|
if (track.IsResultAvailable && Track != null && BeatmapInfo.AudioEquals(other.BeatmapInfo))
|
||||||
other.track = track;
|
other.track = track;
|
||||||
|
|
||||||
if (background.IsValueCreated && Background != null && BeatmapInfo.BackgroundEquals(other.BeatmapInfo))
|
if (background.IsResultAvailable && Background != null && BeatmapInfo.BackgroundEquals(other.BeatmapInfo))
|
||||||
other.background = background;
|
other.background = background;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -97,16 +107,18 @@ namespace osu.Game.Beatmaps
|
|||||||
{
|
{
|
||||||
if (BackgroundLoaded) Background?.Dispose();
|
if (BackgroundLoaded) Background?.Dispose();
|
||||||
if (WaveformLoaded) Waveform?.Dispose();
|
if (WaveformLoaded) Waveform?.Dispose();
|
||||||
|
if (StoryboardLoaded) Storyboard?.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void DisposeTrack()
|
/// <summary>
|
||||||
{
|
/// Eagerly dispose of the audio track associated with this <see cref="WorkingBeatmap"/> (if any).
|
||||||
if (TrackLoaded) Track?.Dispose();
|
/// Accessing track again will load a fresh instance.
|
||||||
}
|
/// </summary>
|
||||||
|
public void RecycleTrack() => track.Recycle();
|
||||||
|
|
||||||
private void applyRateAdjustments(Track t = null)
|
private void applyRateAdjustments(Track t = null)
|
||||||
{
|
{
|
||||||
if (t == null && track.IsValueCreated) t = Track;
|
if (t == null && track.IsResultAvailable) t = Track;
|
||||||
if (t == null) return;
|
if (t == null) return;
|
||||||
|
|
||||||
t.ResetSpeedAdjustments();
|
t.ResetSpeedAdjustments();
|
||||||
@ -114,12 +126,65 @@ namespace osu.Game.Beatmaps
|
|||||||
mod.ApplyToClock(t);
|
mod.ApplyToClock(t);
|
||||||
}
|
}
|
||||||
|
|
||||||
public class AsyncLazy<T> : Lazy<Task<T>>
|
public class AsyncLazy<T>
|
||||||
{
|
{
|
||||||
public AsyncLazy(Func<T> valueFactory)
|
private Lazy<Task<T>> lazy;
|
||||||
: base(() => Task.Run(valueFactory))
|
private readonly Func<T> valueFactory;
|
||||||
|
private readonly Func<T, bool> stillValidFunction;
|
||||||
|
|
||||||
|
private readonly object initLock = new object();
|
||||||
|
|
||||||
|
public AsyncLazy(Func<T> valueFactory, Func<T, bool> stillValidFunction = null)
|
||||||
{
|
{
|
||||||
}
|
this.valueFactory = valueFactory;
|
||||||
|
this.stillValidFunction = stillValidFunction;
|
||||||
|
|
||||||
|
recreate();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Recycle()
|
||||||
|
{
|
||||||
|
if (!IsResultAvailable) return;
|
||||||
|
|
||||||
|
(lazy.Value.Result as IDisposable)?.Dispose();
|
||||||
|
recreate();
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsResultAvailable
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
recreateIfInvalid();
|
||||||
|
return lazy.Value.IsCompleted;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<T> Value
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
recreateIfInvalid();
|
||||||
|
return lazy.Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void recreateIfInvalid()
|
||||||
|
{
|
||||||
|
lock (initLock)
|
||||||
|
{
|
||||||
|
if (!lazy.IsValueCreated || !lazy.Value.IsCompleted)
|
||||||
|
// we have not yet been initialised or haven't run the task.
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (stillValidFunction?.Invoke(lazy.Value.Result) ?? true)
|
||||||
|
// we are still in a valid state.
|
||||||
|
return;
|
||||||
|
|
||||||
|
recreate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void recreate() => lazy = new Lazy<Task<T>>(() => Task.Run(valueFactory));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,12 +16,13 @@ namespace osu.Game.Configuration
|
|||||||
Set(OsuSetting.Ruleset, 0, 0, int.MaxValue);
|
Set(OsuSetting.Ruleset, 0, 0, int.MaxValue);
|
||||||
Set(OsuSetting.BeatmapDetailTab, BeatmapDetailTab.Details);
|
Set(OsuSetting.BeatmapDetailTab, BeatmapDetailTab.Details);
|
||||||
|
|
||||||
|
Set(OsuSetting.ShowConvertedBeatmaps, true);
|
||||||
Set(OsuSetting.DisplayStarsMinimum, 0.0, 0, 10, 0.1);
|
Set(OsuSetting.DisplayStarsMinimum, 0.0, 0, 10, 0.1);
|
||||||
Set(OsuSetting.DisplayStarsMaximum, 10.0, 0, 10, 0.1);
|
Set(OsuSetting.DisplayStarsMaximum, 10.0, 0, 10, 0.1);
|
||||||
|
|
||||||
Set(OsuSetting.SelectionRandomType, SelectionRandomType.RandomPermutation);
|
Set(OsuSetting.SelectionRandomType, SelectionRandomType.RandomPermutation);
|
||||||
|
|
||||||
Set(OsuSetting.ChatDisplayHeight, ChatOverlay.DEFAULT_HEIGHT, 0.2, 1, 0.01);
|
Set(OsuSetting.ChatDisplayHeight, ChatOverlay.DEFAULT_HEIGHT, 0.2, 1);
|
||||||
|
|
||||||
// Online settings
|
// Online settings
|
||||||
Set(OsuSetting.Username, string.Empty);
|
Set(OsuSetting.Username, string.Empty);
|
||||||
@ -112,6 +113,7 @@ namespace osu.Game.Configuration
|
|||||||
SnakingOutSliders,
|
SnakingOutSliders,
|
||||||
ShowFpsDisplay,
|
ShowFpsDisplay,
|
||||||
ChatDisplayHeight,
|
ChatDisplayHeight,
|
||||||
Version
|
Version,
|
||||||
|
ShowConvertedBeatmaps
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -93,6 +93,7 @@ namespace osu.Game.Database
|
|||||||
modelBuilder.Entity<FileInfo>().HasIndex(b => b.ReferenceCount);
|
modelBuilder.Entity<FileInfo>().HasIndex(b => b.ReferenceCount);
|
||||||
|
|
||||||
modelBuilder.Entity<RulesetInfo>().HasIndex(b => b.Available);
|
modelBuilder.Entity<RulesetInfo>().HasIndex(b => b.Available);
|
||||||
|
modelBuilder.Entity<RulesetInfo>().HasIndex(b => b.ShortName).IsUnique();
|
||||||
|
|
||||||
modelBuilder.Entity<BeatmapInfo>().HasOne(b => b.BaseDifficulty);
|
modelBuilder.Entity<BeatmapInfo>().HasOne(b => b.BaseDifficulty);
|
||||||
}
|
}
|
||||||
|
@ -2,34 +2,39 @@
|
|||||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||||
|
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Audio;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Audio.Sample;
|
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Input;
|
using osu.Game.Graphics.UserInterface;
|
||||||
|
|
||||||
namespace osu.Game.Graphics.Containers
|
namespace osu.Game.Graphics.Containers
|
||||||
{
|
{
|
||||||
public class OsuClickableContainer : ClickableContainer
|
public class OsuClickableContainer : ClickableContainer
|
||||||
{
|
{
|
||||||
protected SampleChannel SampleClick, SampleHover;
|
private readonly HoverSampleSet sampleSet;
|
||||||
|
|
||||||
|
private readonly Container content = new Container { RelativeSizeAxes = Axes.Both };
|
||||||
|
|
||||||
|
protected override Container<Drawable> Content => content;
|
||||||
|
|
||||||
|
public OsuClickableContainer(HoverSampleSet sampleSet = HoverSampleSet.Normal)
|
||||||
|
{
|
||||||
|
this.sampleSet = sampleSet;
|
||||||
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(AudioManager audio)
|
private void load()
|
||||||
{
|
{
|
||||||
SampleHover = audio.Sample.Get(@"UI/generic-hover");
|
if (AutoSizeAxes != Axes.None)
|
||||||
SampleClick = audio.Sample.Get(@"UI/generic-click");
|
{
|
||||||
|
content.RelativeSizeAxes = RelativeSizeAxes;
|
||||||
|
content.AutoSizeAxes = AutoSizeAxes;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override bool OnHover(InputState state)
|
InternalChildren = new Drawable[]
|
||||||
{
|
{
|
||||||
SampleHover?.Play();
|
content,
|
||||||
return base.OnHover(state);
|
new HoverClickSounds(sampleSet)
|
||||||
}
|
};
|
||||||
|
|
||||||
protected override bool OnClick(InputState state)
|
|
||||||
{
|
|
||||||
SampleClick?.Play();
|
|
||||||
return base.OnClick(state);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,8 +16,8 @@ namespace osu.Game.Graphics.Containers
|
|||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(AudioManager audio)
|
private void load(AudioManager audio)
|
||||||
{
|
{
|
||||||
samplePopIn = audio.Sample.Get(@"UI/melodic-5");
|
samplePopIn = audio.Sample.Get(@"UI/overlay-pop-in");
|
||||||
samplePopOut = audio.Sample.Get(@"UI/melodic-4");
|
samplePopOut = audio.Sample.Get(@"UI/overlay-pop-out");
|
||||||
|
|
||||||
StateChanged += onStateChanged;
|
StateChanged += onStateChanged;
|
||||||
}
|
}
|
||||||
|
@ -57,19 +57,31 @@ namespace osu.Game.Graphics
|
|||||||
private void load(FontStore store)
|
private void load(FontStore store)
|
||||||
{
|
{
|
||||||
this.store = store;
|
this.store = store;
|
||||||
|
|
||||||
updateTexture();
|
updateTexture();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
updateTexture();
|
||||||
|
}
|
||||||
|
|
||||||
|
private FontAwesome loadedIcon;
|
||||||
private void updateTexture()
|
private void updateTexture()
|
||||||
{
|
{
|
||||||
var texture = store?.Get(((char)icon).ToString());
|
var loadableIcon = icon;
|
||||||
|
|
||||||
|
if (loadableIcon == loadedIcon) return;
|
||||||
|
|
||||||
|
var texture = store?.Get(((char)loadableIcon).ToString());
|
||||||
|
|
||||||
spriteMain.Texture = texture;
|
spriteMain.Texture = texture;
|
||||||
spriteShadow.Texture = texture;
|
spriteShadow.Texture = texture;
|
||||||
|
|
||||||
if (Size == Vector2.Zero)
|
if (Size == Vector2.Zero)
|
||||||
Size = new Vector2(texture?.DisplayWidth ?? 0, texture?.DisplayHeight ?? 0);
|
Size = new Vector2(texture?.DisplayWidth ?? 0, texture?.DisplayHeight ?? 0);
|
||||||
|
|
||||||
|
loadedIcon = loadableIcon;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool Invalidate(Invalidation invalidation = Invalidation.All, Drawable source = null, bool shallPropagate = true)
|
public override bool Invalidate(Invalidation invalidation = Invalidation.All, Drawable source = null, bool shallPropagate = true)
|
||||||
|
36
osu.Game/Graphics/UserInterface/HoverClickSounds.cs
Normal file
36
osu.Game/Graphics/UserInterface/HoverClickSounds.cs
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
// Copyright (c) 2007-2017 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.Audio;
|
||||||
|
using osu.Framework.Audio.Sample;
|
||||||
|
using osu.Framework.Extensions;
|
||||||
|
using osu.Framework.Input;
|
||||||
|
|
||||||
|
namespace osu.Game.Graphics.UserInterface
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Adds hover and click sounds to a drawable.
|
||||||
|
/// Does not draw anything.
|
||||||
|
/// </summary>
|
||||||
|
public class HoverClickSounds : HoverSounds
|
||||||
|
{
|
||||||
|
private SampleChannel sampleClick;
|
||||||
|
|
||||||
|
public HoverClickSounds(HoverSampleSet sampleSet = HoverSampleSet.Normal) : base(sampleSet)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool OnClick(InputState state)
|
||||||
|
{
|
||||||
|
sampleClick?.Play();
|
||||||
|
return base.OnClick(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(AudioManager audio)
|
||||||
|
{
|
||||||
|
sampleClick = audio.Sample.Get($@"UI/generic-select{SampleSet.GetDescription()}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
53
osu.Game/Graphics/UserInterface/HoverSounds.cs
Normal file
53
osu.Game/Graphics/UserInterface/HoverSounds.cs
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
|
||||||
|
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||||
|
|
||||||
|
using System.ComponentModel;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Audio;
|
||||||
|
using osu.Framework.Audio.Sample;
|
||||||
|
using osu.Framework.Extensions;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Input;
|
||||||
|
|
||||||
|
namespace osu.Game.Graphics.UserInterface
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Adds hover sounds to a drawable.
|
||||||
|
/// Does not draw anything.
|
||||||
|
/// </summary>
|
||||||
|
public class HoverSounds : CompositeDrawable
|
||||||
|
{
|
||||||
|
private SampleChannel sampleHover;
|
||||||
|
|
||||||
|
protected readonly HoverSampleSet SampleSet;
|
||||||
|
|
||||||
|
public HoverSounds(HoverSampleSet sampleSet = HoverSampleSet.Normal)
|
||||||
|
{
|
||||||
|
SampleSet = sampleSet;
|
||||||
|
RelativeSizeAxes = Axes.Both;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool OnHover(InputState state)
|
||||||
|
{
|
||||||
|
sampleHover?.Play();
|
||||||
|
return base.OnHover(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(AudioManager audio)
|
||||||
|
{
|
||||||
|
sampleHover = audio.Sample.Get($@"UI/generic-hover{SampleSet.GetDescription()}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum HoverSampleSet
|
||||||
|
{
|
||||||
|
[Description("")]
|
||||||
|
Loud,
|
||||||
|
[Description("-soft")]
|
||||||
|
Normal,
|
||||||
|
[Description("-softer")]
|
||||||
|
Soft
|
||||||
|
}
|
||||||
|
}
|
@ -1,126 +1,18 @@
|
|||||||
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
|
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
|
||||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||||
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using OpenTK.Graphics;
|
|
||||||
using osu.Framework.Allocation;
|
|
||||||
using osu.Framework.Audio;
|
|
||||||
using osu.Framework.Audio.Sample;
|
|
||||||
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.Framework.Graphics.UserInterface;
|
using osu.Framework.Graphics.UserInterface;
|
||||||
using osu.Framework.Input;
|
|
||||||
using osu.Game.Graphics.Backgrounds;
|
|
||||||
using osu.Game.Graphics.Sprites;
|
|
||||||
|
|
||||||
namespace osu.Game.Graphics.UserInterface
|
namespace osu.Game.Graphics.UserInterface
|
||||||
{
|
{
|
||||||
public class OsuButton : Button, IFilterable
|
/// <summary>
|
||||||
|
/// A button with added default sound effects.
|
||||||
|
/// </summary>
|
||||||
|
public class OsuButton : Button
|
||||||
{
|
{
|
||||||
private Box hover;
|
|
||||||
|
|
||||||
private SampleChannel sampleClick;
|
|
||||||
private SampleChannel sampleHover;
|
|
||||||
|
|
||||||
protected Triangles Triangles;
|
|
||||||
|
|
||||||
public OsuButton()
|
public OsuButton()
|
||||||
{
|
{
|
||||||
Height = 40;
|
Add(new HoverClickSounds(HoverSampleSet.Loud));
|
||||||
}
|
|
||||||
|
|
||||||
protected override SpriteText CreateText() => new OsuSpriteText
|
|
||||||
{
|
|
||||||
Depth = -1,
|
|
||||||
Origin = Anchor.Centre,
|
|
||||||
Anchor = Anchor.Centre,
|
|
||||||
Font = @"Exo2.0-Bold",
|
|
||||||
};
|
|
||||||
|
|
||||||
public override bool HandleInput => Action != null;
|
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
|
||||||
private void load(OsuColour colours, AudioManager audio)
|
|
||||||
{
|
|
||||||
if (Action == null)
|
|
||||||
Colour = OsuColour.Gray(0.5f);
|
|
||||||
|
|
||||||
BackgroundColour = colours.BlueDark;
|
|
||||||
|
|
||||||
Content.Masking = true;
|
|
||||||
Content.CornerRadius = 5;
|
|
||||||
|
|
||||||
AddRange(new Drawable[]
|
|
||||||
{
|
|
||||||
Triangles = new Triangles
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
ColourDark = colours.BlueDarker,
|
|
||||||
ColourLight = colours.Blue,
|
|
||||||
},
|
|
||||||
hover = new Box
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Blending = BlendingMode.Additive,
|
|
||||||
Colour = Color4.White.Opacity(0.1f),
|
|
||||||
Alpha = 0,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
sampleClick = audio.Sample.Get(@"UI/generic-click");
|
|
||||||
sampleHover = audio.Sample.Get(@"UI/generic-hover");
|
|
||||||
|
|
||||||
Enabled.ValueChanged += enabled_ValueChanged;
|
|
||||||
Enabled.TriggerChange();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void enabled_ValueChanged(bool enabled)
|
|
||||||
{
|
|
||||||
this.FadeColour(enabled ? Color4.White : Color4.Gray, 200, Easing.OutQuint);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override bool OnClick(InputState state)
|
|
||||||
{
|
|
||||||
sampleClick?.Play();
|
|
||||||
return base.OnClick(state);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override bool OnHover(InputState state)
|
|
||||||
{
|
|
||||||
sampleHover?.Play();
|
|
||||||
hover.FadeIn(200);
|
|
||||||
return base.OnHover(state);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void OnHoverLost(InputState state)
|
|
||||||
{
|
|
||||||
hover.FadeOut(200);
|
|
||||||
base.OnHoverLost(state);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override bool OnMouseDown(InputState state, MouseDownEventArgs args)
|
|
||||||
{
|
|
||||||
Content.ScaleTo(0.9f, 4000, Easing.OutQuint);
|
|
||||||
return base.OnMouseDown(state, args);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override bool OnMouseUp(InputState state, MouseUpEventArgs args)
|
|
||||||
{
|
|
||||||
Content.ScaleTo(1, 1000, Easing.OutElastic);
|
|
||||||
return base.OnMouseUp(state, args);
|
|
||||||
}
|
|
||||||
|
|
||||||
public IEnumerable<string> FilterTerms => new[] { Text };
|
|
||||||
|
|
||||||
public bool MatchingFilter
|
|
||||||
{
|
|
||||||
set
|
|
||||||
{
|
|
||||||
this.FadeTo(value ? 1 : 0);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -70,7 +70,8 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
Anchor = Anchor.CentreRight,
|
Anchor = Anchor.CentreRight,
|
||||||
Origin = Anchor.CentreRight,
|
Origin = Anchor.CentreRight,
|
||||||
Margin = new MarginPadding { Right = 5 },
|
Margin = new MarginPadding { Right = 5 },
|
||||||
}
|
},
|
||||||
|
new HoverClickSounds()
|
||||||
};
|
};
|
||||||
|
|
||||||
Nub.Current.BindTo(Current);
|
Nub.Current.BindTo(Current);
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user