mirror of
https://github.com/ppy/osu.git
synced 2025-02-05 08:12:53 +08:00
Merge branch 'master' of https://github.com/ppy/osu into taiko-hitsounds-fix
This commit is contained in:
commit
9e3d439f0a
@ -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 d92cec764538da2e7ed95bfb566f6bc81a9667c8
|
Subproject commit 797a351db2e852fef5296453641ffbf6b2f6dc11
|
@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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,
|
ExplodingFruitTarget = explodingFruitContainer,
|
||||||
Anchor = Anchor.BottomLeft,
|
Anchor = Anchor.BottomLeft,
|
||||||
Origin = Anchor.TopLeft,
|
Origin = Anchor.TopLeft,
|
||||||
Height = 180,
|
|
||||||
Child = catcher = new Catcher
|
|
||||||
{
|
|
||||||
ExplodingFruitTarget = explodingFruitContainer,
|
|
||||||
RelativePositionAxes = Axes.Both,
|
|
||||||
Origin = Anchor.TopCentre,
|
|
||||||
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" />
|
||||||
|
@ -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)));
|
||||||
}
|
}
|
||||||
|
@ -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
|
int activeColumns = 0;
|
||||||
foreach (var objGroup in Beatmap.HitObjects.GroupBy(h => h.StartTime))
|
foreach (var group in pointGroups)
|
||||||
{
|
{
|
||||||
double groupTime = objGroup.Key;
|
foreach (var point in group)
|
||||||
|
|
||||||
int activeColumns = 0;
|
|
||||||
|
|
||||||
// Get the previously held-down active columns
|
|
||||||
for (int i = 0; i < availableColumns; i++)
|
|
||||||
{
|
{
|
||||||
if (holdEndTimes[i] > groupTime)
|
if (point is HitPoint)
|
||||||
activeColumns |= 1 << i;
|
activeColumns |= 1 << point.Column;
|
||||||
|
if (point is ReleasePoint)
|
||||||
|
activeColumns ^= 1 << point.Column;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add on the group columns, keeping track of the held notes for the next rows
|
Replay.Frames.Add(new ManiaReplayFrame(group.First().Time, activeColumns));
|
||||||
foreach (var obj in objGroup)
|
|
||||||
{
|
|
||||||
var holdNote = obj as HoldNote;
|
|
||||||
if (holdNote != null)
|
|
||||||
holdEndTimes[obj.Column] = Math.Max(holdEndTimes[obj.Column], holdNote.EndTime);
|
|
||||||
|
|
||||||
activeColumns |= 1 << obj.Column;
|
|
||||||
}
|
|
||||||
|
|
||||||
Replay.Frames.Add(new ReplayFrame(groupTime, activeColumns, null, ReplayButtonState.None));
|
|
||||||
|
|
||||||
// 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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
private interface 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;
|
double Time { get; set; }
|
||||||
while (value > 0)
|
int Column { get; set; }
|
||||||
{
|
}
|
||||||
if ((value & 1) > 0)
|
|
||||||
count++;
|
|
||||||
value >>= 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return count;
|
private struct HitPoint : IActionPoint
|
||||||
|
{
|
||||||
|
public double Time { get; set; }
|
||||||
|
public int Column { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
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" />
|
||||||
|
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,6 +118,10 @@ 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 SettingsSubsection CreateSettings() => new OsuSettings();
|
public override SettingsSubsection CreateSettings() => new OsuSettings();
|
||||||
|
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()))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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" />
|
||||||
|
@ -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;
|
||||||
|
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" />
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
@ -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,6 +103,8 @@
|
|||||||
<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" />
|
||||||
@ -147,6 +147,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>
|
||||||
|
@ -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,7 +495,7 @@ 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
|
beatmapSet = beatmaps.BeatmapSets.FirstOrDefault(b => b.OnlineBeatmapSetID == metadata.OnlineBeatmapSetID) ?? new BeatmapSetInfo
|
||||||
@ -517,8 +518,8 @@ 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();
|
||||||
@ -568,23 +569,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 +612,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>
|
||||||
|
@ -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
|
||||||
|
@ -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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -22,7 +22,7 @@ namespace osu.Game.Configuration
|
|||||||
|
|
||||||
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);
|
||||||
|
@ -14,7 +14,7 @@ namespace osu.Game.Input.Bindings
|
|||||||
/// A KeyBindingInputManager with a database backing for custom overrides.
|
/// A KeyBindingInputManager with a database backing for custom overrides.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <typeparam name="T">The type of the custom action.</typeparam>
|
/// <typeparam name="T">The type of the custom action.</typeparam>
|
||||||
public abstract class DatabasedKeyBindingInputManager<T> : KeyBindingInputManager<T>
|
public class DatabasedKeyBindingInputManager<T> : KeyBindingContainer<T>
|
||||||
where T : struct
|
where T : struct
|
||||||
{
|
{
|
||||||
private readonly RulesetInfo ruleset;
|
private readonly RulesetInfo ruleset;
|
||||||
@ -31,7 +31,7 @@ namespace osu.Game.Input.Bindings
|
|||||||
/// <param name="ruleset">A reference to identify the current <see cref="Ruleset"/>. Used to lookup mappings. Null for global mappings.</param>
|
/// <param name="ruleset">A reference to identify the current <see cref="Ruleset"/>. Used to lookup mappings. Null for global mappings.</param>
|
||||||
/// <param name="variant">An optional variant for the specified <see cref="Ruleset"/>. Used when a ruleset has more than one possible keyboard layouts.</param>
|
/// <param name="variant">An optional variant for the specified <see cref="Ruleset"/>. Used when a ruleset has more than one possible keyboard layouts.</param>
|
||||||
/// <param name="simultaneousMode">Specify how to deal with multiple matches of <see cref="KeyCombination"/>s and <see cref="T"/>s.</param>
|
/// <param name="simultaneousMode">Specify how to deal with multiple matches of <see cref="KeyCombination"/>s and <see cref="T"/>s.</param>
|
||||||
protected DatabasedKeyBindingInputManager(RulesetInfo ruleset = null, int? variant = null, SimultaneousBindingMode simultaneousMode = SimultaneousBindingMode.None)
|
public DatabasedKeyBindingInputManager(RulesetInfo ruleset = null, int? variant = null, SimultaneousBindingMode simultaneousMode = SimultaneousBindingMode.None)
|
||||||
: base(simultaneousMode)
|
: base(simultaneousMode)
|
||||||
{
|
{
|
||||||
this.ruleset = ruleset;
|
this.ruleset = ruleset;
|
||||||
|
@ -27,7 +27,7 @@ namespace osu.Game.Input
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Register(KeyBindingInputManager manager) => insertDefaults(manager.DefaultKeyBindings);
|
public void Register(KeyBindingContainer manager) => insertDefaults(manager.DefaultKeyBindings);
|
||||||
|
|
||||||
private void insertDefaults(IEnumerable<KeyBinding> defaults, int? rulesetId = null, int? variant = null)
|
private void insertDefaults(IEnumerable<KeyBinding> defaults, int? rulesetId = null, int? variant = null)
|
||||||
{
|
{
|
||||||
|
@ -154,7 +154,7 @@ namespace osu.Game
|
|||||||
Debug.Assert(lastBeatmap != null);
|
Debug.Assert(lastBeatmap != null);
|
||||||
Debug.Assert(lastBeatmap.Track != null);
|
Debug.Assert(lastBeatmap.Track != null);
|
||||||
|
|
||||||
lastBeatmap.DisposeTrack();
|
lastBeatmap.RecycleTrack();
|
||||||
}
|
}
|
||||||
|
|
||||||
Audio.Track.AddItem(b.Track);
|
Audio.Track.AddItem(b.Track);
|
||||||
|
@ -14,10 +14,10 @@ namespace osu.Game.Overlays.BeatmapSet
|
|||||||
public DownloadButton(string title, string subtitle)
|
public DownloadButton(string title, string subtitle)
|
||||||
{
|
{
|
||||||
Width = 120;
|
Width = 120;
|
||||||
RelativeSizeAxes = Axes.Y;
|
|
||||||
|
|
||||||
Child = new Container
|
Add(new Container
|
||||||
{
|
{
|
||||||
|
Depth = -1,
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Padding = new MarginPadding { Horizontal = 10 },
|
Padding = new MarginPadding { Horizontal = 10 },
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
@ -53,7 +53,7 @@ namespace osu.Game.Overlays.BeatmapSet
|
|||||||
Margin = new MarginPadding { Right = 5 },
|
Margin = new MarginPadding { Right = 5 },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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 osu.Framework.Allocation;
|
||||||
using osu.Framework.Configuration;
|
using osu.Framework.Configuration;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
@ -15,13 +16,12 @@ namespace osu.Game.Overlays.BeatmapSet
|
|||||||
{
|
{
|
||||||
public readonly Bindable<bool> Favourited = new Bindable<bool>();
|
public readonly Bindable<bool> Favourited = new Bindable<bool>();
|
||||||
|
|
||||||
public FavouriteButton()
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Y;
|
|
||||||
|
|
||||||
Container pink;
|
Container pink;
|
||||||
SpriteIcon icon;
|
SpriteIcon icon;
|
||||||
Children = new Drawable[]
|
AddRange(new Drawable[]
|
||||||
{
|
{
|
||||||
pink = new Container
|
pink = new Container
|
||||||
{
|
{
|
||||||
@ -51,7 +51,7 @@ namespace osu.Game.Overlays.BeatmapSet
|
|||||||
Size = new Vector2(18),
|
Size = new Vector2(18),
|
||||||
Shadow = false,
|
Shadow = false,
|
||||||
},
|
},
|
||||||
};
|
});
|
||||||
|
|
||||||
Favourited.ValueChanged += value =>
|
Favourited.ValueChanged += value =>
|
||||||
{
|
{
|
||||||
|
@ -2,44 +2,27 @@
|
|||||||
// 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.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
|
||||||
using osu.Framework.Graphics.Shapes;
|
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Graphics.Backgrounds;
|
using osu.Game.Graphics.UserInterface;
|
||||||
using osu.Game.Graphics.Containers;
|
using osu.Framework.Allocation;
|
||||||
|
|
||||||
namespace osu.Game.Overlays.BeatmapSet
|
namespace osu.Game.Overlays.BeatmapSet
|
||||||
{
|
{
|
||||||
public class HeaderButton : OsuClickableContainer
|
public class HeaderButton : TriangleButton
|
||||||
{
|
{
|
||||||
private readonly Container content;
|
|
||||||
|
|
||||||
protected override Container<Drawable> Content => content;
|
|
||||||
|
|
||||||
public HeaderButton()
|
public HeaderButton()
|
||||||
{
|
{
|
||||||
CornerRadius = 3;
|
Height = 0;
|
||||||
Masking = true;
|
RelativeSizeAxes = Axes.Y;
|
||||||
|
}
|
||||||
|
|
||||||
InternalChildren = new Drawable[]
|
[BackgroundDependencyLoader]
|
||||||
{
|
private void load()
|
||||||
new Box
|
{
|
||||||
{
|
BackgroundColour = OsuColour.FromHex(@"094c5f");
|
||||||
RelativeSizeAxes = Axes.Both,
|
Triangles.ColourLight = OsuColour.FromHex(@"0f7c9b");
|
||||||
Colour = OsuColour.FromHex(@"094c5f"),
|
Triangles.ColourDark = OsuColour.FromHex(@"094c5f");
|
||||||
},
|
Triangles.TriangleScale = 1.5f;
|
||||||
new Triangles
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
ColourLight = OsuColour.FromHex(@"0f7c9b"),
|
|
||||||
ColourDark = OsuColour.FromHex(@"094c5f"),
|
|
||||||
TriangleScale = 1.5f,
|
|
||||||
},
|
|
||||||
content = new Container
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -220,7 +220,11 @@ namespace osu.Game.Overlays
|
|||||||
switch (displayStyle)
|
switch (displayStyle)
|
||||||
{
|
{
|
||||||
case PanelDisplayStyle.Grid:
|
case PanelDisplayStyle.Grid:
|
||||||
return new DirectGridPanel(b);
|
return new DirectGridPanel(b)
|
||||||
|
{
|
||||||
|
Anchor = Anchor.TopCentre,
|
||||||
|
Origin = Anchor.TopCentre,
|
||||||
|
};
|
||||||
default:
|
default:
|
||||||
return new DirectListPanel(b);
|
return new DirectListPanel(b);
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,7 @@ namespace osu.Game.Overlays.KeyBinding
|
|||||||
public override FontAwesome Icon => FontAwesome.fa_osu_hot;
|
public override FontAwesome Icon => FontAwesome.fa_osu_hot;
|
||||||
public override string Header => "Global";
|
public override string Header => "Global";
|
||||||
|
|
||||||
public GlobalKeyBindingsSection(KeyBindingInputManager manager)
|
public GlobalKeyBindingsSection(KeyBindingContainer manager)
|
||||||
{
|
{
|
||||||
Add(new DefaultBindingsSubsection(manager));
|
Add(new DefaultBindingsSubsection(manager));
|
||||||
}
|
}
|
||||||
@ -21,7 +21,7 @@ namespace osu.Game.Overlays.KeyBinding
|
|||||||
{
|
{
|
||||||
protected override string Header => string.Empty;
|
protected override string Header => string.Empty;
|
||||||
|
|
||||||
public DefaultBindingsSubsection(KeyBindingInputManager manager)
|
public DefaultBindingsSubsection(KeyBindingContainer manager)
|
||||||
: base(null)
|
: base(null)
|
||||||
{
|
{
|
||||||
Defaults = manager.DefaultKeyBindings;
|
Defaults = manager.DefaultKeyBindings;
|
||||||
|
@ -35,7 +35,11 @@ namespace osu.Game.Overlays.Music
|
|||||||
set { base.Padding = value; }
|
set { base.Padding = value; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public IEnumerable<BeatmapSetInfo> BeatmapSets { set { items.Sets = value; } }
|
public IEnumerable<BeatmapSetInfo> BeatmapSets
|
||||||
|
{
|
||||||
|
get { return items.Sets; }
|
||||||
|
set { items.Sets = value; }
|
||||||
|
}
|
||||||
|
|
||||||
public BeatmapSetInfo FirstVisibleSet => items.FirstVisibleSet;
|
public BeatmapSetInfo FirstVisibleSet => items.FirstVisibleSet;
|
||||||
public BeatmapSetInfo NextSet => items.NextSet;
|
public BeatmapSetInfo NextSet => items.NextSet;
|
||||||
@ -48,7 +52,7 @@ namespace osu.Game.Overlays.Music
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void AddBeatmapSet(BeatmapSetInfo beatmapSet) => items.AddBeatmapSet(beatmapSet);
|
public void AddBeatmapSet(BeatmapSetInfo beatmapSet) => items.AddBeatmapSet(beatmapSet);
|
||||||
public bool RemoveBeatmapSet(BeatmapSetInfo beatmapSet) => items.RemoveBeatmapSet(beatmapSet);
|
public void RemoveBeatmapSet(BeatmapSetInfo beatmapSet) => items.RemoveBeatmapSet(beatmapSet);
|
||||||
|
|
||||||
public void Filter(string searchTerm) => items.SearchTerm = searchTerm;
|
public void Filter(string searchTerm) => items.SearchTerm = searchTerm;
|
||||||
|
|
||||||
@ -81,6 +85,7 @@ namespace osu.Game.Overlays.Music
|
|||||||
|
|
||||||
public IEnumerable<BeatmapSetInfo> Sets
|
public IEnumerable<BeatmapSetInfo> Sets
|
||||||
{
|
{
|
||||||
|
get { return items.Select(x => x.BeatmapSetInfo).ToList(); }
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
items.Clear();
|
items.Clear();
|
||||||
@ -103,12 +108,11 @@ namespace osu.Game.Overlays.Music
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool RemoveBeatmapSet(BeatmapSetInfo beatmapSet)
|
public void RemoveBeatmapSet(BeatmapSetInfo beatmapSet)
|
||||||
{
|
{
|
||||||
var itemToRemove = items.FirstOrDefault(i => i.BeatmapSetInfo.ID == beatmapSet.ID);
|
var itemToRemove = items.FirstOrDefault(i => i.BeatmapSetInfo.ID == beatmapSet.ID);
|
||||||
if (itemToRemove == null)
|
if (itemToRemove != null)
|
||||||
return false;
|
items.Remove(itemToRemove);
|
||||||
return items.Remove(itemToRemove);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public BeatmapSetInfo SelectedSet
|
public BeatmapSetInfo SelectedSet
|
||||||
@ -230,6 +234,7 @@ namespace osu.Game.Overlays.Music
|
|||||||
private class ItemSearchContainer : FillFlowContainer<PlaylistItem>, IHasFilterableChildren
|
private class ItemSearchContainer : FillFlowContainer<PlaylistItem>, IHasFilterableChildren
|
||||||
{
|
{
|
||||||
public IEnumerable<string> FilterTerms => new string[] { };
|
public IEnumerable<string> FilterTerms => new string[] { };
|
||||||
|
|
||||||
public bool MatchingFilter
|
public bool MatchingFilter
|
||||||
{
|
{
|
||||||
set
|
set
|
||||||
|
@ -13,6 +13,7 @@ using osu.Game.Beatmaps;
|
|||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using OpenTK;
|
using OpenTK;
|
||||||
using OpenTK.Graphics;
|
using OpenTK.Graphics;
|
||||||
|
using System.Threading;
|
||||||
|
|
||||||
namespace osu.Game.Overlays.Music
|
namespace osu.Game.Overlays.Music
|
||||||
{
|
{
|
||||||
@ -29,7 +30,7 @@ namespace osu.Game.Overlays.Music
|
|||||||
|
|
||||||
private readonly Bindable<WorkingBeatmap> beatmapBacking = new Bindable<WorkingBeatmap>();
|
private readonly Bindable<WorkingBeatmap> beatmapBacking = new Bindable<WorkingBeatmap>();
|
||||||
|
|
||||||
public IEnumerable<BeatmapSetInfo> BeatmapSets;
|
public IEnumerable<BeatmapSetInfo> BeatmapSets => list.BeatmapSets;
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(OsuGameBase game, BeatmapManager beatmaps, OsuColour colours)
|
private void load(OsuGameBase game, BeatmapManager beatmaps, OsuColour colours)
|
||||||
@ -74,11 +75,10 @@ namespace osu.Game.Overlays.Music
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
beatmaps.BeatmapSetAdded += s => Schedule(() => list.AddBeatmapSet(s));
|
beatmaps.BeatmapSetAdded += list.AddBeatmapSet;
|
||||||
beatmaps.BeatmapSetRemoved += s => Schedule(() => list.RemoveBeatmapSet(s));
|
beatmaps.BeatmapSetRemoved += list.RemoveBeatmapSet;
|
||||||
|
|
||||||
list.BeatmapSets = BeatmapSets = beatmaps.GetAllUsableBeatmapSets();
|
|
||||||
|
|
||||||
|
list.BeatmapSets = beatmaps.GetAllUsableBeatmapSets();
|
||||||
|
|
||||||
beatmapBacking.BindTo(game.Beatmap);
|
beatmapBacking.BindTo(game.Beatmap);
|
||||||
|
|
||||||
@ -121,7 +121,7 @@ namespace osu.Game.Overlays.Music
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
playSpecified(set.Beatmaps[0]);
|
playSpecified(set.Beatmaps.First());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void PlayPrevious()
|
public void PlayPrevious()
|
||||||
@ -130,7 +130,7 @@ namespace osu.Game.Overlays.Music
|
|||||||
|
|
||||||
if (playable != null)
|
if (playable != null)
|
||||||
{
|
{
|
||||||
playSpecified(playable.Beatmaps[0]);
|
playSpecified(playable.Beatmaps.First());
|
||||||
list.SelectedSet = playable;
|
list.SelectedSet = playable;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -141,7 +141,7 @@ namespace osu.Game.Overlays.Music
|
|||||||
|
|
||||||
if (playable != null)
|
if (playable != null)
|
||||||
{
|
{
|
||||||
playSpecified(playable.Beatmaps[0]);
|
playSpecified(playable.Beatmaps.First());
|
||||||
list.SelectedSet = playable;
|
list.SelectedSet = playable;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -149,7 +149,15 @@ namespace osu.Game.Overlays.Music
|
|||||||
private void playSpecified(BeatmapInfo info)
|
private void playSpecified(BeatmapInfo info)
|
||||||
{
|
{
|
||||||
beatmapBacking.Value = beatmaps.GetWorkingBeatmap(info, beatmapBacking);
|
beatmapBacking.Value = beatmaps.GetWorkingBeatmap(info, beatmapBacking);
|
||||||
beatmapBacking.Value.Track.Start();
|
|
||||||
|
var track = beatmapBacking.Value.Track;
|
||||||
|
|
||||||
|
track.Restart();
|
||||||
|
|
||||||
|
// this is temporary until we have blocking (async.Wait()) audio component methods.
|
||||||
|
// then we can call RestartAsync().Wait() or the blocking version above.
|
||||||
|
while (!track.IsRunning)
|
||||||
|
Thread.Sleep(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -251,7 +251,7 @@ namespace osu.Game.Overlays
|
|||||||
|
|
||||||
playButton.Icon = track.IsRunning ? FontAwesome.fa_pause_circle_o : FontAwesome.fa_play_circle_o;
|
playButton.Icon = track.IsRunning ? FontAwesome.fa_pause_circle_o : FontAwesome.fa_play_circle_o;
|
||||||
|
|
||||||
if (track.HasCompleted && !track.Looping && !beatmapBacking.Disabled)
|
if (track.HasCompleted && !beatmapBacking.Disabled && playlist.BeatmapSets.Any())
|
||||||
next();
|
next();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -97,6 +97,11 @@ namespace osu.Game.Overlays.Settings.Sections.Input
|
|||||||
doubleValue.ValueChanged += newValue => base.Bindable.Value = newValue;
|
doubleValue.ValueChanged += newValue => base.Bindable.Value = newValue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public SensitivitySetting()
|
||||||
|
{
|
||||||
|
KeyboardStep = 0.01f;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class SensitivitySlider : OsuSliderBar<double>
|
private class SensitivitySlider : OsuSliderBar<double>
|
||||||
@ -105,8 +110,6 @@ namespace osu.Game.Overlays.Settings.Sections.Input
|
|||||||
|
|
||||||
public SensitivitySlider()
|
public SensitivitySlider()
|
||||||
{
|
{
|
||||||
KeyboardStep = 0.01f;
|
|
||||||
|
|
||||||
Current.ValueChanged += newValue =>
|
Current.ValueChanged += newValue =>
|
||||||
{
|
{
|
||||||
if (!isDragging && Sensitivity != null)
|
if (!isDragging && Sensitivity != null)
|
||||||
@ -133,4 +136,4 @@ namespace osu.Game.Overlays.Settings.Sections.Input
|
|||||||
public override string TooltipText => Current.Disabled ? "Enable raw input to adjust sensitivity" : Current.Value.ToString(@"0.##x");
|
public override string TooltipText => Current.Disabled ? "Enable raw input to adjust sensitivity" : Current.Value.ToString(@"0.##x");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
109
osu.Game/Rulesets/Edit/HitObjectComposer.cs
Normal file
109
osu.Game/Rulesets/Edit/HitObjectComposer.cs
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
// 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 OpenTK.Graphics;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Framework.Logging;
|
||||||
|
using osu.Framework.Timing;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Rulesets.Edit.Tools;
|
||||||
|
using osu.Game.Rulesets.UI;
|
||||||
|
using osu.Game.Screens.Edit.Screens.Compose.RadioButtons;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Edit
|
||||||
|
{
|
||||||
|
public abstract class HitObjectComposer : CompositeDrawable
|
||||||
|
{
|
||||||
|
private readonly Ruleset ruleset;
|
||||||
|
|
||||||
|
protected ICompositionTool CurrentTool { get; private set; }
|
||||||
|
|
||||||
|
protected HitObjectComposer(Ruleset ruleset)
|
||||||
|
{
|
||||||
|
this.ruleset = ruleset;
|
||||||
|
|
||||||
|
RelativeSizeAxes = Axes.Both;
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(OsuGameBase osuGame)
|
||||||
|
{
|
||||||
|
RulesetContainer rulesetContainer;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
rulesetContainer = CreateRulesetContainer(ruleset, osuGame.Beatmap.Value);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Logger.Log($"Could not load this beatmap sucessfully ({e})!", LoggingTarget.Runtime, LogLevel.Error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
RadioButtonCollection toolboxCollection;
|
||||||
|
InternalChild = new GridContainer
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Content = new[]
|
||||||
|
{
|
||||||
|
new Drawable[]
|
||||||
|
{
|
||||||
|
new FillFlowContainer
|
||||||
|
{
|
||||||
|
Name = "Sidebar",
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Padding = new MarginPadding { Right = 10 },
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new ToolboxGroup { Child = toolboxCollection = new RadioButtonCollection { RelativeSizeAxes = Axes.X } }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
new Container
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Masking = true,
|
||||||
|
BorderColour = Color4.White,
|
||||||
|
BorderThickness = 2,
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Alpha = 0,
|
||||||
|
AlwaysPresent = true,
|
||||||
|
},
|
||||||
|
rulesetContainer
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ColumnDimensions = new[]
|
||||||
|
{
|
||||||
|
new Dimension(GridSizeMode.Absolute, 200),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
rulesetContainer.Clock = new InterpolatingFramedClock((IAdjustableClock)osuGame.Beatmap.Value.Track ?? new StopwatchClock());
|
||||||
|
|
||||||
|
toolboxCollection.Items =
|
||||||
|
new[] { new RadioButton("Select", () => setCompositionTool(null)) }
|
||||||
|
.Concat(
|
||||||
|
CompositionTools.Select(t => new RadioButton(t.Name, () => setCompositionTool(t)))
|
||||||
|
)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
toolboxCollection.Items[0].Select();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setCompositionTool(ICompositionTool tool) => CurrentTool = tool;
|
||||||
|
|
||||||
|
protected virtual RulesetContainer CreateRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap) => ruleset.CreateRulesetContainerWith(beatmap, true);
|
||||||
|
|
||||||
|
protected abstract IReadOnlyList<ICompositionTool> CompositionTools { get; }
|
||||||
|
}
|
||||||
|
}
|
19
osu.Game/Rulesets/Edit/ToolboxGroup.cs
Normal file
19
osu.Game/Rulesets/Edit/ToolboxGroup.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.Framework.Graphics;
|
||||||
|
using osu.Game.Screens.Play.ReplaySettings;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Edit
|
||||||
|
{
|
||||||
|
public class ToolboxGroup : ReplayGroup
|
||||||
|
{
|
||||||
|
protected override string Title => "toolbox";
|
||||||
|
|
||||||
|
public ToolboxGroup()
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X;
|
||||||
|
Width = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
13
osu.Game/Rulesets/Edit/Tools/HitObjectCompositionTool.cs
Normal file
13
osu.Game/Rulesets/Edit/Tools/HitObjectCompositionTool.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.Game.Rulesets.Objects;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Edit.Tools
|
||||||
|
{
|
||||||
|
public class HitObjectCompositionTool<T> : ICompositionTool
|
||||||
|
where T : HitObject
|
||||||
|
{
|
||||||
|
public string Name => typeof(T).Name;
|
||||||
|
}
|
||||||
|
}
|
10
osu.Game/Rulesets/Edit/Tools/ICompositionTool.cs
Normal file
10
osu.Game/Rulesets/Edit/Tools/ICompositionTool.cs
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
|
||||||
|
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Edit.Tools
|
||||||
|
{
|
||||||
|
public interface ICompositionTool
|
||||||
|
{
|
||||||
|
string Name { get; }
|
||||||
|
}
|
||||||
|
}
|
@ -177,8 +177,8 @@ namespace osu.Game.Rulesets.Objects.Legacy
|
|||||||
|
|
||||||
string[] split = str.Split(':');
|
string[] split = str.Split(':');
|
||||||
|
|
||||||
var bank = (OsuLegacyDecoder.LegacySampleBank)Convert.ToInt32(split[0]);
|
var bank = (LegacyDecoder.LegacySampleBank)Convert.ToInt32(split[0]);
|
||||||
var addbank = (OsuLegacyDecoder.LegacySampleBank)Convert.ToInt32(split[1]);
|
var addbank = (LegacyDecoder.LegacySampleBank)Convert.ToInt32(split[1]);
|
||||||
|
|
||||||
// Let's not implement this for now, because this doesn't fit nicely into the bank structure
|
// Let's not implement this for now, because this doesn't fit nicely into the bank structure
|
||||||
//string sampleFile = split2.Length > 4 ? split2[4] : string.Empty;
|
//string sampleFile = split2.Length > 4 ? split2[4] : string.Empty;
|
||||||
|
@ -9,7 +9,7 @@ namespace osu.Game.Rulesets.Replays
|
|||||||
{
|
{
|
||||||
public Vector2 Position => new Vector2(MouseX ?? 0, MouseY ?? 0);
|
public Vector2 Position => new Vector2(MouseX ?? 0, MouseY ?? 0);
|
||||||
|
|
||||||
public bool IsImportant => MouseX.HasValue && MouseY.HasValue && (MouseLeft || MouseRight);
|
public virtual bool IsImportant => MouseX.HasValue && MouseY.HasValue && (MouseLeft || MouseRight);
|
||||||
|
|
||||||
public float? MouseX;
|
public float? MouseX;
|
||||||
public float? MouseY;
|
public float? MouseY;
|
||||||
@ -68,4 +68,4 @@ namespace osu.Game.Rulesets.Replays
|
|||||||
return $"{Time}\t({MouseX},{MouseY})\t{MouseLeft}\t{MouseRight}\t{MouseLeft1}\t{MouseRight1}\t{MouseLeft2}\t{MouseRight2}\t{ButtonState}";
|
return $"{Time}\t({MouseX},{MouseY})\t{MouseLeft}\t{MouseRight}\t{MouseLeft1}\t{MouseRight1}\t{MouseLeft2}\t{MouseRight2}\t{ButtonState}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,9 @@ using osu.Framework.Input.Bindings;
|
|||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Overlays.Settings;
|
using osu.Game.Overlays.Settings;
|
||||||
|
using osu.Game.Rulesets.Edit;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
|
using osu.Game.Rulesets.Scoring;
|
||||||
using osu.Game.Rulesets.UI;
|
using osu.Game.Rulesets.UI;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets
|
namespace osu.Game.Rulesets
|
||||||
@ -49,6 +51,10 @@ namespace osu.Game.Rulesets
|
|||||||
|
|
||||||
public abstract DifficultyCalculator CreateDifficultyCalculator(Beatmap beatmap, Mod[] mods = null);
|
public abstract DifficultyCalculator CreateDifficultyCalculator(Beatmap beatmap, Mod[] mods = null);
|
||||||
|
|
||||||
|
public virtual PerformanceCalculator CreatePerformanceCalculator(Beatmap beatmap, Score score) => null;
|
||||||
|
|
||||||
|
public virtual HitObjectComposer CreateHitObjectComposer() => null;
|
||||||
|
|
||||||
public virtual Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.fa_question_circle };
|
public virtual Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.fa_question_circle };
|
||||||
|
|
||||||
public abstract string Description { get; }
|
public abstract string Description { get; }
|
||||||
|
35
osu.Game/Rulesets/Scoring/PerformanceCalculator.cs
Normal file
35
osu.Game/Rulesets/Scoring/PerformanceCalculator.cs
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
// 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.Objects;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Scoring
|
||||||
|
{
|
||||||
|
public abstract class PerformanceCalculator
|
||||||
|
{
|
||||||
|
public abstract double Calculate(Dictionary<string, double> categoryDifficulty = null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract class PerformanceCalculator<TObject> : PerformanceCalculator
|
||||||
|
where TObject : HitObject
|
||||||
|
{
|
||||||
|
private readonly Dictionary<string, double> attributes = new Dictionary<string, double>();
|
||||||
|
protected IDictionary<string, double> Attributes => attributes;
|
||||||
|
|
||||||
|
protected readonly Beatmap<TObject> Beatmap;
|
||||||
|
protected readonly Score Score;
|
||||||
|
|
||||||
|
protected PerformanceCalculator(Ruleset ruleset, Beatmap beatmap, Score score)
|
||||||
|
{
|
||||||
|
Beatmap = CreateBeatmapConverter().Convert(beatmap);
|
||||||
|
Score = score;
|
||||||
|
|
||||||
|
var diffCalc = ruleset.CreateDifficultyCalculator(beatmap, score.Mods);
|
||||||
|
diffCalc.Calculate(attributes);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract BeatmapConverter<TObject> CreateBeatmapConverter();
|
||||||
|
}
|
||||||
|
}
|
@ -55,6 +55,11 @@ namespace osu.Game.Rulesets.UI
|
|||||||
|
|
||||||
public abstract IEnumerable<HitObject> Objects { get; }
|
public abstract IEnumerable<HitObject> Objects { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The playfield.
|
||||||
|
/// </summary>
|
||||||
|
public Playfield Playfield { get; protected set; }
|
||||||
|
|
||||||
protected readonly Ruleset Ruleset;
|
protected readonly Ruleset Ruleset;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -135,11 +140,6 @@ namespace osu.Game.Rulesets.UI
|
|||||||
|
|
||||||
public override ScoreProcessor CreateScoreProcessor() => new ScoreProcessor<TObject>(this);
|
public override ScoreProcessor CreateScoreProcessor() => new ScoreProcessor<TObject>(this);
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The playfield.
|
|
||||||
/// </summary>
|
|
||||||
public Playfield Playfield { get; private set; }
|
|
||||||
|
|
||||||
protected override Container<Drawable> Content => content;
|
protected override Container<Drawable> Content => content;
|
||||||
private Container content;
|
private Container content;
|
||||||
|
|
||||||
|
@ -5,6 +5,8 @@ using System.Collections.Generic;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Configuration;
|
using osu.Framework.Configuration;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Input;
|
using osu.Framework.Input;
|
||||||
using osu.Framework.Input.Bindings;
|
using osu.Framework.Input.Bindings;
|
||||||
using osu.Framework.Timing;
|
using osu.Framework.Timing;
|
||||||
@ -16,11 +18,24 @@ using OpenTK.Input;
|
|||||||
|
|
||||||
namespace osu.Game.Rulesets.UI
|
namespace osu.Game.Rulesets.UI
|
||||||
{
|
{
|
||||||
public abstract class RulesetInputManager<T> : DatabasedKeyBindingInputManager<T>, ICanAttachKeyCounter, IHasReplayHandler
|
public abstract class RulesetInputManager<T> : PassThroughInputManager, ICanAttachKeyCounter, IHasReplayHandler
|
||||||
where T : struct
|
where T : struct
|
||||||
{
|
{
|
||||||
protected RulesetInputManager(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique) : base(ruleset, variant, unique)
|
public class RulesetKeyBindingContainer : DatabasedKeyBindingInputManager<T>
|
||||||
{
|
{
|
||||||
|
public RulesetKeyBindingContainer(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique)
|
||||||
|
: base(ruleset, variant, unique)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected readonly KeyBindingContainer<T> KeyBindingContainer;
|
||||||
|
|
||||||
|
protected override Container<Drawable> Content => KeyBindingContainer;
|
||||||
|
|
||||||
|
protected RulesetInputManager(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique)
|
||||||
|
{
|
||||||
|
InternalChild = KeyBindingContainer = new RulesetKeyBindingContainer(ruleset, variant, unique);
|
||||||
}
|
}
|
||||||
|
|
||||||
#region Action mapping (for replays)
|
#region Action mapping (for replays)
|
||||||
@ -41,10 +56,10 @@ namespace osu.Game.Rulesets.UI
|
|||||||
List<T> newActions = replayState.PressedActions;
|
List<T> newActions = replayState.PressedActions;
|
||||||
|
|
||||||
foreach (var released in lastPressedActions.Except(newActions))
|
foreach (var released in lastPressedActions.Except(newActions))
|
||||||
PropagateReleased(KeyBindingInputQueue, released);
|
KeyBindingContainer.TriggerReleased(released);
|
||||||
|
|
||||||
foreach (var pressed in newActions.Except(lastPressedActions))
|
foreach (var pressed in newActions.Except(lastPressedActions))
|
||||||
PropagatePressed(KeyBindingInputQueue, pressed);
|
KeyBindingContainer.TriggerPressed(pressed);
|
||||||
|
|
||||||
lastPressedActions = newActions;
|
lastPressedActions = newActions;
|
||||||
}
|
}
|
||||||
@ -203,7 +218,7 @@ namespace osu.Game.Rulesets.UI
|
|||||||
Add(receptor);
|
Add(receptor);
|
||||||
keyCounter.SetReceptor(receptor);
|
keyCounter.SetReceptor(receptor);
|
||||||
|
|
||||||
keyCounter.AddRange(DefaultKeyBindings.Select(b => b.GetAction<T>()).Distinct().Select(b => new KeyCounterAction<T>(b)));
|
keyCounter.AddRange(KeyBindingContainer.DefaultKeyBindings.Select(b => b.GetAction<T>()).Distinct().Select(b => new KeyCounterAction<T>(b)));
|
||||||
}
|
}
|
||||||
|
|
||||||
public class ActionReceptor : KeyCounterCollection.Receptor, IKeyBindingHandler<T>
|
public class ActionReceptor : KeyCounterCollection.Receptor, IKeyBindingHandler<T>
|
||||||
|
@ -40,7 +40,8 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
timeline.RelativeChildSize = new Vector2((float)Math.Max(1, Beatmap.Value.Track.Length), 1);
|
// Todo: This should be handled more gracefully
|
||||||
|
timeline.RelativeChildSize = Beatmap.Value.Track.Length == double.PositiveInfinity ? Vector2.One : new Vector2((float)Math.Max(1, Beatmap.Value.Track.Length), 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void Add(Drawable visualisation) => timeline.Add(visualisation);
|
protected void Add(Drawable visualisation) => timeline.Add(visualisation);
|
||||||
|
@ -6,49 +6,99 @@ using osu.Framework.Extensions.Color4Extensions;
|
|||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Framework.Logging;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Screens.Edit.Screens.Compose.Timeline;
|
using osu.Game.Screens.Edit.Screens.Compose.Timeline;
|
||||||
|
|
||||||
namespace osu.Game.Screens.Edit.Screens.Compose
|
namespace osu.Game.Screens.Edit.Screens.Compose
|
||||||
{
|
{
|
||||||
public class Compose : EditorScreen
|
public class Compose : EditorScreen
|
||||||
{
|
{
|
||||||
|
private const float vertical_margins = 10;
|
||||||
|
private const float horizontal_margins = 20;
|
||||||
|
|
||||||
|
private readonly Container composerContainer;
|
||||||
|
|
||||||
public Compose()
|
public Compose()
|
||||||
{
|
{
|
||||||
ScrollableTimeline timeline;
|
ScrollableTimeline timeline;
|
||||||
Children = new[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
new Container
|
new GridContainer
|
||||||
{
|
{
|
||||||
Name = "Timeline",
|
RelativeSizeAxes = Axes.Both,
|
||||||
RelativeSizeAxes = Axes.X,
|
Content = new[]
|
||||||
Height = 110,
|
|
||||||
Children = new Drawable[]
|
|
||||||
{
|
{
|
||||||
new Box
|
new Drawable[]
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
new Container
|
||||||
Colour = Color4.Black.Opacity(0.5f)
|
|
||||||
},
|
|
||||||
new Container
|
|
||||||
{
|
|
||||||
Name = "Content",
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Padding = new MarginPadding { Horizontal = 17, Vertical = 10 },
|
|
||||||
Children = new Drawable[]
|
|
||||||
{
|
{
|
||||||
new Container
|
Name = "Timeline",
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
new Box
|
||||||
Padding = new MarginPadding { Right = 115 },
|
{
|
||||||
Child = timeline = new ScrollableTimeline { RelativeSizeAxes = Axes.Both }
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Colour = Color4.Black.Opacity(0.5f)
|
||||||
|
},
|
||||||
|
new Container
|
||||||
|
{
|
||||||
|
Name = "Timeline content",
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Padding = new MarginPadding { Horizontal = horizontal_margins, Vertical = vertical_margins },
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new Container
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Padding = new MarginPadding { Right = 115 },
|
||||||
|
Child = timeline = new ScrollableTimeline { RelativeSizeAxes = Axes.Both }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
new Drawable[]
|
||||||
|
{
|
||||||
|
composerContainer = new Container
|
||||||
|
{
|
||||||
|
Name = "Composer content",
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Padding = new MarginPadding { Horizontal = horizontal_margins, Vertical = vertical_margins },
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
}
|
RowDimensions = new[] { new Dimension(GridSizeMode.Absolute, 110) }
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
timeline.Beatmap.BindTo(Beatmap);
|
timeline.Beatmap.BindTo(Beatmap);
|
||||||
|
Beatmap.ValueChanged += beatmapChanged;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void beatmapChanged(WorkingBeatmap newBeatmap)
|
||||||
|
{
|
||||||
|
composerContainer.Clear();
|
||||||
|
|
||||||
|
var ruleset = newBeatmap.BeatmapInfo.Ruleset?.CreateInstance();
|
||||||
|
if (ruleset == null)
|
||||||
|
{
|
||||||
|
Logger.Log("Beatmap doesn't have a ruleset assigned.");
|
||||||
|
// ExitRequested?.Invoke();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var composer = ruleset.CreateHitObjectComposer();
|
||||||
|
if (composer == null)
|
||||||
|
{
|
||||||
|
Logger.Log($"Ruleset {ruleset.Description} doesn't support hitobject composition.");
|
||||||
|
// ExitRequested?.Invoke();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
composerContainer.Child = composer;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,123 @@
|
|||||||
|
// 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 OpenTK;
|
||||||
|
using OpenTK.Graphics;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
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.Input;
|
||||||
|
using osu.Game.Graphics;
|
||||||
|
using osu.Game.Graphics.Sprites;
|
||||||
|
using osu.Game.Graphics.UserInterface;
|
||||||
|
|
||||||
|
namespace osu.Game.Screens.Edit.Screens.Compose.RadioButtons
|
||||||
|
{
|
||||||
|
public class DrawableRadioButton : TriangleButton
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Invoked when this <see cref="DrawableRadioButton"/> has been selected.
|
||||||
|
/// </summary>
|
||||||
|
public Action<RadioButton> Selected;
|
||||||
|
|
||||||
|
private Color4 defaultBackgroundColour;
|
||||||
|
private Color4 defaultBubbleColour;
|
||||||
|
private Color4 selectedBackgroundColour;
|
||||||
|
private Color4 selectedBubbleColour;
|
||||||
|
|
||||||
|
private readonly Drawable bubble;
|
||||||
|
private readonly RadioButton button;
|
||||||
|
|
||||||
|
public DrawableRadioButton(RadioButton button)
|
||||||
|
{
|
||||||
|
this.button = button;
|
||||||
|
|
||||||
|
Text = button.Text;
|
||||||
|
Action = button.Action;
|
||||||
|
|
||||||
|
RelativeSizeAxes = Axes.X;
|
||||||
|
|
||||||
|
bubble = new CircularContainer
|
||||||
|
{
|
||||||
|
Anchor = Anchor.CentreLeft,
|
||||||
|
Origin = Anchor.CentreLeft,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
FillMode = FillMode.Fit,
|
||||||
|
Scale = new Vector2(0.5f),
|
||||||
|
X = 10,
|
||||||
|
Masking = true,
|
||||||
|
Blending = BlendingMode.Additive,
|
||||||
|
Child = new Box { RelativeSizeAxes = Axes.Both }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(OsuColour colours)
|
||||||
|
{
|
||||||
|
defaultBackgroundColour = colours.Gray3;
|
||||||
|
defaultBubbleColour = defaultBackgroundColour.Darken(0.5f);
|
||||||
|
selectedBackgroundColour = colours.BlueDark;
|
||||||
|
selectedBubbleColour = selectedBackgroundColour.Lighten(0.5f);
|
||||||
|
|
||||||
|
Triangles.Alpha = 0;
|
||||||
|
|
||||||
|
Content.EdgeEffect = new EdgeEffectParameters
|
||||||
|
{
|
||||||
|
Type = EdgeEffectType.Shadow,
|
||||||
|
Radius = 2,
|
||||||
|
Offset = new Vector2(0, 1),
|
||||||
|
Colour = Color4.Black.Opacity(0.5f)
|
||||||
|
};
|
||||||
|
|
||||||
|
Add(bubble);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
button.Selected.ValueChanged += v =>
|
||||||
|
{
|
||||||
|
updateSelectionState();
|
||||||
|
if (v)
|
||||||
|
Selected?.Invoke(button);
|
||||||
|
};
|
||||||
|
|
||||||
|
updateSelectionState();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateSelectionState()
|
||||||
|
{
|
||||||
|
if (!IsLoaded)
|
||||||
|
return;
|
||||||
|
|
||||||
|
BackgroundColour = button.Selected ? selectedBackgroundColour : defaultBackgroundColour;
|
||||||
|
bubble.Colour = button.Selected ? selectedBubbleColour : defaultBubbleColour;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool OnClick(InputState state)
|
||||||
|
{
|
||||||
|
if (button.Selected)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if (!Enabled)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
button.Selected.Value = true;
|
||||||
|
|
||||||
|
return base.OnClick(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override SpriteText CreateText() => new OsuSpriteText
|
||||||
|
{
|
||||||
|
Depth = -1,
|
||||||
|
Origin = Anchor.CentreLeft,
|
||||||
|
Anchor = Anchor.CentreLeft,
|
||||||
|
X = 40f
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,51 @@
|
|||||||
|
// 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.Configuration;
|
||||||
|
|
||||||
|
namespace osu.Game.Screens.Edit.Screens.Compose.RadioButtons
|
||||||
|
{
|
||||||
|
public class RadioButton
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Whether this <see cref="RadioButton"/> is selected.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public readonly BindableBool Selected;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The text that should be displayed in this button.
|
||||||
|
/// </summary>
|
||||||
|
public string Text;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The <see cref="Action"/> that should be invoked when this button is selected.
|
||||||
|
/// </summary>
|
||||||
|
public Action Action;
|
||||||
|
|
||||||
|
public RadioButton(string text, Action action)
|
||||||
|
{
|
||||||
|
Text = text;
|
||||||
|
Action = action;
|
||||||
|
Selected = new BindableBool();
|
||||||
|
}
|
||||||
|
|
||||||
|
public RadioButton(string text)
|
||||||
|
: this(text, null)
|
||||||
|
{
|
||||||
|
Text = text;
|
||||||
|
Action = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Selects this <see cref="RadioButton"/>.
|
||||||
|
/// </summary>
|
||||||
|
public void Select() => Selected.Value = true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Deselects this <see cref="RadioButton"/>.
|
||||||
|
/// </summary>
|
||||||
|
public void Deselect() => Selected.Value = false;
|
||||||
|
}
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user