1
0
mirror of https://github.com/ppy/osu.git synced 2025-02-15 16:03:01 +08:00

Merge remote-tracking branch 'origin/master'

This commit is contained in:
smoogipoo 2017-12-30 16:17:14 +09:00
commit 714d93dd92
288 changed files with 15852 additions and 2735 deletions

77
.vscode/tasks.json vendored
View File

@ -2,63 +2,70 @@
// See https://go.microsoft.com/fwlink/?LinkId=733558
// for the documentation about the tasks.json format
"version": "2.0.0",
"command": "msbuild",
"type": "shell",
"suppressTaskName": true,
"args": [
"/property:GenerateFullPaths=true",
"/property:DebugType=portable",
"/verbosity:minimal",
"/m" //parallel compiling support.
],
"tasks": [{
"taskName": "Build (Debug)",
"label": "Build (Debug)",
"type": "shell",
"command": "msbuild",
"args": [
"/p:GenerateFullPaths=true",
"/p:DebugType=portable",
"/m",
"/v:m"
],
"group": {
"kind": "build",
"isDefault": true
},
"problemMatcher": [
"$msCompile"
]
"problemMatcher": "$msCompile"
},
{
"taskName": "Build (Release)",
"label": "Build (Release)",
"type": "shell",
"command": "msbuild",
"args": [
"/p:Configuration=Release",
"/p:DebugType=portable",
"/p:GenerateFullPaths=true",
"/m",
"/v:m"
],
"group": "build",
"args": [
"/property:Configuration=Release"
],
"problemMatcher": [
"$msCompile"
]
"problemMatcher": "$msCompile"
},
{
"taskName": "Clean (Debug)",
"label": "Clean (Debug)",
"type": "shell",
"command": "msbuild",
"args": [
"/target:Clean"
"/p:DebugType=portable",
"/p:GenerateFullPaths=true",
"/m",
"/t:Clean",
"/v:m"
],
"problemMatcher": [
"$msCompile"
]
"problemMatcher": "$msCompile"
},
{
"taskName": "Clean (Release)",
"label": "Clean (Release)",
"type": "shell",
"command": "msbuild",
"args": [
"/target:Clean",
"/property:Configuration=Release"
"/p:Configuration=Release",
"/p:GenerateFullPaths=true",
"/p:DebugType=portable",
"/m",
"/t:Clean",
"/v:m"
],
"problemMatcher": [
"$msCompile"
]
"problemMatcher": "$msCompile"
},
{
"taskName": "Clean All",
"label": "Clean All",
"dependsOn": [
"Clean (Debug)",
"Clean (Release)"
],
"problemMatcher": [
"$msCompile"
]
"problemMatcher": "$msCompile"
}
]
}

36
COMPILING.md Normal file
View File

@ -0,0 +1,36 @@
# Linux
### 1. Requirements:
Mono >= 5.4.0 (>= 5.8.0 recommended)
Please check [here](http://www.mono-project.com/download/) for stable or [here](http://www.mono-project.com/download/alpha/) for an alpha release.
NuGet >= 4.4.0
msbuild
git
### 2. Cloning project
Clone the entire repository with submodules using
```
git clone https://github.com/ppy/osu --recursive
```
Then restore NuGet packages from the repository
```
nuget restore
```
### 3. Compiling
Simply run `msbuild` where `osu.sln` is located, this will create all binaries in `osu/osu.Desktop/bin/Debug`.
### 4. Optimizing
If you want additional performance you can change build type to Release with
```
msbuild -p:Configuration=Release
```
Additionally, mono provides an AOT utility which attempts to precompile binaries. You can utilize that by running
```
mono --aot ./osu\!.exe
```
### 5. Troubleshooting
You may run into trouble with NuGet versioning, as the one in packaging system is almost always out of date. Simply run
```
nuget
sudo nuget update -self
```
**Warning** NuGet creates few config files when it's run for the first time.
Do not run NuGet as root on the first run or you might run into very peculiar issues.

View File

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<add key="opentk-develop" value="https://www.myget.org/F/opentk-develop" />
</packageSources>
</configuration>

@ -1 +1 @@
Subproject commit 4287ee8043fb1419017359bc3a5db5dc06bc643f
Subproject commit e01f71160fb9b3167efcd177c7d7dba9e5d36604

View File

@ -110,7 +110,7 @@ namespace osu.Desktop.Overlays
// only show a notification if we've previously saved a version to the config file (ie. not the first run).
if (!string.IsNullOrEmpty(lastVersion))
Scheduler.AddDelayed(() => notificationOverlay.Post(new UpdateCompleteNotification(version)), 5000);
notificationOverlay.Post(new UpdateCompleteNotification(version));
}
}

View File

@ -135,8 +135,8 @@
<HintPath>$(SolutionDir)\packages\squirrel.windows.1.7.8\lib\Net45\NuGet.Squirrel.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="OpenTK, Version=3.0.0.0, Culture=neutral, PublicKeyToken=bad199fe84eb3df4, processorArchitecture=MSIL">
<HintPath>$(SolutionDir)\packages\OpenTK.3.0.0-git00009\lib\net20\OpenTK.dll</HintPath>
<Reference Include="OpenTK, Version=3.0.0.0, Culture=neutral, PublicKeyToken=bad199fe84eb3df4">
<HintPath>$(SolutionDir)\packages\ppy.OpenTK.3.0.11\lib\net45\OpenTK.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="SharpCompress, Version=0.18.1.0, Culture=neutral, PublicKeyToken=afb0a02973931d96, processorArchitecture=MSIL">

View File

@ -6,7 +6,7 @@ Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/maste
<packages>
<package id="DeltaCompressionDotNet" version="1.1.0" targetFramework="net45" />
<package id="Mono.Cecil" version="0.9.6.4" targetFramework="net45" />
<package id="OpenTK" version="3.0.0-git00009" targetFramework="net461" />
<package id="ppy.OpenTK" version="3.0.11" targetFramework="net461" />
<package id="SharpCompress" version="0.18.1" targetFramework="net461" />
<package id="Splat" version="2.0.0" targetFramework="net45" />
<package id="SQLitePCLRaw.bundle_green" version="1.1.8" targetFramework="net461" />

View File

@ -93,6 +93,8 @@ namespace osu.Game.Rulesets.Catch
public override string Description => "osu!catch";
public override string ShortName => "fruits";
public override Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.fa_osu_fruits_o };
public override DifficultyCalculator CreateDifficultyCalculator(Beatmap beatmap, Mod[] mods = null) => new CatchDifficultyCalculator(beatmap);

View File

@ -37,9 +37,9 @@ namespace osu.Game.Rulesets.Catch.Objects
/// </summary>
public CatchHitObject HyperDashTarget;
public override void ApplyDefaults(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty)
protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty)
{
base.ApplyDefaults(controlPointInfo, difficulty);
base.ApplyDefaultsToSelf(controlPointInfo, difficulty);
Scale = 1.0f - 0.7f * (difficulty.CircleSize - 5) / 5;
}

View File

@ -1,6 +1,7 @@
// 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.Linq;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using OpenTK;
@ -25,7 +26,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
RelativeChildSize = new Vector2(1, (float)HitObject.Duration)
};
foreach (CatchHitObject tick in s.Ticks)
foreach (CatchHitObject tick in s.NestedHitObjects.OfType<CatchHitObject>())
{
TinyDroplet tiny = tick as TinyDroplet;
if (tiny != null)

View File

@ -11,7 +11,6 @@ using osu.Game.Rulesets.Catch.UI;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
using OpenTK;
using osu.Framework.Lists;
namespace osu.Game.Rulesets.Catch.Objects
{
@ -29,9 +28,9 @@ namespace osu.Game.Rulesets.Catch.Objects
public double Velocity;
public double TickDistance;
public override void ApplyDefaults(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty)
protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty)
{
base.ApplyDefaults(controlPointInfo, difficulty);
base.ApplyDefaultsToSelf(controlPointInfo, difficulty);
TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(StartTime);
DifficultyControlPoint difficultyPoint = controlPointInfo.DifficultyPointAt(StartTime);
@ -42,92 +41,94 @@ namespace osu.Game.Rulesets.Catch.Objects
TickDistance = scoringDistance / difficulty.SliderTickRate;
}
public IEnumerable<CatchHitObject> Ticks
protected override void CreateNestedHitObjects()
{
get
base.CreateNestedHitObjects();
createTicks();
}
private void createTicks()
{
if (TickDistance == 0)
return;
var length = Curve.Distance;
var tickDistance = Math.Min(TickDistance, length);
var repeatDuration = length / Velocity;
var minDistanceFromEnd = Velocity * 0.01;
AddNested(new Fruit
{
SortedList<CatchHitObject> ticks = new SortedList<CatchHitObject>((a, b) => a.StartTime.CompareTo(b.StartTime));
Samples = Samples,
ComboColour = ComboColour,
StartTime = StartTime,
X = X
});
if (TickDistance == 0)
return ticks;
for (var repeat = 0; repeat < RepeatCount; repeat++)
{
var repeatStartTime = StartTime + repeat * repeatDuration;
var reversed = repeat % 2 == 1;
var length = Curve.Distance;
var tickDistance = Math.Min(TickDistance, length);
var repeatDuration = length / Velocity;
var minDistanceFromEnd = Velocity * 0.01;
ticks.Add(new Fruit
for (var d = tickDistance; d <= length; d += tickDistance)
{
Samples = Samples,
ComboColour = ComboColour,
StartTime = StartTime,
X = X
});
if (d > length - minDistanceFromEnd)
break;
for (var repeat = 0; repeat < RepeatCount; repeat++)
{
var repeatStartTime = StartTime + repeat * repeatDuration;
var reversed = repeat % 2 == 1;
var timeProgress = d / length;
var distanceProgress = reversed ? 1 - timeProgress : timeProgress;
for (var d = tickDistance; d <= length; d += tickDistance)
var lastTickTime = repeatStartTime + timeProgress * repeatDuration;
AddNested(new Droplet
{
if (d > length - minDistanceFromEnd)
break;
var timeProgress = d / length;
var distanceProgress = reversed ? 1 - timeProgress : timeProgress;
var lastTickTime = repeatStartTime + timeProgress * repeatDuration;
ticks.Add(new Droplet
{
StartTime = lastTickTime,
ComboColour = ComboColour,
X = Curve.PositionAt(distanceProgress).X / CatchPlayfield.BASE_WIDTH,
Samples = new SampleInfoList(Samples.Select(s => new SampleInfo
{
Bank = s.Bank,
Name = @"slidertick",
Volume = s.Volume
}))
});
}
double tinyTickInterval = tickDistance / length * repeatDuration;
while (tinyTickInterval > 100)
tinyTickInterval /= 2;
for (double t = 0; t < repeatDuration; t += tinyTickInterval)
{
double progress = reversed ? 1 - t / repeatDuration : t / repeatDuration;
ticks.Add(new TinyDroplet
{
StartTime = repeatStartTime + t,
ComboColour = ComboColour,
X = Curve.PositionAt(progress).X / CatchPlayfield.BASE_WIDTH,
Samples = new SampleInfoList(Samples.Select(s => new SampleInfo
{
Bank = s.Bank,
Name = @"slidertick",
Volume = s.Volume
}))
});
}
ticks.Add(new Fruit
{
Samples = Samples,
StartTime = lastTickTime,
ComboColour = ComboColour,
StartTime = repeatStartTime + repeatDuration,
X = Curve.PositionAt(reversed ? 0 : 1).X / CatchPlayfield.BASE_WIDTH
X = Curve.PositionAt(distanceProgress).X / CatchPlayfield.BASE_WIDTH,
Samples = new List<SampleInfo>(Samples.Select(s => new SampleInfo
{
Bank = s.Bank,
Name = @"slidertick",
Volume = s.Volume
}))
});
}
return ticks;
double tinyTickInterval = tickDistance / length * repeatDuration;
while (tinyTickInterval > 100)
tinyTickInterval /= 2;
for (double t = 0; t < repeatDuration; t += tinyTickInterval)
{
double progress = reversed ? 1 - t / repeatDuration : t / repeatDuration;
AddNested(new TinyDroplet
{
StartTime = repeatStartTime + t,
ComboColour = ComboColour,
X = Curve.PositionAt(progress).X / CatchPlayfield.BASE_WIDTH,
Samples = new List<SampleInfo>(Samples.Select(s => new SampleInfo
{
Bank = s.Bank,
Name = @"slidertick",
Volume = s.Volume
}))
});
}
AddNested(new Fruit
{
Samples = Samples,
ComboColour = ComboColour,
StartTime = repeatStartTime + repeatDuration,
X = Curve.PositionAt(reversed ? 0 : 1).X / CatchPlayfield.BASE_WIDTH
});
}
}
public double EndTime => StartTime + RepeatCount * Curve.Distance / Velocity;
public float EndX => Curve.PositionAt(ProgressAt(1)).X / CatchPlayfield.BASE_WIDTH;
@ -146,7 +147,7 @@ namespace osu.Game.Rulesets.Catch.Objects
set { Curve.ControlPoints = value; }
}
public List<SampleInfoList> RepeatSamples { get; set; } = new List<SampleInfoList>();
public List<List<SampleInfo>> RepeatSamples { get; set; } = new List<List<SampleInfo>>();
public CurveType CurveType
{

View File

@ -1,6 +1,7 @@
// 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.Linq;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Catch.Judgements;
using osu.Game.Rulesets.Catch.Objects;
@ -28,7 +29,7 @@ namespace osu.Game.Rulesets.Catch.Scoring
AddJudgement(new CatchJudgement { Result = HitResult.Perfect });
AddJudgement(new CatchJudgement { Result = HitResult.Perfect });
foreach (var unused in stream.Ticks)
foreach (var unused in stream.NestedHitObjects.OfType<CatchHitObject>())
AddJudgement(new CatchJudgement { Result = HitResult.Perfect });
continue;

View File

@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Catch.Tests
{
[TestFixture]
[Ignore("getting CI working")]
internal class TestCaseCatcherArea : OsuTestCase
public class TestCaseCatcherArea : OsuTestCase
{
private RulesetInfo catchRuleset;
private TestCatcherArea catcherArea;

View File

@ -36,8 +36,8 @@
<HintPath>$(SolutionDir)\packages\NUnit.3.8.1\lib\net45\nunit.framework.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="OpenTK, Version=3.0.0.0, Culture=neutral, PublicKeyToken=bad199fe84eb3df4, processorArchitecture=MSIL">
<HintPath>$(SolutionDir)\packages\OpenTK.3.0.0-git00009\lib\net20\OpenTK.dll</HintPath>
<Reference Include="OpenTK, Version=3.0.0.0, Culture=neutral, PublicKeyToken=bad199fe84eb3df4">
<HintPath>$(SolutionDir)\packages\ppy.OpenTK.3.0.11\lib\net45\OpenTK.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System" />
@ -90,6 +90,9 @@
<Private>True</Private>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="NUnit" version="3.8.1" targetFramework="net461" />
<package id="OpenTK" version="3.0.0-git00009" targetFramework="net461" />
<package id="ppy.OpenTK" version="3.0.11" targetFramework="net461" />
</packages>

View File

@ -192,7 +192,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
/// </summary>
/// <param name="time">The time to retrieve the sample info list from.</param>
/// <returns></returns>
private SampleInfoList sampleInfoListAt(double time)
private List<SampleInfo> sampleInfoListAt(double time)
{
var curveData = HitObject as IHasCurve;

View File

@ -2,6 +2,7 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using System.Collections.Generic;
using System.Linq;
using osu.Game.Audio;
using osu.Game.Beatmaps;
@ -435,7 +436,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
/// </summary>
/// <param name="time">The time to retrieve the sample info list from.</param>
/// <returns></returns>
private SampleInfoList sampleInfoListAt(double time)
private List<SampleInfo> sampleInfoListAt(double time)
{
var curveData = HitObject as IHasCurve;

View File

@ -1,6 +1,7 @@
// 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.Mania.MathUtils;
using osu.Game.Rulesets.Objects;
@ -76,10 +77,10 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
Duration = endTime - HitObject.StartTime
};
hold.Head.Samples.Add(new SampleInfo
{
Name = SampleInfo.HIT_NORMAL
});
if (hold.Head.Samples == null)
hold.Head.Samples = new List<SampleInfo>();
hold.Head.Samples.Add(new SampleInfo { Name = SampleInfo.HIT_NORMAL });
hold.Tail.Samples = HitObject.Samples;

View File

@ -105,6 +105,8 @@ namespace osu.Game.Rulesets.Mania
public override string Description => "osu!mania";
public override string ShortName => "mania";
public override Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.fa_osu_mania_o };
public override DifficultyCalculator CreateDifficultyCalculator(Beatmap beatmap, Mod[] mods = null) => new ManiaDifficultyCalculator(beatmap);

View File

@ -77,7 +77,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
}
});
foreach (var tick in HitObject.Ticks)
foreach (var tick in HitObject.NestedHitObjects.OfType<HoldNoteTick>())
{
var drawableTick = new DrawableHoldNoteTick(tick)
{

View File

@ -1,7 +1,6 @@
// 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.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Objects.Types;
@ -63,9 +62,9 @@ namespace osu.Game.Rulesets.Mania.Objects
/// </summary>
private double tickSpacing = 50;
public override void ApplyDefaults(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty)
protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty)
{
base.ApplyDefaults(controlPointInfo, difficulty);
base.ApplyDefaultsToSelf(controlPointInfo, difficulty);
TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(StartTime);
tickSpacing = timingPoint.BeatLength / difficulty.SliderTickRate;
@ -74,29 +73,27 @@ namespace osu.Game.Rulesets.Mania.Objects
Tail.ApplyDefaults(controlPointInfo, difficulty);
}
/// <summary>
/// The scoring scoring ticks of the hold note.
/// </summary>
public IEnumerable<HoldNoteTick> Ticks => ticks ?? (ticks = createTicks());
private List<HoldNoteTick> ticks;
private List<HoldNoteTick> createTicks()
protected override void CreateNestedHitObjects()
{
var ret = new List<HoldNoteTick>();
base.CreateNestedHitObjects();
createTicks();
}
private void createTicks()
{
if (tickSpacing == 0)
return ret;
return;
for (double t = StartTime + tickSpacing; t <= EndTime - tickSpacing; t += tickSpacing)
{
ret.Add(new HoldNoteTick
AddNested(new HoldNoteTick
{
StartTime = t,
Column = Column
});
}
return ret;
}
/// <summary>
@ -110,9 +107,9 @@ namespace osu.Game.Rulesets.Mania.Objects
/// </summary>
private const double release_window_lenience = 1.5;
public override void ApplyDefaults(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty)
protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty)
{
base.ApplyDefaults(controlPointInfo, difficulty);
base.ApplyDefaultsToSelf(controlPointInfo, difficulty);
HitWindows *= release_window_lenience;
}

View File

@ -1,6 +1,7 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using Newtonsoft.Json;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Mania.Judgements;
@ -15,11 +16,12 @@ namespace osu.Game.Rulesets.Mania.Objects
/// <summary>
/// The key-press hit window for this note.
/// </summary>
[JsonIgnore]
public HitWindows HitWindows { get; protected set; } = new HitWindows();
public override void ApplyDefaults(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty)
protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty)
{
base.ApplyDefaults(controlPointInfo, difficulty);
base.ApplyDefaultsToSelf(controlPointInfo, difficulty);
HitWindows = new HitWindows(difficulty.OverallDifficulty);
}

View File

@ -116,7 +116,7 @@ namespace osu.Game.Rulesets.Mania.Scoring
AddJudgement(new ManiaJudgement { Result = HitResult.Perfect });
// Ticks
int tickCount = holdNote.Ticks.Count();
int tickCount = holdNote.NestedHitObjects.OfType<HoldNoteTick>().Count();
for (int i = 0; i < tickCount; i++)
AddJudgement(new HoldNoteTickJudgement { Result = HitResult.Perfect });
}

View File

@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Mania.Tests
{
[TestFixture]
[Ignore("getting CI working")]
internal class TestCaseManiaHitObjects : OsuTestCase
public class TestCaseManiaHitObjects : OsuTestCase
{
public TestCaseManiaHitObjects()
{

View File

@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Mania.Tests
{
[TestFixture]
[Ignore("getting CI working")]
internal class TestCaseManiaPlayfield : OsuTestCase
public class TestCaseManiaPlayfield : OsuTestCase
{
private const double start_time = 500;
private const double duration = 500;

View File

@ -9,7 +9,6 @@ using osu.Framework.Allocation;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics;
using osu.Framework.Input;
using osu.Framework.Lists;
using osu.Framework.MathUtils;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
@ -44,7 +43,7 @@ namespace osu.Game.Rulesets.Mania.UI
// Generate the bar lines
double lastObjectTime = (Objects.LastOrDefault() as IHasEndTime)?.EndTime ?? Objects.LastOrDefault()?.StartTime ?? double.MaxValue;
SortedList<TimingControlPoint> timingPoints = Beatmap.ControlPointInfo.TimingPoints;
var timingPoints = Beatmap.ControlPointInfo.TimingPoints;
var barLines = new List<DrawableBarLine>();
for (int i = 0; i < timingPoints.Count; i++)

View File

@ -36,8 +36,12 @@
<HintPath>$(SolutionDir)\packages\NUnit.3.8.1\lib\net45\nunit.framework.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="OpenTK, Version=3.0.0.0, Culture=neutral, PublicKeyToken=bad199fe84eb3df4, processorArchitecture=MSIL">
<HintPath>$(SolutionDir)\packages\OpenTK.3.0.0-git00009\lib\net20\OpenTK.dll</HintPath>
<Reference Include="OpenTK, Version=3.0.0.0, Culture=neutral, PublicKeyToken=bad199fe84eb3df4">
<HintPath>$(SolutionDir)\packages\ppy.OpenTK.3.0.11\lib\net45\OpenTK.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Newtonsoft.Json, Version=10.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
<HintPath>$(SolutionDir)\packages\Newtonsoft.Json.10.0.3\lib\net45\Newtonsoft.Json.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System" />
@ -114,6 +118,9 @@
<Private>True</Private>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="NUnit" version="3.8.1" targetFramework="net461" />
<package id="OpenTK" version="3.0.0-git00009" targetFramework="net461" />
<package id="ppy.OpenTK" version="3.0.11" targetFramework="net461" />
</packages>

View File

@ -9,5 +9,7 @@ namespace osu.Game.Rulesets.Osu.Edit
public class OsuEditPlayfield : OsuPlayfield
{
protected override CursorContainer CreateCursor() => null;
protected override bool ProxyApproachCircles => false;
}
}

View File

@ -11,8 +11,11 @@ using System.Collections.Generic;
using System.Linq;
using osu.Game.Rulesets.Osu.UI;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.UI;
using OpenTK;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Osu.Objects.Drawables;
using osu.Framework.Graphics;
using osu.Game.Rulesets.Objects.Types;
namespace osu.Game.Rulesets.Osu.Mods
{
@ -23,13 +26,86 @@ namespace osu.Game.Rulesets.Osu.Mods
public class OsuModEasy : ModEasy
{
}
public class OsuModHidden : ModHidden
public class OsuModHidden : ModHidden, IApplicableToDrawableHitObjects
{
public override string Description => @"Play with no approach circles and fading notes for a slight score advantage.";
public override double ScoreMultiplier => 1.06;
private const double fade_in_duration_multiplier = 0.4;
private const double fade_out_duration_multiplier = 0.3;
private float preEmpt => DrawableOsuHitObject.TIME_PREEMPT;
public void ApplyToDrawableHitObjects(IEnumerable<DrawableHitObject> drawables)
{
foreach (var d in drawables.OfType<DrawableOsuHitObject>())
{
d.ApplyCustomUpdateState += ApplyHiddenState;
d.FadeInDuration = preEmpt * fade_in_duration_multiplier;
}
}
protected void ApplyHiddenState(DrawableHitObject drawable, ArmedState state)
{
if (!(drawable is DrawableOsuHitObject d))
return;
var fadeOutStartTime = d.HitObject.StartTime - preEmpt + d.FadeInDuration;
var fadeOutDuration = preEmpt * fade_out_duration_multiplier;
// new duration from completed fade in to end (before fading out)
var longFadeDuration = ((d.HitObject as IHasEndTime)?.EndTime ?? d.HitObject.StartTime) - fadeOutStartTime;
switch (drawable)
{
case DrawableHitCircle circle:
// we don't want to see the approach circle
circle.ApproachCircle.Hide();
// fade out immediately after fade in.
using (drawable.BeginAbsoluteSequence(fadeOutStartTime, true))
circle.FadeOut(fadeOutDuration);
break;
case DrawableSlider slider:
using (slider.BeginAbsoluteSequence(fadeOutStartTime, true))
{
slider.Body.FadeOut(longFadeDuration, Easing.Out);
// delay a bit less to let the sliderball fade out peacefully instead of having a hard cut
using (slider.BeginDelayedSequence(longFadeDuration - fadeOutDuration, true))
slider.Ball.FadeOut(fadeOutDuration);
}
break;
case DrawableSpinner spinner:
// hide elements we don't care about.
spinner.Disc.Hide();
spinner.Ticks.Hide();
spinner.Background.Hide();
using (spinner.BeginAbsoluteSequence(fadeOutStartTime + longFadeDuration, true))
{
spinner.FadeOut(fadeOutDuration);
// speed up the end sequence accordingly
switch (state)
{
case ArmedState.Hit:
spinner.ScaleTo(spinner.Scale * 1.2f, fadeOutDuration * 2, Easing.Out);
break;
case ArmedState.Miss:
spinner.ScaleTo(spinner.Scale * 0.8f, fadeOutDuration * 2, Easing.In);
break;
}
spinner.Expire();
}
break;
}
}
}
public class OsuModHardRock : ModHardRock, IApplicableToHitObject<OsuHitObject>
@ -51,11 +127,6 @@ namespace osu.Game.Rulesets.Osu.Mods
slider.ControlPoints = newControlPoints;
slider.Curve?.Calculate(); // Recalculate the slider curve
}
public void ApplyToHitObjects(RulesetContainer<OsuHitObject> rulesetContainer)
{
}
}
public class OsuModSuddenDeath : ModSuddenDeath
@ -96,7 +167,6 @@ namespace osu.Game.Rulesets.Osu.Mods
public class OsuModPerfect : ModPerfect
{
}
public class OsuModSpunOut : Mod

View File

@ -6,7 +6,6 @@ using osu.Framework.Graphics;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces;
using OpenTK;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Osu.Judgements;
namespace osu.Game.Rulesets.Osu.Objects.Drawables
@ -21,7 +20,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
private readonly NumberPiece number;
private readonly GlowPiece glow;
public DrawableHitCircle(OsuHitObject h) : base(h)
public DrawableHitCircle(HitCircle h) : base(h)
{
Origin = Anchor.Centre;
@ -48,7 +47,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
},
number = new NumberPiece
{
Text = h is Spinner ? "S" : (HitObject.ComboIndex + 1).ToString(),
Text = (HitObject.ComboIndex + 1).ToString(),
},
ring = new RingPiece(),
flash = new FlashPiece(),
@ -88,25 +87,27 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
{
base.UpdatePreemptState();
ApproachCircle.FadeIn(Math.Min(TIME_FADEIN * 2, TIME_PREEMPT));
ApproachCircle.FadeIn(Math.Min(FadeInDuration * 2, TIME_PREEMPT));
ApproachCircle.ScaleTo(1.1f, TIME_PREEMPT);
}
protected override void UpdateCurrentState(ArmedState state)
{
double duration = ((HitObject as IHasEndTime)?.EndTime ?? HitObject.StartTime) - HitObject.StartTime;
glow.Delay(duration).FadeOut(400);
glow.FadeOut(400);
switch (state)
{
case ArmedState.Idle:
this.Delay(duration + TIME_PREEMPT).FadeOut(TIME_FADEOUT);
this.Delay(TIME_PREEMPT).FadeOut(500);
Expire(true);
// override lifetime end as FadeIn may have been changed externally, causing out expiration to be too early.
LifetimeEnd = HitObject.StartTime + HitObject.HitWindowFor(HitResult.Miss);
break;
case ArmedState.Miss:
ApproachCircle.FadeOut(50);
this.FadeOut(TIME_FADEOUT / 5);
this.FadeOut(100);
Expire();
break;
case ArmedState.Hit:

View File

@ -12,7 +12,13 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
{
public const float TIME_PREEMPT = 600;
public const float TIME_FADEIN = 400;
public const float TIME_FADEOUT = 500;
/// <summary>
/// The number of milliseconds used to fade in.
/// </summary>
public virtual double FadeInDuration { get; set; } = TIME_FADEIN;
public override bool IsPresent => base.IsPresent || State.Value == ArmedState.Idle && Time.Current >= HitObject.StartTime - TIME_PREEMPT;
protected DrawableOsuHitObject(OsuHitObject hitObject)
: base(hitObject)
@ -37,10 +43,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
}
}
protected virtual void UpdatePreemptState()
{
this.FadeIn(TIME_FADEIN);
}
protected virtual void UpdatePreemptState() => this.FadeIn(FadeInDuration);
protected virtual void UpdateCurrentState(ArmedState state)
{

View File

@ -9,6 +9,7 @@ using System.Collections.Generic;
using System.Linq;
using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Osu.Judgements;
using osu.Framework.Graphics.Primitives;
namespace osu.Game.Rulesets.Osu.Objects.Drawables
{
@ -16,23 +17,24 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
{
private readonly Slider slider;
private readonly DrawableHitCircle initialCircle;
public readonly DrawableHitCircle InitialCircle;
private readonly List<ISliderProgress> components = new List<ISliderProgress>();
private readonly Container<DrawableSliderTick> ticks;
private readonly Container<DrawableRepeatPoint> repeatPoints;
private readonly SliderBody body;
private readonly SliderBall ball;
public readonly SliderBody Body;
public readonly SliderBall Ball;
public DrawableSlider(Slider s) : base(s)
public DrawableSlider(Slider s)
: base(s)
{
slider = s;
Children = new Drawable[]
{
body = new SliderBody(s)
Body = new SliderBody(s)
{
AccentColour = AccentColour,
Position = s.StackedPosition,
@ -40,35 +42,35 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
},
ticks = new Container<DrawableSliderTick>(),
repeatPoints = new Container<DrawableRepeatPoint>(),
ball = new SliderBall(s)
Ball = new SliderBall(s)
{
Scale = new Vector2(s.Scale),
AccentColour = AccentColour,
AlwaysPresent = true,
Alpha = 0
},
initialCircle = new DrawableHitCircle(new HitCircle
InitialCircle = new DrawableHitCircle(new HitCircle
{
//todo: avoid creating this temporary HitCircle.
StartTime = s.StartTime,
Position = s.StackedPosition,
ComboIndex = s.ComboIndex,
Scale = s.Scale,
ComboColour = s.ComboColour,
Samples = s.Samples,
SampleControlPoint = s.SampleControlPoint
})
};
components.Add(body);
components.Add(ball);
components.Add(Body);
components.Add(Ball);
AddNested(initialCircle);
AddNested(InitialCircle);
var repeatDuration = s.Curve.Distance / s.Velocity;
foreach (var tick in s.Ticks)
foreach (var tick in s.NestedHitObjects.OfType<SliderTick>())
{
var repeatStartTime = s.StartTime + tick.RepeatIndex * repeatDuration;
var fadeInTime = repeatStartTime + (tick.StartTime - repeatStartTime) / 2 - (tick.RepeatIndex == 0 ? TIME_FADEIN : TIME_FADEIN / 2);
var fadeInTime = repeatStartTime + (tick.StartTime - repeatStartTime) / 2 - (tick.RepeatIndex == 0 ? FadeInDuration : FadeInDuration / 2);
var fadeOutTime = repeatStartTime + repeatDuration;
var drawableTick = new DrawableSliderTick(tick)
@ -82,10 +84,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
AddNested(drawableTick);
}
foreach (var repeatPoint in s.RepeatPoints)
foreach (var repeatPoint in s.NestedHitObjects.OfType<RepeatPoint>())
{
var repeatStartTime = s.StartTime + repeatPoint.RepeatIndex * repeatDuration;
var fadeInTime = repeatStartTime + (repeatPoint.StartTime - repeatStartTime) / 2 - (repeatPoint.RepeatIndex == 0 ? TIME_FADEIN : TIME_FADEIN / 2);
var fadeInTime = repeatStartTime + (repeatPoint.StartTime - repeatStartTime) / 2 - (repeatPoint.RepeatIndex == 0 ? FadeInDuration : FadeInDuration / 2);
var fadeOutTime = repeatStartTime + repeatDuration;
var drawableRepeatPoint = new DrawableRepeatPoint(repeatPoint, this)
@ -103,11 +105,17 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
private int currentRepeat;
public bool Tracking;
public override double FadeInDuration
{
get { return base.FadeInDuration; }
set { InitialCircle.FadeInDuration = base.FadeInDuration = value; }
}
protected override void Update()
{
base.Update();
Tracking = ball.Tracking;
Tracking = Ball.Tracking;
double progress = MathHelper.Clamp((Time.Current - slider.StartTime) / slider.Duration, 0, 1);
@ -115,18 +123,14 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
progress = slider.ProgressAt(progress);
if (repeat > currentRepeat)
{
if (repeat < slider.RepeatCount && ball.Tracking)
PlaySamples();
currentRepeat = repeat;
}
//todo: we probably want to reconsider this before adding scoring, but it looks and feels nice.
if (!initialCircle.Judgements.Any(j => j.IsHit))
initialCircle.Position = slider.Curve.PositionAt(progress);
if (!InitialCircle.Judgements.Any(j => j.IsHit))
InitialCircle.Position = slider.Curve.PositionAt(progress);
foreach (var c in components) c.UpdateProgress(progress, repeat);
foreach (var t in ticks.Children) t.Tracking = ball.Tracking;
foreach (var t in ticks.Children) t.Tracking = Ball.Tracking;
}
protected override void CheckForJudgements(bool userTriggered, double timeOffset)
@ -135,13 +139,13 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
{
var judgementsCount = ticks.Children.Count + repeatPoints.Children.Count + 1;
var judgementsHit = ticks.Children.Count(t => t.Judgements.Any(j => j.IsHit)) + repeatPoints.Children.Count(t => t.Judgements.Any(j => j.IsHit));
if (initialCircle.Judgements.Any(j => j.IsHit))
if (InitialCircle.Judgements.Any(j => j.IsHit))
judgementsHit++;
var hitFraction = (double)judgementsHit / judgementsCount;
if (hitFraction == 1 && initialCircle.Judgements.Any(j => j.Result == HitResult.Great))
if (hitFraction == 1 && InitialCircle.Judgements.Any(j => j.Result == HitResult.Great))
AddJudgement(new OsuJudgement { Result = HitResult.Great });
else if (hitFraction >= 0.5 && initialCircle.Judgements.Any(j => j.Result >= HitResult.Good))
else if (hitFraction >= 0.5 && InitialCircle.Judgements.Any(j => j.Result >= HitResult.Good))
AddJudgement(new OsuJudgement { Result = HitResult.Good });
else if (hitFraction > 0)
AddJudgement(new OsuJudgement { Result = HitResult.Meh });
@ -152,23 +156,21 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
protected override void UpdateCurrentState(ArmedState state)
{
ball.FadeIn();
Ball.FadeIn();
using (BeginDelayedSequence(slider.Duration, true))
{
body.FadeOut(160);
ball.FadeOut(160);
Body.FadeOut(160);
Ball.FadeOut(160);
this.FadeOut(800)
.Expire();
}
}
public Drawable ProxiedLayer => initialCircle.ApproachCircle;
}
public Drawable ProxiedLayer => InitialCircle.ApproachCircle;
internal interface ISliderProgress
{
void UpdateProgress(double progress, int repeat);
public override Vector2 SelectionPoint => ToScreenSpace(Body.Position);
public override Quad SelectionQuad => Body.PathDrawQuad;
}
}

View File

@ -20,13 +20,13 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
{
private readonly Spinner spinner;
private readonly SpinnerDisc disc;
private readonly SpinnerTicks ticks;
public readonly SpinnerDisc Disc;
public readonly SpinnerTicks Ticks;
private readonly SpinnerSpmCounter spmCounter;
private readonly Container mainContainer;
private readonly SpinnerBackground background;
public readonly SpinnerBackground Background;
private readonly Container circleContainer;
private readonly CirclePiece circle;
private readonly GlowPiece glow;
@ -84,20 +84,20 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
RelativeSizeAxes = Axes.Y,
Children = new Drawable[]
{
background = new SpinnerBackground
Background = new SpinnerBackground
{
Alpha = 0.6f,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
},
disc = new SpinnerDisc(spinner)
Disc = new SpinnerDisc(spinner)
{
Scale = Vector2.Zero,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
},
circleContainer.CreateProxy(),
ticks = new SpinnerTicks
Ticks = new SpinnerTicks
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
@ -114,22 +114,22 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
};
}
public float Progress => MathHelper.Clamp(disc.RotationAbsolute / 360 / spinner.SpinsRequired, 0, 1);
public float Progress => MathHelper.Clamp(Disc.RotationAbsolute / 360 / spinner.SpinsRequired, 0, 1);
protected override void CheckForJudgements(bool userTriggered, double timeOffset)
{
if (Time.Current < HitObject.StartTime) return;
if (Progress >= 1 && !disc.Complete)
if (Progress >= 1 && !Disc.Complete)
{
disc.Complete = true;
Disc.Complete = true;
const float duration = 200;
disc.FadeAccent(completeColour, duration);
Disc.FadeAccent(completeColour, duration);
background.FadeAccent(completeColour, duration);
background.FadeOut(duration);
Background.FadeAccent(completeColour, duration);
Background.FadeOut(duration);
circle.FadeColour(completeColour, duration);
glow.FadeColour(completeColour, duration);
@ -153,20 +153,20 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
{
normalColour = baseColour;
background.AccentColour = normalColour;
Background.AccentColour = normalColour;
completeColour = colours.YellowLight.Opacity(0.75f);
disc.AccentColour = fillColour;
Disc.AccentColour = fillColour;
circle.Colour = colours.BlueDark;
glow.Colour = colours.BlueDark;
}
protected override void Update()
{
disc.Tracking = OsuActionInputManager.PressedActions.Any(x => x == OsuAction.LeftButton || x == OsuAction.RightButton);
if (!spmCounter.IsPresent && disc.Tracking)
spmCounter.FadeIn(TIME_FADEIN);
Disc.Tracking = OsuActionInputManager.PressedActions.Any(x => x == OsuAction.LeftButton || x == OsuAction.RightButton);
if (!spmCounter.IsPresent && Disc.Tracking)
spmCounter.FadeIn(FadeInDuration);
base.Update();
}
@ -175,14 +175,14 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
{
base.UpdateAfterChildren();
circle.Rotation = disc.Rotation;
ticks.Rotation = disc.Rotation;
spmCounter.SetRotation(disc.RotationAbsolute);
circle.Rotation = Disc.Rotation;
Ticks.Rotation = Disc.Rotation;
spmCounter.SetRotation(Disc.RotationAbsolute);
float relativeCircleScale = spinner.Scale * circle.DrawHeight / mainContainer.DrawHeight;
disc.ScaleTo(relativeCircleScale + (1 - relativeCircleScale) * Progress, 200, Easing.OutQuint);
Disc.ScaleTo(relativeCircleScale + (1 - relativeCircleScale) * Progress, 200, Easing.OutQuint);
symbol.RotateTo(disc.Rotation / 2, 500, Easing.OutQuint);
symbol.RotateTo(Disc.Rotation / 2, 500, Easing.OutQuint);
}
protected override void UpdatePreemptState()
@ -192,7 +192,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
circleContainer.ScaleTo(spinner.Scale * 0.3f);
circleContainer.ScaleTo(spinner.Scale, TIME_PREEMPT / 1.4f, Easing.OutQuint);
disc.RotateTo(-720);
Disc.RotateTo(-720);
symbol.RotateTo(-720);
mainContainer

View File

@ -14,6 +14,7 @@ using osu.Game.Configuration;
using OpenTK;
using OpenTK.Graphics.ES30;
using OpenTK.Graphics;
using osu.Framework.Graphics.Primitives;
namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
{
@ -49,6 +50,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
}
}
public Quad PathDrawQuad => container.ScreenSpaceDrawQuad;
private int textureWidth => (int)PathWidth * 2;
private readonly Slider slider;
@ -182,4 +185,4 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
SetRange(start, end);
}
}
}
}

View File

@ -26,6 +26,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
private const float idle_alpha = 0.2f;
private const float tracking_alpha = 0.4f;
public override bool IsPresent => true; // handle input when hidden
public SpinnerDisc(Spinner s)
{
spinner = s;

View 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.Osu.Objects
{
public interface ISliderProgress
{
void UpdateProgress(double progress, int repeat);
}
}

View File

@ -68,9 +68,9 @@ namespace osu.Game.Rulesets.Osu.Objects
return HitResult.Miss;
}
public override void ApplyDefaults(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty)
protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty)
{
base.ApplyDefaults(controlPointInfo, difficulty);
base.ApplyDefaultsToSelf(controlPointInfo, difficulty);
Scale = (1.0f - 0.7f * (difficulty.CircleSize - 5) / 5) / 2;
}

View File

@ -57,7 +57,7 @@ namespace osu.Game.Rulesets.Osu.Objects
/// </summary>
internal float LazyTravelDistance;
public List<SampleInfoList> RepeatSamples { get; set; } = new List<SampleInfoList>();
public List<List<SampleInfo>> RepeatSamples { get; set; } = new List<List<SampleInfo>>();
public int RepeatCount { get; set; } = 1;
private int stackHeight;
@ -74,9 +74,9 @@ namespace osu.Game.Rulesets.Osu.Objects
public double Velocity;
public double TickDistance;
public override void ApplyDefaults(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty)
protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty)
{
base.ApplyDefaults(controlPointInfo, difficulty);
base.ApplyDefaultsToSelf(controlPointInfo, difficulty);
TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(StartTime);
DifficultyControlPoint difficultyPoint = controlPointInfo.DifficultyPointAt(StartTime);
@ -99,76 +99,74 @@ namespace osu.Game.Rulesets.Osu.Objects
public int RepeatAt(double progress) => (int)(progress * RepeatCount);
public IEnumerable<SliderTick> Ticks
protected override void CreateNestedHitObjects()
{
get
base.CreateNestedHitObjects();
createTicks();
createRepeatPoints();
}
private void createTicks()
{
if (TickDistance == 0) return;
var length = Curve.Distance;
var tickDistance = Math.Min(TickDistance, length);
var repeatDuration = length / Velocity;
var minDistanceFromEnd = Velocity * 0.01;
for (var repeat = 0; repeat < RepeatCount; repeat++)
{
if (TickDistance == 0) yield break;
var repeatStartTime = StartTime + repeat * repeatDuration;
var reversed = repeat % 2 == 1;
var length = Curve.Distance;
var tickDistance = Math.Min(TickDistance, length);
var repeatDuration = length / Velocity;
var minDistanceFromEnd = Velocity * 0.01;
for (var repeat = 0; repeat < RepeatCount; repeat++)
for (var d = tickDistance; d <= length; d += tickDistance)
{
var repeatStartTime = StartTime + repeat * repeatDuration;
var reversed = repeat % 2 == 1;
if (d > length - minDistanceFromEnd)
break;
for (var d = tickDistance; d <= length; d += tickDistance)
var distanceProgress = d / length;
var timeProgress = reversed ? 1 - distanceProgress : distanceProgress;
AddNested(new SliderTick
{
if (d > length - minDistanceFromEnd)
break;
var distanceProgress = d / length;
var timeProgress = reversed ? 1 - distanceProgress : distanceProgress;
yield return new SliderTick
RepeatIndex = repeat,
StartTime = repeatStartTime + timeProgress * repeatDuration,
Position = Curve.PositionAt(distanceProgress),
StackHeight = StackHeight,
Scale = Scale,
ComboColour = ComboColour,
Samples = new List<SampleInfo>(Samples.Select(s => new SampleInfo
{
RepeatIndex = repeat,
StartTime = repeatStartTime + timeProgress * repeatDuration,
Position = Curve.PositionAt(distanceProgress),
StackHeight = StackHeight,
Scale = Scale,
ComboColour = ComboColour,
Samples = new SampleInfoList(Samples.Select(s => new SampleInfo
{
Bank = s.Bank,
Name = @"slidertick",
Volume = s.Volume
}))
};
}
Bank = s.Bank,
Name = @"slidertick",
Volume = s.Volume
}))
});
}
}
}
public IEnumerable<RepeatPoint> RepeatPoints
private void createRepeatPoints()
{
get
var repeatDuration = Distance / Velocity;
for (var repeat = 1; repeat < RepeatCount; repeat++)
{
var length = Curve.Distance;
var repeatPointDistance = Math.Min(Distance, length);
var repeatDuration = length / Velocity;
var repeatStartTime = StartTime + repeat * repeatDuration;
for (var repeat = 1; repeat < RepeatCount; repeat++)
AddNested(new RepeatPoint
{
for (var d = repeatPointDistance; d <= length; d += repeatPointDistance)
{
var repeatStartTime = StartTime + repeat * repeatDuration;
var distanceProgress = d / length;
yield return new RepeatPoint
{
RepeatIndex = repeat,
StartTime = repeatStartTime,
Position = Curve.PositionAt(distanceProgress),
StackHeight = StackHeight,
Scale = Scale,
ComboColour = ComboColour,
};
}
}
RepeatIndex = repeat,
StartTime = repeatStartTime,
Position = Curve.PositionAt(repeat % 2),
StackHeight = StackHeight,
Scale = Scale,
ComboColour = ComboColour,
Samples = new List<SampleInfo>(RepeatSamples[repeat])
});
}
}
}

View File

@ -19,9 +19,9 @@ namespace osu.Game.Rulesets.Osu.Objects
public override bool NewCombo => true;
public override void ApplyDefaults(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty)
protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty)
{
base.ApplyDefaults(controlPointInfo, difficulty);
base.ApplyDefaultsToSelf(controlPointInfo, difficulty);
SpinsRequired = (int)(Duration / 1000 * BeatmapDifficulty.DifficultyRange(difficulty.OverallDifficulty, 3, 5, 7.5));

View File

@ -93,6 +93,7 @@ namespace osu.Game.Rulesets.Osu.OsuDifficulty.Preprocessing
float approxFollowCircleRadius = (float)(slider.Radius * 3);
var computeVertex = new Action<double>(t =>
{
// ReSharper disable once PossibleInvalidOperationException (bugged in current r# version)
var diff = slider.PositionAt(t) - slider.LazyEndPosition.Value;
float dist = diff.Length;
@ -106,7 +107,7 @@ namespace osu.Game.Rulesets.Osu.OsuDifficulty.Preprocessing
}
});
var scoringTimes = slider.Ticks.Select(t => t.StartTime).Concat(slider.RepeatPoints.Select(r => r.StartTime)).OrderBy(t => t);
var scoringTimes = slider.NestedHitObjects.Select(t => t.StartTime);
foreach (var time in scoringTimes)
computeVertex(time);
computeVertex(slider.EndTime);

View File

@ -5,7 +5,6 @@ using osu.Game.Beatmaps;
using osu.Game.Graphics;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.OsuDifficulty;
using osu.Game.Rulesets.Osu.UI;
using osu.Game.Rulesets.UI;
@ -18,6 +17,8 @@ using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.Osu.Scoring;
using osu.Game.Rulesets.Osu.Edit;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Objects;
namespace osu.Game.Rulesets.Osu
{
@ -33,21 +34,35 @@ namespace osu.Game.Rulesets.Osu
new KeyBinding(InputKey.MouseRight, OsuAction.RightButton),
};
public override IEnumerable<BeatmapStatistic> GetBeatmapStatistics(WorkingBeatmap beatmap) => new[]
public override IEnumerable<BeatmapStatistic> GetBeatmapStatistics(WorkingBeatmap beatmap)
{
new BeatmapStatistic
IEnumerable<HitObject> hitObjects = beatmap.Beatmap.HitObjects;
IEnumerable<HitObject> circles = hitObjects.Where(c => !(c is IHasEndTime));
IEnumerable<HitObject> sliders = hitObjects.Where(s => s is IHasCurve);
IEnumerable<HitObject> spinners = hitObjects.Where(s => s is IHasEndTime && !(s is IHasCurve));
return new[]
{
Name = @"Circle count",
Content = beatmap.Beatmap.HitObjects.Count(h => h is HitCircle).ToString(),
Icon = FontAwesome.fa_dot_circle_o
},
new BeatmapStatistic
{
Name = @"Slider count",
Content = beatmap.Beatmap.HitObjects.Count(h => h is Slider).ToString(),
Icon = FontAwesome.fa_circle_o
}
};
new BeatmapStatistic
{
Name = @"Circle Count",
Content = circles.Count().ToString(),
Icon = FontAwesome.fa_circle_o
},
new BeatmapStatistic
{
Name = @"Slider Count",
Content = sliders.Count().ToString(),
Icon = FontAwesome.fa_circle
},
new BeatmapStatistic
{
Name = @"Spinner Count",
Content = spinners.Count().ToString(),
Icon = FontAwesome.fa_circle
}
};
}
public override IEnumerable<Mod> GetModsFor(ModType type)
{
@ -124,6 +139,8 @@ namespace osu.Game.Rulesets.Osu
public override string Description => "osu!";
public override string ShortName => "osu";
public override SettingsSubsection CreateSettings() => new OsuSettings();
public override int LegacyID => 0;

View File

@ -33,7 +33,7 @@ namespace osu.Game.Rulesets.Osu.Scoring
countHitCircles = Beatmap.HitObjects.Count(h => h is HitCircle);
beatmapMaxCombo = Beatmap.HitObjects.Count;
beatmapMaxCombo += Beatmap.HitObjects.OfType<Slider>().Sum(s => s.RepeatCount + s.Ticks.Count());
beatmapMaxCombo += Beatmap.HitObjects.OfType<Slider>().Sum(s => s.NestedHitObjects.Count) + 1;
}
public override double Calculate(Dictionary<string, double> categoryRatings = null)

View File

@ -2,6 +2,7 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Extensions;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Judgements;
@ -39,11 +40,11 @@ namespace osu.Game.Rulesets.Osu.Scoring
AddJudgement(new OsuJudgement { Result = HitResult.Great });
// Ticks
foreach (var unused in slider.Ticks)
foreach (var unused in slider.NestedHitObjects.OfType<SliderTick>())
AddJudgement(new OsuJudgement { Result = HitResult.Great });
//Repeats
foreach (var unused in slider.RepeatPoints)
foreach (var unused in slider.NestedHitObjects.OfType<RepeatPoint>())
AddJudgement(new OsuJudgement { Result = HitResult.Great });
}

View File

@ -0,0 +1,116 @@
// 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.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables;
using osu.Game.Tests.Visual;
using OpenTK;
using osu.Game.Rulesets.Osu.Mods;
using OpenTK.Graphics;
using osu.Game.Rulesets.Osu.Judgements;
using System.Collections.Generic;
using System;
namespace osu.Game.Rulesets.Osu.Tests
{
[Ignore("getting CI working")]
public class TestCaseHitCircle : OsuTestCase
{
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(HitCircle),
typeof(OsuModHidden),
typeof(DrawableHitCircle)
};
private readonly Container content;
protected override Container<Drawable> Content => content;
private bool auto;
private bool hidden;
private int depthIndex;
private int circleSize;
private float circleScale = 1;
public TestCaseHitCircle()
{
base.Content.Add(content = new OsuInputManager(new RulesetInfo { ID = 0 }));
AddStep("Single", () => testSingle());
AddStep("Stream", testStream);
AddToggleStep("Auto", v => auto = v);
AddToggleStep("Hidden", v => hidden = v);
AddSliderStep("CircleSize", 0, 10, 0, s => circleSize = s);
AddSliderStep("CircleScale", 0.5f, 2, 1, s => circleScale = s);
}
private void testSingle(double timeOffset = 0, Vector2? positionOffset = null)
{
positionOffset = positionOffset ?? Vector2.Zero;
var circle = new HitCircle
{
StartTime = Time.Current + 1000 + timeOffset,
Position = positionOffset.Value,
ComboColour = Color4.LightSeaGreen
};
circle.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty { CircleSize = circleSize });
var drawable = new TestDrawableHitCircle(circle, auto)
{
Anchor = Anchor.Centre,
Scale = new Vector2(circleScale),
Depth = depthIndex++
};
if (auto)
drawable.State.Value = ArmedState.Hit;
if (hidden)
new OsuModHidden().ApplyToDrawableHitObjects(new [] { drawable });
Add(drawable);
}
private void testStream()
{
Vector2 pos = Vector2.Zero;
for (int i = 0; i <= 1000; i += 100)
{
testSingle(i, pos);
pos += new Vector2(10);
}
}
private class TestDrawableHitCircle : DrawableHitCircle
{
private readonly bool auto;
public TestDrawableHitCircle(HitCircle h, bool auto) : base(h)
{
this.auto = auto;
}
protected override void CheckForJudgements(bool userTriggered, double timeOffset)
{
if (auto && !userTriggered && timeOffset > 0)
{
// pretend we really hit it
AddJudgement(new OsuJudgement
{
Result = HitObject.ScoreResultForOffset(timeOffset)
});
}
base.CheckForJudgements(userTriggered, timeOffset);
}
}
}
}

View File

@ -1,129 +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.Collections.Generic;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Timing;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables;
using osu.Game.Tests.Visual;
using OpenTK;
namespace osu.Game.Rulesets.Osu.Tests
{
[TestFixture]
[Ignore("getting CI working")]
internal class TestCaseHitObjects : OsuTestCase
{
private FramedClock framedClock;
private bool auto;
[BackgroundDependencyLoader]
private void load(RulesetStore rulesets)
{
var rateAdjustClock = new StopwatchClock(true);
framedClock = new FramedClock(rateAdjustClock);
AddStep(@"circles", () => loadHitobjects(HitObjectType.Circle));
AddStep(@"slider", () => loadHitobjects(HitObjectType.Slider));
AddStep(@"spinner", () => loadHitobjects(HitObjectType.Spinner));
AddToggleStep("Auto", state => { auto = state; loadHitobjects(mode); });
AddSliderStep("Playback speed", 0.0, 2.0, 0.5, v => rateAdjustClock.Rate = v);
framedClock.ProcessFrame();
var clockAdjustContainer = new Container
{
RelativeSizeAxes = Axes.Both,
Clock = framedClock,
Children = new[]
{
playfieldContainer = new OsuInputManager(rulesets.GetRuleset(0)) { RelativeSizeAxes = Axes.Both },
approachContainer = new Container { RelativeSizeAxes = Axes.Both }
}
};
Add(clockAdjustContainer);
}
private HitObjectType mode = HitObjectType.Slider;
private Container playfieldContainer;
private Container approachContainer;
private void loadHitobjects(HitObjectType mode)
{
this.mode = mode;
switch (mode)
{
case HitObjectType.Circle:
const int count = 10;
for (int i = 0; i < count; i++)
{
var h = new HitCircle
{
StartTime = framedClock.CurrentTime + 600 + i * 80,
Position = new Vector2((i - count / 2) * 14),
};
add(new DrawableHitCircle(h));
}
break;
case HitObjectType.Slider:
add(new DrawableSlider(new Slider
{
StartTime = framedClock.CurrentTime + 600,
ControlPoints = new List<Vector2>
{
new Vector2(-200, 0),
new Vector2(400, 0),
},
Distance = 400,
Position = new Vector2(-200, 0),
Velocity = 1,
TickDistance = 100,
}));
break;
case HitObjectType.Spinner:
add(new DrawableSpinner(new Spinner
{
StartTime = framedClock.CurrentTime + 600,
EndTime = framedClock.CurrentTime + 1600,
Position = new Vector2(0, 0),
}));
break;
}
}
private int depth;
private void add(DrawableOsuHitObject h)
{
h.Anchor = Anchor.Centre;
h.Depth = depth++;
if (auto)
h.State.Value = ArmedState.Hit;
playfieldContainer.Add(h);
var proxyable = h as IDrawableHitObjectWithProxiedApproach;
if (proxyable != null)
approachContainer.Add(proxyable.ProxiedLayer.CreateProxy());
}
private enum HitObjectType
{
Circle,
Slider,
Spinner
}
}
}

View File

@ -0,0 +1,146 @@
// 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.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Audio;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables;
using osu.Game.Tests.Visual;
using OpenTK;
using osu.Game.Rulesets.Osu.Mods;
using OpenTK.Graphics;
namespace osu.Game.Rulesets.Osu.Tests
{
[Ignore("getting CI working")]
public class TestCaseSlider : OsuTestCase
{
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(Slider),
typeof(HitCircle),
typeof(OsuModHidden),
typeof(DrawableSlider),
typeof(DrawableHitCircle),
typeof(DrawableSliderTick),
typeof(DrawableRepeatPoint)
};
private readonly Container content;
protected override Container<Drawable> Content => content;
private bool hidden;
private int repeats;
private int depthIndex;
private int circleSize;
private float circleScale = 1;
private double speedMultiplier = 2;
private double sliderMultiplier = 2;
public TestCaseSlider()
{
base.Content.Add(content = new OsuInputManager(new RulesetInfo { ID = 0 }));
AddStep("Single", () => testSingle());
AddStep("Stream", testStream);
AddStep("Repeated", () => testRepeated(repeats));
AddToggleStep("Hidden", v => hidden = v);
AddSliderStep("Repeats", 1, 10, 1, s => repeats = s);
AddSliderStep("CircleSize", 0, 10, 0, s => circleSize = s);
AddSliderStep("CircleScale", 0.5f, 2, 1, s => circleScale = s);
AddSliderStep("SpeedMultiplier", 0.1, 10, 2, s => speedMultiplier = s);
AddSliderStep("SliderMultiplier", 0.1, 10, 2, s => sliderMultiplier = s);
}
private void testSingle(double timeOffset = 0, Vector2? positionOffset = null)
{
positionOffset = positionOffset ?? Vector2.Zero;
var slider = new Slider
{
StartTime = Time.Current + 1000 + timeOffset,
Position = new Vector2(-200, 0) + positionOffset.Value,
ComboColour = Color4.LightSeaGreen,
ControlPoints = new List<Vector2>
{
new Vector2(-200, 0) + positionOffset.Value,
new Vector2(400, 0) + positionOffset.Value,
},
Distance = 400
};
addSlider(slider);
}
private void testRepeated(int repeats)
{
// The first run through the slider is considered a repeat
repeats++;
var repeatSamples = new List<List<SampleInfo>>();
for (int i = 0; i < repeats; i++)
repeatSamples.Add(new List<SampleInfo>());
var slider = new Slider
{
StartTime = Time.Current + 1000,
Position = new Vector2(-200, 0),
ComboColour = Color4.LightSeaGreen,
ControlPoints = new List<Vector2>
{
new Vector2(-200, 0),
new Vector2(400, 0),
},
Distance = 400,
RepeatCount = repeats,
RepeatSamples = repeatSamples
};
addSlider(slider);
}
private void testStream()
{
Vector2 pos = Vector2.Zero;
for (int i = 0; i <= 1000; i += 100)
{
testSingle(i, pos);
pos += new Vector2(10);
}
}
private void addSlider(Slider slider)
{
var cpi = new ControlPointInfo();
cpi.DifficultyPoints.Add(new DifficultyControlPoint { SpeedMultiplier = speedMultiplier });
var difficulty = new BeatmapDifficulty
{
SliderMultiplier = (float)sliderMultiplier,
CircleSize = circleSize
};
slider.ApplyDefaults(cpi, difficulty);
var drawable = new DrawableSlider(slider)
{
Anchor = Anchor.Centre,
Scale = new Vector2(circleScale),
Depth = depthIndex++
};
if (hidden)
new OsuModHidden().ApplyToDrawableHitObjects(new [] { drawable });
Add(drawable);
}
}
}

View File

@ -0,0 +1,66 @@
// 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 OpenTK;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables;
using osu.Game.Tests.Visual;
namespace osu.Game.Rulesets.Osu.Tests
{
[Ignore("getting CI working")]
public class TestCaseSpinner : OsuTestCase
{
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(Spinner),
typeof(OsuModHidden),
typeof(DrawableSpinner)
};
private readonly Container content;
protected override Container<Drawable> Content => content;
private bool hidden;
private int depthIndex;
private int circleSize;
private float circleScale = 1;
public TestCaseSpinner()
{
base.Content.Add(content = new OsuInputManager(new RulesetInfo { ID = 0 }));
AddStep("Single", testSingle);
AddToggleStep("Hidden", v => hidden = v);
AddSliderStep("CircleSize", 0, 10, 0, s => circleSize = s);
AddSliderStep("CircleScale", 0.5f, 2, 1, s => circleScale = s);
}
private void testSingle()
{
var spinner = new Spinner { StartTime = Time.Current + 1000, EndTime = Time.Current + 4000 };
spinner.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty { CircleSize = circleSize });
var drawable = new DrawableSpinner(spinner)
{
Anchor = Anchor.Centre,
Scale = new Vector2(circleScale),
Depth = depthIndex++
};
if (hidden)
new OsuModHidden().ApplyToDrawableHitObjects(new [] { drawable });
Add(drawable);
}
}
}

View File

@ -20,8 +20,6 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
{
internal class CursorTrail : Drawable
{
public override bool HandleInput => true;
private int currentIndex;
private Shader shader;

View File

@ -25,12 +25,19 @@ namespace osu.Game.Rulesets.Osu.UI
public override bool ProvidingUserCursor => true;
// Todo: This should not be a thing, but is currently required for the editor
// https://github.com/ppy/osu-framework/issues/1283
protected virtual bool ProxyApproachCircles => true;
public static readonly Vector2 BASE_SIZE = new Vector2(512, 384);
public override Vector2 Size
{
get
{
if (Parent == null)
return Vector2.Zero;
var parentSize = Parent.DrawSize;
var aspectSize = parentSize.X * 0.75f < parentSize.Y ? new Vector2(parentSize.X, parentSize.X * 0.75f) : new Vector2(parentSize.Y * 4f / 3f, parentSize.Y);
@ -77,7 +84,7 @@ namespace osu.Game.Rulesets.Osu.UI
h.Depth = (float)h.HitObject.StartTime;
var c = h as IDrawableHitObjectWithProxiedApproach;
if (c != null)
if (c != null && ProxyApproachCircles)
approachCircles.Add(c.ProxiedLayer.CreateProxy());
base.Add(h);

View File

@ -35,16 +35,13 @@ namespace osu.Game.Rulesets.Osu.UI
protected override DrawableHitObject<OsuHitObject> GetVisualRepresentation(OsuHitObject h)
{
var circle = h as HitCircle;
if (circle != null)
if (h is HitCircle circle)
return new DrawableHitCircle(circle);
var slider = h as Slider;
if (slider != null)
if (h is Slider slider)
return new DrawableSlider(slider);
var spinner = h as Spinner;
if (spinner != null)
if (h is Spinner spinner)
return new DrawableSpinner(spinner);
return null;
}

View File

@ -37,8 +37,8 @@
<HintPath>$(SolutionDir)\packages\NUnit.3.8.1\lib\net45\nunit.framework.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="OpenTK, Version=3.0.0.0, Culture=neutral, PublicKeyToken=bad199fe84eb3df4, processorArchitecture=MSIL">
<HintPath>$(SolutionDir)\packages\OpenTK.3.0.0-git00009\lib\net20\OpenTK.dll</HintPath>
<Reference Include="OpenTK, Version=3.0.0.0, Culture=neutral, PublicKeyToken=bad199fe84eb3df4">
<HintPath>$(SolutionDir)\packages\ppy.OpenTK.3.0.11\lib\net45\OpenTK.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System" />
@ -75,6 +75,7 @@
<Compile Include="Objects\Drawables\Pieces\TrianglesPiece.cs" />
<Compile Include="Objects\Drawables\Pieces\SliderBall.cs" />
<Compile Include="Objects\Drawables\Pieces\SliderBody.cs" />
<Compile Include="Objects\ISliderProgress.cs" />
<Compile Include="Objects\RepeatPoint.cs" />
<Compile Include="Objects\SliderTick.cs" />
<Compile Include="OsuDifficulty\OsuDifficultyCalculator.cs" />
@ -86,8 +87,10 @@
<Compile Include="OsuDifficulty\Utils\History.cs" />
<Compile Include="OsuInputManager.cs" />
<Compile Include="Replays\OsuReplayInputHandler.cs" />
<Compile Include="Tests\TestCaseHitObjects.cs" />
<Compile Include="Tests\TestCaseHitCircle.cs" />
<Compile Include="Tests\TestCasePerformancePoints.cs" />
<Compile Include="Tests\TestCaseSlider.cs" />
<Compile Include="Tests\TestCaseSpinner.cs" />
<Compile Include="UI\Cursor\CursorTrail.cs" />
<Compile Include="UI\Cursor\GameplayCursor.cs" />
<Compile Include="UI\OsuSettings.cs" />
@ -123,7 +126,9 @@
<Private>True</Private>
</ProjectReference>
</ItemGroup>
<ItemGroup />
<ItemGroup>
<Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="NUnit" version="3.8.1" targetFramework="net461" />
<package id="OpenTK" version="3.0.0-git00009" targetFramework="net461" />
<package id="ppy.OpenTK" version="3.0.11" targetFramework="net461" />
</packages>

View 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.Collections.Generic;
using osu.Framework.Audio;
using osu.Framework.Audio.Sample;
using osu.Game.Audio;
using osu.Game.Beatmaps.ControlPoints;
namespace osu.Game.Rulesets.Taiko.Audio
{
public class DrumSampleMapping
{
private readonly ControlPointInfo controlPoints;
private readonly Dictionary<double, DrumSample> mappings = new Dictionary<double, DrumSample>();
public DrumSampleMapping(ControlPointInfo controlPoints, AudioManager audio)
{
this.controlPoints = controlPoints;
IEnumerable<SampleControlPoint> samplePoints;
if (controlPoints.SamplePoints.Count == 0)
// Get the default sample point
samplePoints = new[] { controlPoints.SamplePointAt(double.MinValue) };
else
samplePoints = controlPoints.SamplePoints;
foreach (var s in samplePoints)
{
mappings[s.Time] = new DrumSample
{
Centre = s.GetSampleInfo().GetChannel(audio.Sample, "Taiko"),
Rim = s.GetSampleInfo(SampleInfo.HIT_CLAP).GetChannel(audio.Sample, "Taiko")
};
}
}
public DrumSample SampleAt(double time) => mappings[controlPoints.SamplePointAt(time).Time];
public class DrumSample
{
public SampleChannel Centre;
public SampleChannel Rim;
}
}
}

View File

@ -78,7 +78,7 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
var curveData = obj as IHasCurve;
// Old osu! used hit sounding to determine various hit type information
SampleInfoList samples = obj.Samples;
List<SampleInfo> samples = obj.Samples;
bool strong = samples.Any(s => s.Name == SampleInfo.HIT_FINISH);
@ -115,12 +115,12 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
if (!isForCurrentRuleset && tickSpacing > 0 && osuDuration < 2 * speedAdjustedBeatLength)
{
List<SampleInfoList> allSamples = curveData != null ? curveData.RepeatSamples : new List<SampleInfoList>(new[] { samples });
List<List<SampleInfo>> allSamples = curveData != null ? curveData.RepeatSamples : new List<List<SampleInfo>>(new[] { samples });
int i = 0;
for (double j = obj.StartTime; j <= obj.StartTime + taikoDuration + tickSpacing / 8; j += tickSpacing)
{
SampleInfoList currentSamples = allSamples[i];
List<SampleInfo> currentSamples = allSamples[i];
bool isRim = currentSamples.Any(s => s.Name == SampleInfo.HIT_CLAP || s.Name == SampleInfo.HIT_WHISTLE);
strong = currentSamples.Any(s => s.Name == SampleInfo.HIT_FINISH);

View File

@ -43,7 +43,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
RelativeChildSize = new Vector2((float)HitObject.Duration, 1)
});
foreach (var tick in drumRoll.Ticks)
foreach (var tick in drumRoll.NestedHitObjects.OfType<DrumRollTick>())
{
var newTick = new DrawableDrumRollTick(tick);
newTick.OnJudgement += onTickJudgement;

View File

@ -67,6 +67,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
{
validKeyPressed = HitActions.Contains(action);
// Only count this as handled if the new judgement is a hit
return UpdateJudgement(true);
}

View File

@ -36,7 +36,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
if (!userTriggered)
{
if (timeOffset > second_hit_window)
AddJudgement(new TaikoStrongHitJudgement { Result = HitResult.Miss });
AddJudgement(new TaikoStrongHitJudgement { Result = HitResult.None });
return;
}

View File

@ -34,10 +34,6 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
private readonly CircularContainer targetRing;
private readonly CircularContainer expandingRing;
private readonly TaikoAction[] rimActions = { TaikoAction.LeftRim, TaikoAction.RightRim };
private readonly TaikoAction[] centreActions = { TaikoAction.LeftCentre, TaikoAction.RightCentre };
private TaikoAction[] lastAction;
/// <summary>
/// The amount of times the user has hit this swell.
/// </summary>
@ -205,19 +201,20 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
}
}
private bool? lastWasCentre;
public override bool OnPressed(TaikoAction action)
{
// Don't handle keys before the swell starts
if (Time.Current < HitObject.StartTime)
return false;
// Find the keyset which this key corresponds to
var keySet = rimActions.Contains(action) ? rimActions : centreActions;
var isCentre = action == TaikoAction.LeftCentre || action == TaikoAction.RightCentre;
// Ensure alternating keysets
if (keySet == lastAction)
// Ensure alternating centre and rim hits
if (lastWasCentre == isCentre)
return false;
lastAction = keySet;
lastWasCentre = isCentre;
UpdateJudgement(true);

View File

@ -6,6 +6,9 @@ using osu.Framework.Input.Bindings;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces;
using OpenTK;
using System.Linq;
using osu.Game.Audio;
using System.Collections.Generic;
namespace osu.Game.Rulesets.Taiko.Objects.Drawables
{
@ -35,6 +38,11 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
MainPiece.KiaiMode = HitObject.Kiai;
}
// Normal and clap samples are handled by the drum
protected override IEnumerable<SampleInfo> GetSamples() => HitObject.Samples.Where(s => s.Name != SampleInfo.HIT_NORMAL && s.Name != SampleInfo.HIT_CLAP);
protected override string SampleNamespace => "Taiko";
protected virtual TaikoPiece CreateMainPiece() => new CirclePiece();
public abstract bool OnPressed(TaikoAction action);

View File

@ -3,9 +3,6 @@
using osu.Game.Rulesets.Objects.Types;
using System;
using System.Collections.Generic;
using System.Linq;
using osu.Game.Audio;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
@ -37,64 +34,49 @@ namespace osu.Game.Rulesets.Taiko.Objects
/// </summary>
public double RequiredGreatHits { get; protected set; }
/// <summary>
/// Total number of drum roll ticks.
/// </summary>
public int TotalTicks => Ticks.Count();
/// <summary>
/// Initializes the drum roll ticks if not initialized and returns them.
/// </summary>
public IEnumerable<DrumRollTick> Ticks => ticks ?? (ticks = createTicks());
private List<DrumRollTick> ticks;
/// <summary>
/// The length (in milliseconds) between ticks of this drumroll.
/// <para>Half of this value is the hit window of the ticks.</para>
/// </summary>
private double tickSpacing = 100;
public override void ApplyDefaults(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty)
protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty)
{
base.ApplyDefaults(controlPointInfo, difficulty);
base.ApplyDefaultsToSelf(controlPointInfo, difficulty);
TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(StartTime);
tickSpacing = timingPoint.BeatLength / TickRate;
RequiredGoodHits = TotalTicks * Math.Min(0.15, 0.05 + 0.10 / 6 * difficulty.OverallDifficulty);
RequiredGreatHits = TotalTicks * Math.Min(0.30, 0.10 + 0.20 / 6 * difficulty.OverallDifficulty);
RequiredGoodHits = NestedHitObjects.Count * Math.Min(0.15, 0.05 + 0.10 / 6 * difficulty.OverallDifficulty);
RequiredGreatHits = NestedHitObjects.Count * Math.Min(0.30, 0.10 + 0.20 / 6 * difficulty.OverallDifficulty);
}
private List<DrumRollTick> createTicks()
protected override void CreateNestedHitObjects()
{
var ret = new List<DrumRollTick>();
base.CreateNestedHitObjects();
createTicks();
}
private void createTicks()
{
if (tickSpacing == 0)
return ret;
return;
bool first = true;
for (double t = StartTime; t < EndTime + tickSpacing / 2; t += tickSpacing)
{
ret.Add(new DrumRollTick
AddNested(new DrumRollTick
{
FirstTick = first,
TickSpacing = tickSpacing,
StartTime = t,
IsStrong = IsStrong,
Samples = new SampleInfoList(Samples.Select(s => new SampleInfo
{
Bank = s.Bank,
Name = @"slidertick",
Volume = s.Volume
}))
IsStrong = IsStrong
});
first = false;
}
return ret;
}
}
}
}

View File

@ -23,9 +23,9 @@ namespace osu.Game.Rulesets.Taiko.Objects
/// </summary>
public double HitWindowMiss = 95;
public override void ApplyDefaults(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty)
protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty)
{
base.ApplyDefaults(controlPointInfo, difficulty);
base.ApplyDefaultsToSelf(controlPointInfo, difficulty);
HitWindowGreat = BeatmapDifficulty.DifficultyRange(difficulty.OverallDifficulty, 50, 35, 20);
HitWindowGood = BeatmapDifficulty.DifficultyRange(difficulty.OverallDifficulty, 120, 80, 50);

View File

@ -16,4 +16,4 @@ namespace osu.Game.Rulesets.Taiko.Objects
/// </summary>
public int RequiredHits = 10;
}
}
}

View File

@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Taiko.Objects;
@ -83,9 +84,9 @@ namespace osu.Game.Rulesets.Taiko.Replays
}
else if (drumRoll != null)
{
foreach (var tick in drumRoll.Ticks)
foreach (var tick in drumRoll.NestedHitObjects.OfType<DrumRollTick>())
{
Frames.Add(new ReplayFrame(tick.StartTime, null, null, hitButton ? ReplayButtonState.Left1 : ReplayButtonState.Left2));
Frames.Add(new ReplayFrame(tick.StartTime, null, null, hitButton ? ReplayButtonState.Right1 : ReplayButtonState.Right2));
hitButton = !hitButton;
}
}

View File

@ -1,6 +1,7 @@
// 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.Linq;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects.Drawables;
@ -88,7 +89,7 @@ namespace osu.Game.Rulesets.Taiko.Scoring
}
else if (obj is DrumRoll)
{
for (int i = 0; i < ((DrumRoll)obj).TotalTicks; i++)
for (int i = 0; i < ((DrumRoll)obj).NestedHitObjects.OfType<DrumRollTick>().Count(); i++)
{
AddJudgement(new TaikoDrumRollTickJudgement { Result = HitResult.Great });

View File

@ -95,6 +95,8 @@ namespace osu.Game.Rulesets.Taiko
public override string Description => "osu!taiko";
public override string ShortName => "taiko";
public override Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.fa_osu_taiko_o };
public override DifficultyCalculator CreateDifficultyCalculator(Beatmap beatmap, Mod[] mods = null) => new TaikoDifficultyCalculator(beatmap);

View File

@ -0,0 +1,44 @@
// 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 OpenTK;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Audio;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Taiko.Audio;
using osu.Game.Rulesets.Taiko.UI;
using osu.Game.Tests.Visual;
namespace osu.Game.Rulesets.Taiko.Tests
{
[Ignore("getting CI working")]
public class TestCaseInputDrum : OsuTestCase
{
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(InputDrum),
typeof(DrumSampleMapping),
typeof(SampleInfo),
typeof(SampleControlPoint)
};
public TestCaseInputDrum()
{
Add(new TaikoInputManager(new RulesetInfo { ID = 1 })
{
RelativeSizeAxes = Axes.Both,
Child = new Container
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = new Vector2(200),
Child = new InputDrum(new ControlPointInfo())
}
});
}
}
}

View File

@ -25,7 +25,7 @@ namespace osu.Game.Rulesets.Taiko.Tests
{
[TestFixture]
[Ignore("getting CI working")]
internal class TestCaseTaikoPlayfield : OsuTestCase
public class TestCaseTaikoPlayfield : OsuTestCase
{
private const double default_duration = 1000;
private const float scroll_time = 1000;
@ -165,11 +165,15 @@ namespace osu.Game.Rulesets.Taiko.Tests
private void addSwell(double duration = default_duration)
{
rulesetContainer.Playfield.Add(new DrawableSwell(new Swell
var swell = new Swell
{
StartTime = rulesetContainer.Playfield.Time.Current + scroll_time,
Duration = duration,
}));
};
swell.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
rulesetContainer.Playfield.Add(new DrawableSwell(swell));
}
private void addDrumRoll(bool strong, double duration = default_duration)
@ -184,6 +188,8 @@ namespace osu.Game.Rulesets.Taiko.Tests
Duration = duration,
};
d.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
rulesetContainer.Playfield.Add(new DrawableDrumRoll(d));
}
@ -195,6 +201,8 @@ namespace osu.Game.Rulesets.Taiko.Tests
IsStrong = strong
};
h.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
if (strong)
rulesetContainer.Playfield.Add(new DrawableCentreHitStrong(h));
else
@ -209,6 +217,8 @@ namespace osu.Game.Rulesets.Taiko.Tests
IsStrong = strong
};
h.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
if (strong)
rulesetContainer.Playfield.Add(new DrawableRimHitStrong(h));
else

View File

@ -4,12 +4,15 @@
using System;
using OpenTK;
using osu.Framework.Allocation;
using osu.Framework.Audio;
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.Game.Beatmaps.ControlPoints;
using osu.Game.Graphics;
using osu.Game.Rulesets.Taiko.Audio;
namespace osu.Game.Rulesets.Taiko.UI
{
@ -18,16 +21,26 @@ namespace osu.Game.Rulesets.Taiko.UI
/// </summary>
internal class InputDrum : Container
{
public InputDrum()
private const float middle_split = 0.025f;
private readonly ControlPointInfo controlPoints;
public InputDrum(ControlPointInfo controlPoints)
{
this.controlPoints = controlPoints;
RelativeSizeAxes = Axes.Both;
FillMode = FillMode.Fit;
}
const float middle_split = 0.025f;
[BackgroundDependencyLoader]
private void load(AudioManager audio)
{
var sampleMappings = new DrumSampleMapping(controlPoints, audio);
Children = new Drawable[]
{
new TaikoHalfDrum(false)
new TaikoHalfDrum(false, sampleMappings)
{
Name = "Left Half",
Anchor = Anchor.Centre,
@ -38,7 +51,7 @@ namespace osu.Game.Rulesets.Taiko.UI
RimAction = TaikoAction.LeftRim,
CentreAction = TaikoAction.LeftCentre
},
new TaikoHalfDrum(true)
new TaikoHalfDrum(true, sampleMappings)
{
Name = "Right Half",
Anchor = Anchor.Centre,
@ -72,8 +85,12 @@ namespace osu.Game.Rulesets.Taiko.UI
private readonly Sprite centre;
private readonly Sprite centreHit;
public TaikoHalfDrum(bool flipped)
private readonly DrumSampleMapping sampleMappings;
public TaikoHalfDrum(bool flipped, DrumSampleMapping sampleMappings)
{
this.sampleMappings = sampleMappings;
Masking = true;
Children = new Drawable[]
@ -128,15 +145,21 @@ namespace osu.Game.Rulesets.Taiko.UI
Drawable target = null;
Drawable back = null;
var drumSample = sampleMappings.SampleAt(Time.Current);
if (action == CentreAction)
{
target = centreHit;
back = centre;
drumSample.Centre?.Play();
}
else if (action == RimAction)
{
target = rimHit;
back = rim;
drumSample.Rim?.Play();
}
if (target != null)

View File

@ -16,6 +16,7 @@ using osu.Framework.Extensions.Color4Extensions;
using System.Linq;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Taiko.Objects.Drawables;
using osu.Game.Beatmaps.ControlPoints;
namespace osu.Game.Rulesets.Taiko.UI
{
@ -54,7 +55,7 @@ namespace osu.Game.Rulesets.Taiko.UI
private readonly Box overlayBackground;
private readonly Box background;
public TaikoPlayfield()
public TaikoPlayfield(ControlPointInfo controlPoints)
: base(Axes.X)
{
AddRangeInternal(new Drawable[]
@ -149,7 +150,7 @@ namespace osu.Game.Rulesets.Taiko.UI
{
RelativeSizeAxes = Axes.Both,
},
new InputDrum
new InputDrum(controlPoints)
{
Anchor = Anchor.CentreRight,
Origin = Anchor.CentreRight,
@ -249,7 +250,9 @@ namespace osu.Game.Rulesets.Taiko.UI
{
topLevelHitContainer.Add(judgedObject.CreateProxy());
}
catch { }
catch
{
}
}
hitExplosionContainer.Add(new HitExplosion(judgedObject, isRim));

View File

@ -93,7 +93,7 @@ namespace osu.Game.Rulesets.Taiko.UI
public override PassThroughInputManager CreateInputManager() => new TaikoInputManager(Ruleset.RulesetInfo);
protected override Playfield CreatePlayfield() => new TaikoPlayfield
protected override Playfield CreatePlayfield() => new TaikoPlayfield(Beatmap.ControlPointInfo)
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft

View File

@ -36,14 +36,15 @@
<HintPath>$(SolutionDir)\packages\NUnit.3.8.1\lib\net45\nunit.framework.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="OpenTK, Version=3.0.0.0, Culture=neutral, PublicKeyToken=bad199fe84eb3df4, processorArchitecture=MSIL">
<HintPath>$(SolutionDir)\packages\OpenTK.3.0.0-git00009\lib\net20\OpenTK.dll</HintPath>
<Reference Include="OpenTK, Version=3.0.0.0, Culture=neutral, PublicKeyToken=bad199fe84eb3df4">
<HintPath>$(SolutionDir)\packages\ppy.OpenTK.3.0.11\lib\net45\OpenTK.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
</ItemGroup>
<ItemGroup>
<Compile Include="Audio\DrumSampleMapping.cs" />
<Compile Include="Beatmaps\TaikoBeatmapConverter.cs" />
<Compile Include="Judgements\TaikoDrumRollTickJudgement.cs" />
<Compile Include="Judgements\TaikoStrongHitJudgement.cs" />
@ -82,6 +83,7 @@
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Scoring\TaikoScoreProcessor.cs" />
<Compile Include="TaikoInputManager.cs" />
<Compile Include="Tests\TestCaseInputDrum.cs" />
<Compile Include="Tests\TestCasePerformancePoints.cs" />
<Compile Include="Tests\TestCaseTaikoPlayfield.cs" />
<Compile Include="UI\HitTarget.cs" />
@ -111,6 +113,9 @@
<Private>True</Private>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="NUnit" version="3.8.1" targetFramework="net461" />
<package id="OpenTK" version="3.0.0-git00009" targetFramework="net461" />
<package id="ppy.OpenTK" version="3.0.11" targetFramework="net461" />
</packages>

View File

@ -146,8 +146,8 @@ namespace osu.Game.Tests.Beatmaps.Formats
Assert.AreEqual(116999, difficultyPoint.Time);
Assert.AreEqual(0.75000000000000189d, difficultyPoint.SpeedMultiplier);
Assert.AreEqual(34, controlPoints.SoundPoints.Count);
var soundPoint = controlPoints.SoundPoints[0];
Assert.AreEqual(34, controlPoints.SamplePoints.Count);
var soundPoint = controlPoints.SamplePoints[0];
Assert.AreEqual(956, soundPoint.Time);
Assert.AreEqual("soft", soundPoint.SampleBank);
Assert.AreEqual(60, soundPoint.SampleVolume);

View File

@ -0,0 +1,176 @@
// 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 DeepEqual.Syntax;
using NUnit.Framework;
using osu.Game.Audio;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Formats;
using osu.Game.IO.Serialization;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Tests.Resources;
using OpenTK;
using OpenTK.Graphics;
namespace osu.Game.Tests.Beatmaps.Formats
{
[TestFixture]
public class OsuJsonDecoderTest
{
private const string normal = "Soleily - Renatus (Gamu) [Insane].osu";
private const string marathon = "Within Temptation - The Unforgiving (Armin) [Marathon].osu";
private const string with_sb = "Kozato snow - Rengetsu Ouka (_Kiva) [Yuki YukI].osu";
[Test]
public void TestDecodeMetadata()
{
var beatmap = decodeAsJson(normal);
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 beatmap = decodeAsJson(normal);
var beatmapInfo = beatmap.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 beatmap = decodeAsJson(normal);
var beatmapInfo = beatmap.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 TestDecodeDifficulty()
{
var beatmap = decodeAsJson(normal);
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 beatmap = decodeAsJson(normal);
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 beatmap = decodeAsJson(normal);
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));
}
[TestCase(normal)]
[TestCase(marathon)]
// Currently fails:
// [TestCase(with_sb)]
public void TestParity(string beatmap)
{
var beatmaps = decode(beatmap);
beatmaps.jsonDecoded.ShouldDeepEqual(beatmaps.legacyDecoded);
}
/// <summary>
/// Reads a .osu file first with a <see cref="LegacyBeatmapDecoder"/>, serializes the resulting <see cref="Beatmap"/> to JSON
/// and then deserializes the result back into a <see cref="Beatmap"/> through an <see cref="JsonBeatmapDecoder"/>.
/// </summary>
/// <param name="filename">The .osu file to decode.</param>
/// <returns>The <see cref="Beatmap"/> after being decoded by an <see cref="LegacyBeatmapDecoder"/>.</returns>
private Beatmap decodeAsJson(string filename) => decode(filename).jsonDecoded;
/// <summary>
/// Reads a .osu file first with a <see cref="LegacyBeatmapDecoder"/>, serializes the resulting <see cref="Beatmap"/> to JSON
/// and then deserializes the result back into a <see cref="Beatmap"/> through an <see cref="JsonBeatmapDecoder"/>.
/// </summary>
/// <param name="filename">The .osu file to decode.</param>
/// <returns>The <see cref="Beatmap"/> after being decoded by an <see cref="LegacyBeatmapDecoder"/>.</returns>
private (Beatmap legacyDecoded, Beatmap jsonDecoded) decode(string filename)
{
using (var stream = Resource.OpenResource(filename))
using (var sr = new StreamReader(stream))
{
var legacyDecoded = new LegacyBeatmapDecoder().DecodeBeatmap(sr);
using (var ms = new MemoryStream())
using (var sw = new StreamWriter(ms))
using (var sr2 = new StreamReader(ms))
{
sw.Write(legacyDecoded.Serialize());
sw.Flush();
ms.Position = 0;
return (legacyDecoded, new JsonBeatmapDecoder().DecodeBeatmap(sr2));
}
}
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -7,17 +7,17 @@ using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Lists;
using osu.Framework.Timing;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites;
using osu.Game.Overlays;
using OpenTK.Graphics;
using osu.Framework.Lists;
namespace osu.Game.Tests.Visual
{
internal class TestCaseBeatSyncedContainer : OsuTestCase
public class TestCaseBeatSyncedContainer : OsuTestCase
{
private readonly MusicController mc;

View File

@ -0,0 +1,394 @@
// 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 System.Linq;
using System.Text;
using osu.Framework.Allocation;
using osu.Framework.Extensions;
using osu.Framework.Graphics;
using osu.Game.Beatmaps;
using osu.Game.Configuration;
using osu.Game.Screens.Select;
using osu.Game.Screens.Select.Carousel;
using osu.Game.Screens.Select.Filter;
namespace osu.Game.Tests.Visual
{
public class TestCaseBeatmapCarousel : OsuTestCase
{
private TestBeatmapCarousel carousel;
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(CarouselItem),
typeof(CarouselGroup),
typeof(CarouselGroupEagerSelect),
typeof(CarouselBeatmap),
typeof(CarouselBeatmapSet),
typeof(DrawableCarouselItem),
typeof(CarouselItemState),
typeof(DrawableCarouselBeatmap),
typeof(DrawableCarouselBeatmapSet),
};
private readonly Stack<BeatmapSetInfo> selectedSets = new Stack<BeatmapSetInfo>();
private BeatmapInfo currentSelection;
private const int set_count = 5;
[BackgroundDependencyLoader]
private void load()
{
Add(carousel = new TestBeatmapCarousel
{
RelativeSizeAxes = Axes.Both,
});
List<BeatmapSetInfo> beatmapSets = new List<BeatmapSetInfo>();
for (int i = 1; i <= set_count; i++)
beatmapSets.Add(createTestBeatmapSet(i));
carousel.SelectionChanged = s => currentSelection = s;
AddStep("Load Beatmaps", () => { carousel.BeatmapSets = beatmapSets; });
AddUntilStep(() => carousel.BeatmapSets.Any(), "Wait for load");
testTraversal();
testFiltering();
testRandom();
testAddRemove();
testSorting();
testRemoveAll();
testEmptyTraversal();
testHiding();
}
private void ensureRandomFetchSuccess() =>
AddAssert("ensure prev random fetch worked", () => selectedSets.Peek() == carousel.SelectedBeatmapSet);
private void checkSelected(int set, int? diff = null) =>
AddAssert($"selected is set{set}{(diff.HasValue ? $" diff{diff.Value}" : "")}", () =>
{
if (diff != null)
return carousel.SelectedBeatmap == carousel.BeatmapSets.Skip(set - 1).First().Beatmaps.Skip(diff.Value - 1).First();
return carousel.BeatmapSets.Skip(set - 1).First().Beatmaps.Contains(carousel.SelectedBeatmap);
});
private void setSelected(int set, int diff) =>
AddStep($"select set{set} diff{diff}", () =>
carousel.SelectBeatmap(carousel.BeatmapSets.Skip(set - 1).First().Beatmaps.Skip(diff - 1).First()));
private void advanceSelection(bool diff, int direction = 1, int count = 1)
{
if (count == 1)
AddStep($"select {(direction > 0 ? "next" : "prev")} {(diff ? "diff" : "set")}", () =>
carousel.SelectNext(direction, !diff));
else
{
AddRepeatStep($"select {(direction > 0 ? "next" : "prev")} {(diff ? "diff" : "set")}", () =>
carousel.SelectNext(direction, !diff), count);
}
}
private void checkVisibleItemCount(bool diff, int count) =>
AddAssert($"{count} {(diff ? "diffs" : "sets")} visible", () =>
carousel.Items.Count(s => (diff ? s.Item is CarouselBeatmap : s.Item is CarouselBeatmapSet) && s.Item.Visible) == count);
private void checkNoSelection() => AddAssert("Selection is null", () => currentSelection == null);
private void nextRandom() =>
AddStep("select random next", () =>
{
carousel.RandomAlgorithm.Value = RandomSelectAlgorithm.RandomPermutation;
if (!selectedSets.Any() && carousel.SelectedBeatmap != null)
selectedSets.Push(carousel.SelectedBeatmapSet);
carousel.SelectNextRandom();
selectedSets.Push(carousel.SelectedBeatmapSet);
});
private void ensureRandomDidntRepeat() =>
AddAssert("ensure no repeats", () => selectedSets.Distinct().Count() == selectedSets.Count);
private void prevRandom() => AddStep("select random last", () =>
{
carousel.SelectPreviousRandom();
selectedSets.Pop();
});
/// <summary>
/// Test keyboard traversal
/// </summary>
private void testTraversal()
{
advanceSelection(direction: 1, diff: false);
checkSelected(1, 1);
advanceSelection(direction: 1, diff: true);
checkSelected(1, 2);
advanceSelection(direction: -1, diff: false);
checkSelected(set_count, 1);
advanceSelection(direction: -1, diff: true);
checkSelected(set_count - 1, 3);
advanceSelection(diff: false);
advanceSelection(diff: false);
checkSelected(1, 2);
advanceSelection(direction: -1, diff: true);
advanceSelection(direction: -1, diff: true);
checkSelected(set_count, 3);
}
/// <summary>
/// Test filtering
/// </summary>
private void testFiltering()
{
// basic filtering
setSelected(1, 1);
AddStep("Filter", () => carousel.Filter(new FilterCriteria { SearchText = "set #3!" }, false));
checkVisibleItemCount(diff: false, count: 1);
checkVisibleItemCount(diff: true, count: 3);
checkSelected(3, 1);
advanceSelection(diff: true, count: 4);
checkSelected(3, 2);
AddStep("Un-filter (debounce)", () => carousel.Filter(new FilterCriteria()));
AddUntilStep(() => !carousel.PendingFilterTask, "Wait for debounce");
checkVisibleItemCount(diff: false, count: set_count);
checkVisibleItemCount(diff: true, count: 3);
// test filtering some difficulties (and keeping current beatmap set selected).
setSelected(1, 2);
AddStep("Filter some difficulties", () => carousel.Filter(new FilterCriteria { SearchText = "Normal" }, false));
checkSelected(1, 1);
AddStep("Un-filter", () => carousel.Filter(new FilterCriteria(), false));
checkSelected(1, 1);
AddStep("Filter all", () => carousel.Filter(new FilterCriteria { SearchText = "Dingo" }, false));
checkVisibleItemCount(false, 0);
checkVisibleItemCount(true, 0);
AddAssert("Selection is null", () => currentSelection == null);
AddStep("Un-filter", () => carousel.Filter(new FilterCriteria(), false));
AddAssert("Selection is non-null", () => currentSelection != null);
}
/// <summary>
/// Test random non-repeating algorithm
/// </summary>
private void testRandom()
{
setSelected(1, 1);
nextRandom();
ensureRandomDidntRepeat();
nextRandom();
ensureRandomDidntRepeat();
nextRandom();
ensureRandomDidntRepeat();
prevRandom();
ensureRandomFetchSuccess();
prevRandom();
ensureRandomFetchSuccess();
nextRandom();
ensureRandomDidntRepeat();
nextRandom();
ensureRandomDidntRepeat();
nextRandom();
AddAssert("ensure repeat", () => selectedSets.Contains(carousel.SelectedBeatmapSet));
}
/// <summary>
/// Test adding and removing beatmap sets
/// </summary>
private void testAddRemove()
{
AddStep("Add new set", () => carousel.UpdateBeatmapSet(createTestBeatmapSet(set_count + 1)));
AddStep("Add new set", () => carousel.UpdateBeatmapSet(createTestBeatmapSet(set_count + 2)));
checkVisibleItemCount(false, set_count + 2);
AddStep("Remove set", () => carousel.RemoveBeatmapSet(createTestBeatmapSet(set_count + 2)));
checkVisibleItemCount(false, set_count + 1);
setSelected(set_count + 1, 1);
AddStep("Remove set", () => carousel.RemoveBeatmapSet(createTestBeatmapSet(set_count + 1)));
checkVisibleItemCount(false, set_count);
checkSelected(set_count);
}
/// <summary>
/// Test sorting
/// </summary>
private void testSorting()
{
AddStep("Sort by author", () => carousel.Filter(new FilterCriteria { Sort = SortMode.Author }, false));
AddAssert("Check zzzzz is at bottom", () => carousel.BeatmapSets.Last().Metadata.AuthorString == "zzzzz");
AddStep("Sort by artist", () => carousel.Filter(new FilterCriteria { Sort = SortMode.Artist }, false));
AddAssert($"Check #{set_count} is at bottom", () => carousel.BeatmapSets.Last().Metadata.Title.EndsWith($"#{set_count}!"));
}
private void testRemoveAll()
{
setSelected(2, 1);
AddAssert("Selection is non-null", () => currentSelection != null);
AddStep("Remove selected", () => carousel.RemoveBeatmapSet(carousel.SelectedBeatmapSet));
checkSelected(2);
AddStep("Remove first", () => carousel.RemoveBeatmapSet(carousel.BeatmapSets.First()));
AddStep("Remove first", () => carousel.RemoveBeatmapSet(carousel.BeatmapSets.First()));
checkSelected(1);
AddUntilStep(() =>
{
if (!carousel.BeatmapSets.Any()) return true;
carousel.RemoveBeatmapSet(carousel.BeatmapSets.Last());
return false;
}, "Remove all");
checkNoSelection();
}
private void testEmptyTraversal()
{
advanceSelection(direction: 1, diff: false);
checkNoSelection();
advanceSelection(direction: 1, diff: true);
checkNoSelection();
advanceSelection(direction: -1, diff: false);
checkNoSelection();
advanceSelection(direction: -1, diff: true);
checkNoSelection();
}
private void testHiding()
{
var hidingSet = createTestBeatmapSet(1);
hidingSet.Beatmaps[1].Hidden = true;
AddStep("Add set with diff 2 hidden", () => carousel.UpdateBeatmapSet(hidingSet));
setSelected(1, 1);
checkVisibleItemCount(true, 2);
advanceSelection(true);
checkSelected(1, 3);
setHidden(3);
checkSelected(1, 1);
setHidden(2, false);
advanceSelection(true);
checkSelected(1, 2);
setHidden(1);
checkSelected(1, 2);
setHidden(2);
checkNoSelection();
void setHidden(int diff, bool hidden = true)
{
AddStep((hidden ? "" : "un") + $"hide diff {diff}", () =>
{
hidingSet.Beatmaps[diff - 1].Hidden = hidden;
carousel.UpdateBeatmapSet(hidingSet);
});
}
}
private BeatmapSetInfo createTestBeatmapSet(int i)
{
return new BeatmapSetInfo
{
ID = i,
OnlineBeatmapSetID = i,
Hash = new MemoryStream(Encoding.UTF8.GetBytes(Guid.NewGuid().ToString())).ComputeMD5Hash(),
Metadata = new BeatmapMetadata
{
OnlineBeatmapSetID = i,
// Create random metadata, then we can check if sorting works based on these
Artist = $"peppy{i.ToString().PadLeft(6, '0')}",
Title = $"test set #{i}!",
AuthorString = string.Concat(Enumerable.Repeat((char)('z' - Math.Min(25, i - 1)), 5))
},
Beatmaps = new List<BeatmapInfo>(new[]
{
new BeatmapInfo
{
OnlineBeatmapID = i * 10,
Path = "normal.osu",
Version = "Normal",
StarDifficulty = 2,
BaseDifficulty = new BeatmapDifficulty
{
OverallDifficulty = 3.5f,
}
},
new BeatmapInfo
{
OnlineBeatmapID = i * 10 + 1,
Path = "hard.osu",
Version = "Hard",
StarDifficulty = 5,
BaseDifficulty = new BeatmapDifficulty
{
OverallDifficulty = 5,
}
},
new BeatmapInfo
{
OnlineBeatmapID = i * 10 + 2,
Path = "insane.osu",
Version = "Insane",
StarDifficulty = 6,
BaseDifficulty = new BeatmapDifficulty
{
OverallDifficulty = 7,
}
},
}),
};
}
private class TestBeatmapCarousel : BeatmapCarousel
{
public new List<DrawableCarouselItem> Items => base.Items;
public bool PendingFilterTask => FilterTask != null;
}
}
}

View File

@ -10,7 +10,7 @@ namespace osu.Game.Tests.Visual
{
[TestFixture]
[System.ComponentModel.Description("PlaySongSelect leaderboard/details area")]
internal class TestCaseBeatmapDetailArea : OsuTestCase
public class TestCaseBeatmapDetailArea : OsuTestCase
{
public TestCaseBeatmapDetailArea()
{

View File

@ -10,7 +10,7 @@ using osu.Game.Screens.Select;
namespace osu.Game.Tests.Visual
{
[Description("PlaySongSelect beatmap details")]
internal class TestCaseBeatmapDetails : OsuTestCase
public class TestCaseBeatmapDetails : OsuTestCase
{
public TestCaseBeatmapDetails()
{

View File

@ -0,0 +1,161 @@
// 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 System.Linq;
using OpenTK;
using osu.Framework.Allocation;
using osu.Framework.Configuration;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Beatmaps;
using osu.Game.Graphics.Sprites;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Osu;
using osu.Game.Screens.Select;
using osu.Game.Tests.Beatmaps;
namespace osu.Game.Tests.Visual
{
public class TestCaseBeatmapInfoWedge : OsuTestCase
{
private RulesetStore rulesets;
private TestBeatmapInfoWedge infoWedge;
private readonly List<Beatmap> beatmaps = new List<Beatmap>();
private readonly Bindable<WorkingBeatmap> beatmap = new Bindable<WorkingBeatmap>();
[BackgroundDependencyLoader]
private void load(OsuGameBase game, RulesetStore rulesets)
{
this.rulesets = rulesets;
beatmap.BindTo(game.Beatmap);
}
protected override void LoadComplete()
{
base.LoadComplete();
Add(infoWedge = new TestBeatmapInfoWedge
{
Size = new Vector2(0.5f, 245),
RelativeSizeAxes = Axes.X,
Margin = new MarginPadding { Top = 20 }
});
AddStep("show", () =>
{
infoWedge.State = Visibility.Visible;
infoWedge.UpdateBeatmap(beatmap);
});
AddWaitStep(3);
AddStep("hide", () => { infoWedge.State = Visibility.Hidden; });
AddWaitStep(3);
AddStep("show", () => { infoWedge.State = Visibility.Visible; });
foreach (var rulesetInfo in rulesets.AvailableRulesets)
{
var ruleset = rulesetInfo.CreateInstance();
beatmaps.Add(createTestBeatmap(rulesetInfo));
var name = rulesetInfo.ShortName;
selectBeatmap(name);
// TODO: adjust cases once more info is shown for other gamemodes
switch (ruleset)
{
case OsuRuleset osu:
testOsuBeatmap(osu);
testInfoLabels(5);
break;
default:
testInfoLabels(2);
break;
}
}
testNullBeatmap();
}
private void testOsuBeatmap(OsuRuleset ruleset)
{
AddAssert("check version", () => infoWedge.Info.VersionLabel.Text == $"{ruleset.ShortName}Version");
AddAssert("check title", () => infoWedge.Info.TitleLabel.Text == $"{ruleset.ShortName}Source — {ruleset.ShortName}Title");
AddAssert("check artist", () => infoWedge.Info.ArtistLabel.Text == $"{ruleset.ShortName}Artist");
AddAssert("check author", () => infoWedge.Info.MapperContainer.Children.OfType<OsuSpriteText>().Any(s => s.Text == $"{ruleset.ShortName}Author"));
}
private void testInfoLabels(int expectedCount)
{
AddAssert("check infolabels exists", () => infoWedge.Info.InfoLabelContainer.Children.Any());
AddAssert("check infolabels count", () => infoWedge.Info.InfoLabelContainer.Children.Count == expectedCount);
}
private void testNullBeatmap()
{
selectNullBeatmap();
AddAssert("check empty version", () => string.IsNullOrEmpty(infoWedge.Info.VersionLabel.Text));
AddAssert("check default title", () => infoWedge.Info.TitleLabel.Text == beatmap.Default.BeatmapInfo.Metadata.Title);
AddAssert("check default artist", () => infoWedge.Info.ArtistLabel.Text == beatmap.Default.BeatmapInfo.Metadata.Artist);
AddAssert("check empty author", () => !infoWedge.Info.MapperContainer.Children.Any());
AddAssert("check no infolabels", () => !infoWedge.Info.InfoLabelContainer.Children.Any());
}
private void selectBeatmap(string name)
{
var infoBefore = infoWedge.Info;
AddStep($"select {name} beatmap", () =>
{
beatmap.Value = new TestWorkingBeatmap(beatmaps.First(b => b.BeatmapInfo.Ruleset.ShortName == name));
infoWedge.UpdateBeatmap(beatmap);
});
AddUntilStep(() => infoWedge.Info != infoBefore, "wait for async load");
}
private void selectNullBeatmap()
{
AddStep("select null beatmap", () =>
{
beatmap.Value = beatmap.Default;
infoWedge.UpdateBeatmap(beatmap);
});
}
private Beatmap createTestBeatmap(RulesetInfo ruleset)
{
List<HitObject> objects = new List<HitObject>();
for (double i = 0; i < 50000; i += 1000)
objects.Add(new HitObject { StartTime = i });
return new Beatmap
{
BeatmapInfo = new BeatmapInfo
{
Metadata = new BeatmapMetadata
{
AuthorString = $"{ruleset.ShortName}Author",
Artist = $"{ruleset.ShortName}Artist",
Source = $"{ruleset.ShortName}Source",
Title = $"{ruleset.ShortName}Title"
},
Ruleset = ruleset,
StarDifficulty = 6,
Version = $"{ruleset.ShortName}Version"
},
HitObjects = objects
};
}
private class TestBeatmapInfoWedge : BeatmapInfoWedge
{
public new BufferedWedgeInfo Info => base.Info;
}
}
}

View File

@ -10,7 +10,7 @@ using OpenTK.Input;
namespace osu.Game.Tests.Visual
{
[Description("bottom beatmap details")]
internal class TestCaseBeatmapOptionsOverlay : OsuTestCase
public class TestCaseBeatmapOptionsOverlay : OsuTestCase
{
public TestCaseBeatmapOptionsOverlay()
{

View File

@ -12,7 +12,7 @@ using osu.Game.Users;
namespace osu.Game.Tests.Visual
{
internal class TestCaseBeatmapSetOverlay : OsuTestCase
public class TestCaseBeatmapSetOverlay : OsuTestCase
{
private readonly BeatmapSetOverlay overlay;

View File

@ -6,7 +6,7 @@ using osu.Game.Graphics.UserInterface;
namespace osu.Game.Tests.Visual
{
internal class TestCaseBreadcrumbs : OsuTestCase
public class TestCaseBreadcrumbs : OsuTestCase
{
public TestCaseBreadcrumbs()
{

View File

@ -8,7 +8,7 @@ using System.Collections.Generic;
namespace osu.Game.Tests.Visual
{
internal class TestCaseBreakOverlay : OsuTestCase
public class TestCaseBreakOverlay : OsuTestCase
{
private readonly BreakOverlay breakOverlay;

View File

@ -9,7 +9,7 @@ using OpenTK.Graphics;
namespace osu.Game.Tests.Visual
{
internal class TestCaseButtonSystem : OsuTestCase
public class TestCaseButtonSystem : OsuTestCase
{
public TestCaseButtonSystem()
{

View File

@ -8,7 +8,7 @@ using osu.Game.Overlays;
namespace osu.Game.Tests.Visual
{
[Description("Testing chat api and overlay")]
internal class TestCaseChatDisplay : OsuTestCase
public class TestCaseChatDisplay : OsuTestCase
{
public TestCaseChatDisplay()
{

View File

@ -13,7 +13,7 @@ using osu.Game.Graphics.Cursor;
namespace osu.Game.Tests.Visual
{
internal class TestCaseContextMenu : OsuTestCase
public class TestCaseContextMenu : OsuTestCase
{
private const int start_time = 0;
private const int duration = 1000;

View File

@ -7,7 +7,7 @@ using osu.Game.Overlays.Dialog;
namespace osu.Game.Tests.Visual
{
internal class TestCaseDialogOverlay : OsuTestCase
public class TestCaseDialogOverlay : OsuTestCase
{
public TestCaseDialogOverlay()
{

View File

@ -12,7 +12,7 @@ using osu.Game.Users;
namespace osu.Game.Tests.Visual
{
internal class TestCaseDrawableRoom : OsuTestCase
public class TestCaseDrawableRoom : OsuTestCase
{
private RulesetStore rulesets;

View File

@ -9,7 +9,7 @@ using osu.Game.Screens.Tournament.Teams;
namespace osu.Game.Tests.Visual
{
[Description("for tournament use")]
internal class TestCaseDrawings : OsuTestCase
public class TestCaseDrawings : OsuTestCase
{
public TestCaseDrawings()
{

View File

@ -0,0 +1,54 @@
// 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 OpenTK;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Timing;
using osu.Game.Rulesets.Edit.Layers.Selection;
using osu.Game.Rulesets.Osu.Edit;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables;
namespace osu.Game.Tests.Visual
{
public class TestCaseEditorSelectionLayer : OsuTestCase
{
public override IReadOnlyList<Type> RequiredTypes => new[] { typeof(SelectionLayer) };
public TestCaseEditorSelectionLayer()
{
var playfield = new OsuEditPlayfield
{
new DrawableHitCircle(new HitCircle { Position = new Vector2(256, 192), Scale = 0.5f }),
new DrawableHitCircle(new HitCircle { Position = new Vector2(344, 148), Scale = 0.5f }),
new DrawableSlider(new Slider
{
ControlPoints = new List<Vector2>
{
new Vector2(128, 256),
new Vector2(344, 256),
},
Distance = 400,
Position = new Vector2(128, 256),
Velocity = 1,
TickDistance = 100,
Scale = 0.5f
})
};
Children = new Drawable[]
{
new Container
{
RelativeSizeAxes = Axes.Both,
Clock = new FramedClock(new StopwatchClock()),
Child = playfield
},
new SelectionLayer(playfield)
};
}
}
}

View File

@ -14,7 +14,7 @@ using osu.Framework.Configuration;
namespace osu.Game.Tests.Visual
{
internal class TestCaseEditorSummaryTimeline : OsuTestCase
public class TestCaseEditorSummaryTimeline : OsuTestCase
{
private const int length = 60000;
private readonly Random random;
@ -56,7 +56,7 @@ namespace osu.Game.Tests.Visual
b.ControlPointInfo.EffectPoints.Add(new EffectControlPoint { Time = random.Next(0, length) });
for (int i = 0; i < random.Next(1, 5); i++)
b.ControlPointInfo.SoundPoints.Add(new SoundControlPoint { Time = random.Next(0, length) });
b.ControlPointInfo.SamplePoints.Add(new SampleControlPoint { Time = random.Next(0, length) });
b.BeatmapInfo.Bookmarks = new int[random.Next(10, 30)];
for (int i = 0; i < b.BeatmapInfo.Bookmarks.Length; i++)

View File

@ -5,7 +5,7 @@ using osu.Game.Beatmaps.ControlPoints;
namespace osu.Game.Tests.Visual
{
internal class TestCaseGamefield : OsuTestCase
public class TestCaseGamefield : OsuTestCase
{
protected override void LoadComplete()
{

View File

@ -0,0 +1,258 @@
// 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.ComponentModel;
using System.Linq;
using OpenTK.Input;
using osu.Framework.Allocation;
using osu.Framework.Graphics.Containers;
using osu.Framework.Input;
using osu.Framework.Logging;
using osu.Game.Screens.Play;
namespace osu.Game.Tests.Visual
{
[Description("player pause/fail screens")]
public class TestCaseGameplayMenuOverlay : OsuTestCase
{
public override IReadOnlyList<Type> RequiredTypes => new[] { typeof(FailOverlay), typeof(PauseContainer) };
private FailOverlay failOverlay;
private PauseContainer.PauseOverlay pauseOverlay;
[BackgroundDependencyLoader]
private void load()
{
Add(pauseOverlay = new PauseContainer.PauseOverlay
{
OnResume = () => Logger.Log(@"Resume"),
OnRetry = () => Logger.Log(@"Retry"),
OnQuit = () => Logger.Log(@"Quit"),
});
Add(failOverlay = new FailOverlay
{
OnRetry = () => Logger.Log(@"Retry"),
OnQuit = () => Logger.Log(@"Quit"),
});
var retryCount = 0;
AddStep("Add retry", () =>
{
retryCount++;
pauseOverlay.Retries = failOverlay.Retries = retryCount;
});
AddToggleStep("Toggle pause overlay", t => pauseOverlay.ToggleVisibility());
AddToggleStep("Toggle fail overlay", t => failOverlay.ToggleVisibility());
testHideResets();
testEnterWithoutSelection();
testKeyUpFromInitial();
testKeyDownFromInitial();
testKeyUpWrapping();
testKeyDownWrapping();
testMouseSelectionAfterKeySelection();
testKeySelectionAfterMouseSelection();
testMouseDeselectionResets();
testClickSelection();
testEnterKeySelection();
}
/// <summary>
/// Test that hiding the overlay after hovering a button will reset the overlay to the initial state with no buttons selected.
/// </summary>
private void testHideResets()
{
AddStep("Show overlay", () => failOverlay.Show());
AddStep("Hover first button", () => failOverlay.Buttons.First().TriggerOnMouseMove(null));
AddStep("Hide overlay", () => failOverlay.Hide());
AddAssert("Overlay state is reset", () => !failOverlay.Buttons.Any(b => b.Selected));
}
/// <summary>
/// Tests that pressing enter after an overlay shows doesn't trigger an event because a selection hasn't occurred.
/// </summary>
private void testEnterWithoutSelection()
{
AddStep("Show overlay", () => pauseOverlay.Show());
AddStep("Press enter", () => pauseOverlay.TriggerOnKeyDown(null, new KeyDownEventArgs { Key = Key.Enter }));
AddAssert("Overlay still open", () => pauseOverlay.State == Visibility.Visible);
AddStep("Hide overlay", () => pauseOverlay.Hide());
}
/// <summary>
/// Tests that pressing the up arrow from the initial state selects the last button.
/// </summary>
private void testKeyUpFromInitial()
{
AddStep("Show overlay", () => pauseOverlay.Show());
AddStep("Up arrow", () => pauseOverlay.TriggerOnKeyDown(null, new KeyDownEventArgs { Key = Key.Up }));
AddAssert("Last button selected", () => pauseOverlay.Buttons.Last().Selected);
AddStep("Hide overlay", () => pauseOverlay.Hide());
}
/// <summary>
/// Tests that pressing the down arrow from the initial state selects the first button.
/// </summary>
private void testKeyDownFromInitial()
{
AddStep("Show overlay", () => pauseOverlay.Show());
AddStep("Down arrow", () => pauseOverlay.TriggerOnKeyDown(null, new KeyDownEventArgs { Key = Key.Down }));
AddAssert("First button selected", () => pauseOverlay.Buttons.First().Selected);
AddStep("Hide overlay", () => pauseOverlay.Hide());
}
/// <summary>
/// Tests that pressing the up arrow repeatedly causes the selected button to wrap correctly.
/// </summary>
private void testKeyUpWrapping()
{
AddStep("Show overlay", () => failOverlay.Show());
AddStep("Up arrow", () => failOverlay.TriggerOnKeyDown(null, new KeyDownEventArgs { Key = Key.Up }));
AddAssert("Last button selected", () => failOverlay.Buttons.Last().Selected);
AddStep("Up arrow", () => failOverlay.TriggerOnKeyDown(null, new KeyDownEventArgs { Key = Key.Up }));
AddAssert("First button selected", () => failOverlay.Buttons.First().Selected);
AddStep("Up arrow", () => failOverlay.TriggerOnKeyDown(null, new KeyDownEventArgs { Key = Key.Up }));
AddAssert("Last button selected", () => failOverlay.Buttons.Last().Selected);
AddStep("Hide overlay", () => failOverlay.Hide());
}
/// <summary>
/// Tests that pressing the down arrow repeatedly causes the selected button to wrap correctly.
/// </summary>
private void testKeyDownWrapping()
{
AddStep("Show overlay", () => failOverlay.Show());
AddStep("Down arrow", () => failOverlay.TriggerOnKeyDown(null, new KeyDownEventArgs { Key = Key.Down }));
AddAssert("First button selected", () => failOverlay.Buttons.First().Selected);
AddStep("Down arrow", () => failOverlay.TriggerOnKeyDown(null, new KeyDownEventArgs { Key = Key.Down }));
AddAssert("Last button selected", () => failOverlay.Buttons.Last().Selected);
AddStep("Down arrow", () => failOverlay.TriggerOnKeyDown(null, new KeyDownEventArgs { Key = Key.Down }));
AddAssert("First button selected", () => failOverlay.Buttons.First().Selected);
AddStep("Hide overlay", () => failOverlay.Hide());
}
/// <summary>
/// Tests that hovering a button that was previously selected with the keyboard correctly selects the new button and deselects the previous button.
/// </summary>
private void testMouseSelectionAfterKeySelection()
{
AddStep("Show overlay", () => pauseOverlay.Show());
var secondButton = pauseOverlay.Buttons.Skip(1).First();
AddStep("Down arrow", () => pauseOverlay.TriggerOnKeyDown(null, new KeyDownEventArgs { Key = Key.Down }));
AddStep("Hover second button", () => secondButton.TriggerOnMouseMove(null));
AddAssert("First button not selected", () => !pauseOverlay.Buttons.First().Selected);
AddAssert("Second button selected", () => secondButton.Selected);
AddStep("Hide overlay", () => pauseOverlay.Hide());
}
/// <summary>
/// Tests that pressing a key after selecting a button with a hover event correctly selects a new button and deselects the previous button.
/// </summary>
private void testKeySelectionAfterMouseSelection()
{
AddStep("Show overlay", () => pauseOverlay.Show());
var secondButton = pauseOverlay.Buttons.Skip(1).First();
AddStep("Hover second button", () => secondButton.TriggerOnMouseMove(null));
AddStep("Up arrow", () => pauseOverlay.TriggerOnKeyDown(null, new KeyDownEventArgs { Key = Key.Up }));
AddAssert("Second button not selected", () => !secondButton.Selected);
AddAssert("First button selected", () => pauseOverlay.Buttons.First().Selected);
AddStep("Hide overlay", () => pauseOverlay.Hide());
}
/// <summary>
/// Tests that deselecting with the mouse by losing hover will reset the overlay to the initial state.
/// </summary>
private void testMouseDeselectionResets()
{
AddStep("Show overlay", () => pauseOverlay.Show());
var secondButton = pauseOverlay.Buttons.Skip(1).First();
AddStep("Hover second button", () => secondButton.TriggerOnMouseMove(null));
AddStep("Unhover second button", () => secondButton.TriggerOnHoverLost(null));
AddStep("Down arrow", () => pauseOverlay.TriggerOnKeyDown(null, new KeyDownEventArgs { Key = Key.Down }));
AddAssert("First button selected", () => pauseOverlay.Buttons.First().Selected); // Initial state condition
AddStep("Hide overlay", () => pauseOverlay.Hide());
}
/// <summary>
/// Tests that clicking on a button correctly causes a click event for that button.
/// </summary>
private void testClickSelection()
{
AddStep("Show overlay", () => pauseOverlay.Show());
var retryButton = pauseOverlay.Buttons.Skip(1).First();
bool triggered = false;
AddStep("Click retry button", () =>
{
var lastAction = pauseOverlay.OnRetry;
pauseOverlay.OnRetry = () => triggered = true;
retryButton.TriggerOnClick();
pauseOverlay.OnRetry = lastAction;
});
AddAssert("Action was triggered", () => triggered);
AddAssert("Overlay is closed", () => pauseOverlay.State == Visibility.Hidden);
}
/// <summary>
/// Tests that pressing the enter key with a button selected correctly causes a click event for that button.
/// </summary>
private void testEnterKeySelection()
{
AddStep("Show overlay", () => pauseOverlay.Show());
AddStep("Select second button", () =>
{
pauseOverlay.TriggerOnKeyDown(null, new KeyDownEventArgs { Key = Key.Down });
pauseOverlay.TriggerOnKeyDown(null, new KeyDownEventArgs { Key = Key.Down });
});
var retryButton = pauseOverlay.Buttons.Skip(1).First();
bool triggered = false;
AddStep("Press enter", () =>
{
var lastAction = pauseOverlay.OnRetry;
pauseOverlay.OnRetry = () => triggered = true;
retryButton.TriggerOnKeyDown(null, new KeyDownEventArgs { Key = Key.Enter });
pauseOverlay.OnRetry = lastAction;
});
AddAssert("Action was triggered", () => triggered);
AddAssert("Overlay is closed", () => pauseOverlay.State == Visibility.Hidden);
}
}
}

View File

@ -8,7 +8,7 @@ using OpenTK;
namespace osu.Game.Tests.Visual
{
internal class TestCaseGraph : OsuTestCase
public class TestCaseGraph : OsuTestCase
{
public TestCaseGraph()
{

View File

@ -0,0 +1,47 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using System.Collections.Generic;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Game.Graphics;
using osu.Game.Overlays.Profile.Sections;
using osu.Game.Overlays.Profile.Sections.Historical;
using osu.Game.Users;
namespace osu.Game.Tests.Visual
{
public class TestCaseHistoricalSection : OsuTestCase
{
public override IReadOnlyList<Type> RequiredTypes =>
new[]
{
typeof(HistoricalSection),
typeof(PaginatedMostPlayedBeatmapContainer),
typeof(DrawableMostPlayedRow),
typeof(DrawableProfileRow)
};
public TestCaseHistoricalSection()
{
HistoricalSection section;
Add(new Box
{
RelativeSizeAxes = Axes.Both,
Colour = OsuColour.Gray(0.2f)
});
Add(new ScrollContainer
{
RelativeSizeAxes = Axes.Both,
Child = section = new HistoricalSection(),
});
AddStep("Show peppy", () => section.User.Value = new User { Id = 2 });
AddStep("Show WubWoofWolf", () => section.User.Value = new User { Id = 39828 });
}
}
}

View File

@ -8,7 +8,7 @@ using OpenTK.Input;
namespace osu.Game.Tests.Visual
{
internal class TestCaseKeyCounter : OsuTestCase
public class TestCaseKeyCounter : OsuTestCase
{
public TestCaseKeyCounter()
{

View File

@ -6,14 +6,45 @@ using osu.Framework.Graphics;
using osu.Game.Rulesets.Scoring;
using osu.Game.Screens.Select.Leaderboards;
using osu.Game.Users;
using osu.Framework.Allocation;
using OpenTK;
using System.Linq;
using osu.Game.Beatmaps;
using osu.Game.Rulesets;
namespace osu.Game.Tests.Visual
{
[Description("PlaySongSelect leaderboard")]
internal class TestCaseLeaderboard : OsuTestCase
public class TestCaseLeaderboard : OsuTestCase
{
private readonly Leaderboard leaderboard;
private RulesetStore rulesets;
private readonly FailableLeaderboard leaderboard;
public TestCaseLeaderboard()
{
Add(leaderboard = new FailableLeaderboard
{
Origin = Anchor.Centre,
Anchor = Anchor.Centre,
Size = new Vector2(550f, 450f),
Scope = LeaderboardScope.Global,
});
AddStep(@"New Scores", newScores);
AddStep(@"Empty Scores", () => leaderboard.SetRetrievalState(PlaceholderState.NoScores));
AddStep(@"Network failure", () => leaderboard.SetRetrievalState(PlaceholderState.NetworkFailure));
AddStep(@"No supporter", () => leaderboard.SetRetrievalState(PlaceholderState.NotSupporter));
AddStep(@"Not logged in", () => leaderboard.SetRetrievalState(PlaceholderState.NotLoggedIn));
AddStep(@"Unavailable", () => leaderboard.SetRetrievalState(PlaceholderState.Unavailable));
AddStep(@"Real beatmap", realBeatmap);
}
[BackgroundDependencyLoader]
private void load(RulesetStore rulesets)
{
this.rulesets = rulesets;
}
private void newScores()
{
@ -204,17 +235,44 @@ namespace osu.Game.Tests.Visual
leaderboard.Scores = scores;
}
public TestCaseLeaderboard()
private void realBeatmap()
{
Add(leaderboard = new Leaderboard
leaderboard.Beatmap = new BeatmapInfo
{
Origin = Anchor.Centre,
Anchor = Anchor.Centre,
Size = new Vector2(550f, 450f),
});
StarDifficulty = 1.36,
Version = @"BASIC",
OnlineBeatmapID = 1113057,
Ruleset = rulesets.GetRuleset(0),
BaseDifficulty = new BeatmapDifficulty
{
CircleSize = 4,
DrainRate = 6.5f,
OverallDifficulty = 6.5f,
ApproachRate = 5,
},
OnlineInfo = new BeatmapOnlineInfo
{
Length = 115000,
CircleCount = 265,
SliderCount = 71,
PlayCount = 47906,
PassCount = 19899,
},
Metrics = new BeatmapMetrics
{
Ratings = Enumerable.Range(0, 11),
Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6),
Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6),
},
};
}
AddStep(@"New Scores", newScores);
newScores();
private class FailableLeaderboard : Leaderboard
{
public void SetRetrievalState(PlaceholderState state)
{
PlaceholderState = state;
}
}
}
}

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