mirror of
https://github.com/ppy/osu
synced 2025-04-11 03:31:46 +00:00
Merge branch 'master' into fix-taiko-rewind
This commit is contained in:
commit
26d9e3cc10
8
.github/pull_request_template.md
vendored
Normal file
8
.github/pull_request_template.md
vendored
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
Add any details pertaining to developers above the break.
|
||||||
|
|
||||||
|
- [ ] Depends on #PR
|
||||||
|
- Closes #ISSUE
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Add a sentence or two describing this change in plain english. This will be displayed on the [changelog](https://osu.ppy.sh/home/changelog). A single screenshot or short gif is also welcomed.
|
@ -1,12 +1,11 @@
|
|||||||
<component name="ProjectRunConfigurationManager">
|
<component name="ProjectRunConfigurationManager">
|
||||||
<configuration default="false" name="VisualTests (netcoreapp2.1)" type="DotNetProject" factoryName=".NET Project">
|
<configuration default="false" name="VisualTests" type="DotNetProject" factoryName=".NET Project">
|
||||||
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Game.Tests/bin/Debug/netcoreapp2.1/osu.Game.Tests.dll" />
|
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Game.Tests/bin/Debug/netcoreapp2.1/osu.Game.Tests.dll" />
|
||||||
<option name="PROGRAM_PARAMETERS" value="" />
|
<option name="PROGRAM_PARAMETERS" value="" />
|
||||||
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/osu.Game.Tests" />
|
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/osu.Game.Tests" />
|
||||||
<option name="PASS_PARENT_ENVS" value="1" />
|
<option name="PASS_PARENT_ENVS" value="1" />
|
||||||
<envs />
|
|
||||||
<option name="USE_MONO" value="0" />
|
|
||||||
<option name="USE_EXTERNAL_CONSOLE" value="0" />
|
<option name="USE_EXTERNAL_CONSOLE" value="0" />
|
||||||
|
<option name="USE_MONO" value="0" />
|
||||||
<option name="PROJECT_PATH" value="$PROJECT_DIR$/osu.Game.Tests/osu.Game.Tests.csproj" />
|
<option name="PROJECT_PATH" value="$PROJECT_DIR$/osu.Game.Tests/osu.Game.Tests.csproj" />
|
||||||
<option name="PROJECT_EXE_PATH_TRACKING" value="1" />
|
<option name="PROJECT_EXE_PATH_TRACKING" value="1" />
|
||||||
<option name="PROJECT_ARGUMENTS_TRACKING" value="1" />
|
<option name="PROJECT_ARGUMENTS_TRACKING" value="1" />
|
@ -1,12 +1,11 @@
|
|||||||
<component name="ProjectRunConfigurationManager">
|
<component name="ProjectRunConfigurationManager">
|
||||||
<configuration default="false" name="osu! (netcoreapp2.1)" type="DotNetProject" factoryName=".NET Project">
|
<configuration default="false" name="osu!" type="DotNetProject" factoryName=".NET Project">
|
||||||
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Desktop/bin/Debug/netcoreapp2.1/osu!.dll" />
|
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Desktop/bin/Debug/netcoreapp2.1/osu!.dll" />
|
||||||
<option name="PROGRAM_PARAMETERS" value="" />
|
<option name="PROGRAM_PARAMETERS" value="" />
|
||||||
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/osu.Desktop" />
|
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/osu.Desktop" />
|
||||||
<option name="PASS_PARENT_ENVS" value="1" />
|
<option name="PASS_PARENT_ENVS" value="1" />
|
||||||
<envs />
|
|
||||||
<option name="USE_MONO" value="0" />
|
|
||||||
<option name="USE_EXTERNAL_CONSOLE" value="0" />
|
<option name="USE_EXTERNAL_CONSOLE" value="0" />
|
||||||
|
<option name="USE_MONO" value="0" />
|
||||||
<option name="PROJECT_PATH" value="$PROJECT_DIR$/osu.Desktop/osu.Desktop.csproj" />
|
<option name="PROJECT_PATH" value="$PROJECT_DIR$/osu.Desktop/osu.Desktop.csproj" />
|
||||||
<option name="PROJECT_EXE_PATH_TRACKING" value="1" />
|
<option name="PROJECT_EXE_PATH_TRACKING" value="1" />
|
||||||
<option name="PROJECT_ARGUMENTS_TRACKING" value="1" />
|
<option name="PROJECT_ARGUMENTS_TRACKING" value="1" />
|
36
COMPILING.md
36
COMPILING.md
@ -1,36 +0,0 @@
|
|||||||
# 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.
|
|
29
osu.Desktop.Deploy/.vscode/launch.json
vendored
29
osu.Desktop.Deploy/.vscode/launch.json
vendored
@ -1,29 +0,0 @@
|
|||||||
{
|
|
||||||
// Use IntelliSense to learn about possible attributes.
|
|
||||||
// Hover to view descriptions of existing attributes.
|
|
||||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
|
||||||
"version": "0.2.0",
|
|
||||||
"configurations": [{
|
|
||||||
"name": "Deploy (Debug)",
|
|
||||||
"request": "launch",
|
|
||||||
"type": "mono",
|
|
||||||
"program": "${workspaceRoot}/bin/Debug/net471/osu.Desktop.Deploy.exe",
|
|
||||||
"cwd": "${workspaceRoot}",
|
|
||||||
"preLaunchTask": "Build (Debug)",
|
|
||||||
"runtimeExecutable": null,
|
|
||||||
"env": {},
|
|
||||||
"console": "internalConsole"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Deploy (Release)",
|
|
||||||
"request": "launch",
|
|
||||||
"type": "clr",
|
|
||||||
"program": "${workspaceRoot}/bin/Release/net471/osu.Desktop.Deploy.exe",
|
|
||||||
"cwd": "${workspaceRoot}",
|
|
||||||
"preLaunchTask": "Build (Release)",
|
|
||||||
"runtimeExecutable": null,
|
|
||||||
"env": {},
|
|
||||||
"console": "internalConsole"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
64
osu.Desktop.Deploy/.vscode/tasks.json
vendored
64
osu.Desktop.Deploy/.vscode/tasks.json
vendored
@ -1,64 +0,0 @@
|
|||||||
{
|
|
||||||
// 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)",
|
|
||||||
"group": {
|
|
||||||
"kind": "build",
|
|
||||||
"isDefault": true
|
|
||||||
},
|
|
||||||
"problemMatcher": [
|
|
||||||
"$msCompile"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"taskName": "Build (Release)",
|
|
||||||
"group": "build",
|
|
||||||
"args": [
|
|
||||||
"/property:Configuration=Release"
|
|
||||||
],
|
|
||||||
"problemMatcher": [
|
|
||||||
"$msCompile"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"taskName": "Clean (Debug)",
|
|
||||||
"args": [
|
|
||||||
"/target:Clean"
|
|
||||||
],
|
|
||||||
"problemMatcher": [
|
|
||||||
"$msCompile"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"taskName": "Clean (Release)",
|
|
||||||
"args": [
|
|
||||||
"/target:Clean",
|
|
||||||
"/property:Configuration=Release"
|
|
||||||
],
|
|
||||||
"problemMatcher": [
|
|
||||||
"$msCompile"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"taskName": "Clean All",
|
|
||||||
"dependsOn": [
|
|
||||||
"Clean (Debug)",
|
|
||||||
"Clean (Release)"
|
|
||||||
],
|
|
||||||
"problemMatcher": [
|
|
||||||
"$msCompile"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
@ -73,7 +73,7 @@ namespace osu.Desktop
|
|||||||
}
|
}
|
||||||
|
|
||||||
public StableStorage()
|
public StableStorage()
|
||||||
: base(string.Empty)
|
: base(string.Empty, null)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||||
|
|
||||||
using System.Diagnostics;
|
using System;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
using osu.Framework.Graphics.Textures;
|
using osu.Framework.Graphics.Textures;
|
||||||
|
using osu.Framework.Platform;
|
||||||
using osu.Game;
|
using osu.Game;
|
||||||
using osu.Game.Configuration;
|
using osu.Game.Configuration;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
@ -24,16 +25,18 @@ namespace osu.Desktop.Overlays
|
|||||||
private OsuConfigManager config;
|
private OsuConfigManager config;
|
||||||
private OsuGameBase game;
|
private OsuGameBase game;
|
||||||
private NotificationOverlay notificationOverlay;
|
private NotificationOverlay notificationOverlay;
|
||||||
|
private GameHost host;
|
||||||
|
|
||||||
public override bool HandleKeyboardInput => false;
|
public override bool HandleKeyboardInput => false;
|
||||||
public override bool HandleMouseInput => false;
|
public override bool HandleMouseInput => false;
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(NotificationOverlay notification, OsuColour colours, TextureStore textures, OsuGameBase game, OsuConfigManager config)
|
private void load(NotificationOverlay notification, OsuColour colours, TextureStore textures, OsuGameBase game, OsuConfigManager config, GameHost host)
|
||||||
{
|
{
|
||||||
notificationOverlay = notification;
|
notificationOverlay = notification;
|
||||||
this.config = config;
|
this.config = config;
|
||||||
this.game = game;
|
this.game = game;
|
||||||
|
this.host = host;
|
||||||
|
|
||||||
AutoSizeAxes = Axes.Both;
|
AutoSizeAxes = Axes.Both;
|
||||||
Anchor = Anchor.BottomCentre;
|
Anchor = Anchor.BottomCentre;
|
||||||
@ -106,19 +109,19 @@ namespace osu.Desktop.Overlays
|
|||||||
|
|
||||||
// only show a notification if we've previously saved a version to the config file (ie. not the first run).
|
// only show a notification if we've previously saved a version to the config file (ie. not the first run).
|
||||||
if (!string.IsNullOrEmpty(lastVersion))
|
if (!string.IsNullOrEmpty(lastVersion))
|
||||||
notificationOverlay.Post(new UpdateCompleteNotification(version));
|
notificationOverlay.Post(new UpdateCompleteNotification(version, host.OpenUrlExternally));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class UpdateCompleteNotification : SimpleNotification
|
private class UpdateCompleteNotification : SimpleNotification
|
||||||
{
|
{
|
||||||
public UpdateCompleteNotification(string version)
|
public UpdateCompleteNotification(string version, Action<string> openUrl = null)
|
||||||
{
|
{
|
||||||
Text = $"You are now running osu!lazer {version}.\nClick to see what's new!";
|
Text = $"You are now running osu!lazer {version}.\nClick to see what's new!";
|
||||||
Icon = FontAwesome.fa_check_square;
|
Icon = FontAwesome.fa_check_square;
|
||||||
Activated = delegate
|
Activated = delegate
|
||||||
{
|
{
|
||||||
Process.Start($"https://osu.ppy.sh/home/changelog/{version}");
|
openUrl?.Invoke($"https://osu.ppy.sh/home/changelog/lazer/{version}");
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -30,6 +30,7 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup Label="Package References">
|
<ItemGroup Label="Package References">
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.0" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.0" />
|
||||||
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="2.1.1" />
|
||||||
<PackageReference Include="squirrel.windows" Version="1.8.0" Condition="'$(TargetFramework)' == 'net471'" />
|
<PackageReference Include="squirrel.windows" Version="1.8.0" Condition="'$(TargetFramework)' == 'net471'" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup Label="Resources">
|
<ItemGroup Label="Resources">
|
||||||
|
@ -1,16 +0,0 @@
|
|||||||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
|
||||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
|
||||||
|
|
||||||
using NUnit.Framework;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Tests
|
|
||||||
{
|
|
||||||
[TestFixture]
|
|
||||||
public class TestCasePerformancePoints : Game.Tests.Visual.TestCasePerformancePoints
|
|
||||||
{
|
|
||||||
public TestCasePerformancePoints()
|
|
||||||
: base(new CatchRuleset())
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -112,7 +112,7 @@ namespace osu.Game.Rulesets.Catch
|
|||||||
|
|
||||||
public override Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.fa_osu_fruits_o };
|
public override Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.fa_osu_fruits_o };
|
||||||
|
|
||||||
public override DifficultyCalculator CreateDifficultyCalculator(IBeatmap beatmap, Mod[] mods = null) => new CatchDifficultyCalculator(beatmap);
|
public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new CatchDifficultyCalculator(this, beatmap);
|
||||||
|
|
||||||
public override int? LegacyID => 2;
|
public override int? LegacyID => 2;
|
||||||
|
|
||||||
|
@ -0,0 +1,19 @@
|
|||||||
|
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||||
|
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||||
|
|
||||||
|
using osu.Game.Rulesets.Difficulty;
|
||||||
|
using osu.Game.Rulesets.Mods;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Catch.Difficulty
|
||||||
|
{
|
||||||
|
public class CatchDifficultyAttributes : DifficultyAttributes
|
||||||
|
{
|
||||||
|
public double ApproachRate;
|
||||||
|
public int MaxCombo;
|
||||||
|
|
||||||
|
public CatchDifficultyAttributes(Mod[] mods, double starRating)
|
||||||
|
: base(mods, starRating)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,18 +1,146 @@
|
|||||||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||||
|
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Rulesets.Difficulty;
|
using osu.Game.Rulesets.Difficulty;
|
||||||
|
using osu.Game.Rulesets.Mods;
|
||||||
|
using osu.Game.Rulesets.Catch.Objects;
|
||||||
|
using osu.Game.Rulesets.Catch.UI;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Difficulty
|
namespace osu.Game.Rulesets.Catch.Difficulty
|
||||||
{
|
{
|
||||||
public class CatchDifficultyCalculator : DifficultyCalculator
|
public class CatchDifficultyCalculator : DifficultyCalculator
|
||||||
{
|
{
|
||||||
public CatchDifficultyCalculator(IBeatmap beatmap) : base(beatmap)
|
|
||||||
|
/// <summary>
|
||||||
|
/// In milliseconds. For difficulty calculation we will only look at the highest strain value in each time interval of size STRAIN_STEP.
|
||||||
|
/// This is to eliminate higher influence of stream over aim by simply having more HitObjects with high strain.
|
||||||
|
/// The higher this value, the less strains there will be, indirectly giving long beatmaps an advantage.
|
||||||
|
/// </summary>
|
||||||
|
private const double strain_step = 750;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The weighting of each strain value decays to this number * it's previous value
|
||||||
|
/// </summary>
|
||||||
|
private const double decay_weight = 0.94;
|
||||||
|
|
||||||
|
private const double star_scaling_factor = 0.145;
|
||||||
|
|
||||||
|
public CatchDifficultyCalculator(Ruleset ruleset, WorkingBeatmap beatmap)
|
||||||
|
: base(ruleset, beatmap)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public override double Calculate(Dictionary<string, double> categoryDifficulty = null) => 0;
|
protected override DifficultyAttributes Calculate(IBeatmap beatmap, Mod[] mods, double timeRate)
|
||||||
|
{
|
||||||
|
if (!beatmap.HitObjects.Any())
|
||||||
|
return new CatchDifficultyAttributes(mods, 0);
|
||||||
|
|
||||||
|
var catcher = new CatcherArea.Catcher(beatmap.BeatmapInfo.BaseDifficulty);
|
||||||
|
float halfCatchWidth = catcher.CatchWidth * 0.5f;
|
||||||
|
|
||||||
|
var difficultyHitObjects = new List<CatchDifficultyHitObject>();
|
||||||
|
|
||||||
|
foreach (var hitObject in beatmap.HitObjects)
|
||||||
|
{
|
||||||
|
// We want to only consider fruits that contribute to the combo. Droplets are addressed as accuracy and spinners are not relevant for "skill" calculations.
|
||||||
|
if (hitObject is Fruit)
|
||||||
|
{
|
||||||
|
difficultyHitObjects.Add(new CatchDifficultyHitObject((CatchHitObject)hitObject, halfCatchWidth));
|
||||||
|
}
|
||||||
|
if (hitObject is JuiceStream)
|
||||||
|
difficultyHitObjects.AddRange(hitObject.NestedHitObjects.OfType<CatchHitObject>().Where(o => !(o is TinyDroplet)).Select(o => new CatchDifficultyHitObject(o, halfCatchWidth)));
|
||||||
|
}
|
||||||
|
|
||||||
|
difficultyHitObjects.Sort((a, b) => a.BaseHitObject.StartTime.CompareTo(b.BaseHitObject.StartTime));
|
||||||
|
|
||||||
|
if (!calculateStrainValues(difficultyHitObjects, timeRate))
|
||||||
|
return new CatchDifficultyAttributes(mods, 0);
|
||||||
|
|
||||||
|
// this is the same as osu!, so there's potential to share the implementation... maybe
|
||||||
|
double preEmpt = BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.ApproachRate, 1800, 1200, 450) / timeRate;
|
||||||
|
double starRating = Math.Sqrt(calculateDifficulty(difficultyHitObjects, timeRate)) * star_scaling_factor;
|
||||||
|
|
||||||
|
return new CatchDifficultyAttributes(mods, starRating)
|
||||||
|
{
|
||||||
|
ApproachRate = preEmpt > 1200.0 ? -(preEmpt - 1800.0) / 120.0 : -(preEmpt - 1200.0) / 150.0 + 5.0,
|
||||||
|
MaxCombo = difficultyHitObjects.Count
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool calculateStrainValues(List<CatchDifficultyHitObject> objects, double timeRate)
|
||||||
|
{
|
||||||
|
CatchDifficultyHitObject lastObject = null;
|
||||||
|
|
||||||
|
if (!objects.Any()) return false;
|
||||||
|
|
||||||
|
// Traverse hitObjects in pairs to calculate the strain value of NextHitObject from the strain value of CurrentHitObject and environment.
|
||||||
|
foreach (var currentObject in objects)
|
||||||
|
{
|
||||||
|
if (lastObject != null)
|
||||||
|
currentObject.CalculateStrains(lastObject, timeRate);
|
||||||
|
|
||||||
|
lastObject = currentObject;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private double calculateDifficulty(List<CatchDifficultyHitObject> objects, double timeRate)
|
||||||
|
{
|
||||||
|
// The strain step needs to be adjusted for the algorithm to be considered equal with speed changing mods
|
||||||
|
double actualStrainStep = strain_step * timeRate;
|
||||||
|
|
||||||
|
// Find the highest strain value within each strain step
|
||||||
|
var highestStrains = new List<double>();
|
||||||
|
double intervalEndTime = actualStrainStep;
|
||||||
|
double maximumStrain = 0; // We need to keep track of the maximum strain in the current interval
|
||||||
|
|
||||||
|
CatchDifficultyHitObject previousHitObject = null;
|
||||||
|
foreach (CatchDifficultyHitObject hitObject in objects)
|
||||||
|
{
|
||||||
|
// While we are beyond the current interval push the currently available maximum to our strain list
|
||||||
|
while (hitObject.BaseHitObject.StartTime > intervalEndTime)
|
||||||
|
{
|
||||||
|
highestStrains.Add(maximumStrain);
|
||||||
|
|
||||||
|
// The maximum strain of the next interval is not zero by default! We need to take the last hitObject we encountered, take its strain and apply the decay
|
||||||
|
// until the beginning of the next interval.
|
||||||
|
if (previousHitObject == null)
|
||||||
|
{
|
||||||
|
maximumStrain = 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
double decay = Math.Pow(CatchDifficultyHitObject.DECAY_BASE, (intervalEndTime - previousHitObject.BaseHitObject.StartTime) / 1000);
|
||||||
|
maximumStrain = previousHitObject.Strain * decay;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Go to the next time interval
|
||||||
|
intervalEndTime += actualStrainStep;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Obtain maximum strain
|
||||||
|
maximumStrain = Math.Max(hitObject.Strain, maximumStrain);
|
||||||
|
|
||||||
|
previousHitObject = hitObject;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build the weighted sum over the highest strains for each interval
|
||||||
|
double difficulty = 0;
|
||||||
|
double weight = 1;
|
||||||
|
highestStrains.Sort((a, b) => b.CompareTo(a)); // Sort from highest to lowest strain.
|
||||||
|
|
||||||
|
foreach (double strain in highestStrains)
|
||||||
|
{
|
||||||
|
difficulty += weight * strain;
|
||||||
|
weight *= decay_weight;
|
||||||
|
}
|
||||||
|
|
||||||
|
return difficulty;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
130
osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyHitObject.cs
Normal file
130
osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyHitObject.cs
Normal file
@ -0,0 +1,130 @@
|
|||||||
|
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||||
|
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using osu.Game.Rulesets.Catch.Objects;
|
||||||
|
using osu.Game.Rulesets.Catch.UI;
|
||||||
|
using OpenTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Catch.Difficulty
|
||||||
|
{
|
||||||
|
public class CatchDifficultyHitObject
|
||||||
|
{
|
||||||
|
internal static readonly double DECAY_BASE = 0.20;
|
||||||
|
private const float normalized_hitobject_radius = 41.0f;
|
||||||
|
private const float absolute_player_positioning_error = 16f;
|
||||||
|
private readonly float playerPositioningError;
|
||||||
|
|
||||||
|
internal CatchHitObject BaseHitObject;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Measures jump difficulty. CtB doesn't have something like button pressing speed or accuracy
|
||||||
|
/// </summary>
|
||||||
|
internal double Strain = 1;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This is required to keep track of lazy player movement (always moving only as far as necessary)
|
||||||
|
/// Without this quick repeat sliders / weirdly shaped streams might become ridiculously overrated
|
||||||
|
/// </summary>
|
||||||
|
internal float PlayerPositionOffset;
|
||||||
|
internal float LastMovement;
|
||||||
|
|
||||||
|
internal float NormalizedPosition;
|
||||||
|
internal float ActualNormalizedPosition => NormalizedPosition + PlayerPositionOffset;
|
||||||
|
|
||||||
|
internal CatchDifficultyHitObject(CatchHitObject baseHitObject, float catcherWidthHalf)
|
||||||
|
{
|
||||||
|
BaseHitObject = baseHitObject;
|
||||||
|
|
||||||
|
// We will scale everything by this factor, so we can assume a uniform CircleSize among beatmaps.
|
||||||
|
float scalingFactor = normalized_hitobject_radius / catcherWidthHalf;
|
||||||
|
|
||||||
|
playerPositioningError = absolute_player_positioning_error; // * scalingFactor;
|
||||||
|
NormalizedPosition = baseHitObject.X * CatchPlayfield.BASE_WIDTH * scalingFactor;
|
||||||
|
}
|
||||||
|
|
||||||
|
private const double direction_change_bonus = 12.5;
|
||||||
|
internal void CalculateStrains(CatchDifficultyHitObject previousHitObject, double timeRate)
|
||||||
|
{
|
||||||
|
// Rather simple, but more specialized things are inherently inaccurate due to the big difference playstyles and opinions make.
|
||||||
|
// See Taiko feedback thread.
|
||||||
|
double timeElapsed = (BaseHitObject.StartTime - previousHitObject.BaseHitObject.StartTime) / timeRate;
|
||||||
|
double decay = Math.Pow(DECAY_BASE, timeElapsed / 1000);
|
||||||
|
|
||||||
|
// Update new position with lazy movement.
|
||||||
|
PlayerPositionOffset =
|
||||||
|
MathHelper.Clamp(
|
||||||
|
previousHitObject.ActualNormalizedPosition,
|
||||||
|
NormalizedPosition - (normalized_hitobject_radius - playerPositioningError),
|
||||||
|
NormalizedPosition + (normalized_hitobject_radius - playerPositioningError)) // Obtain new lazy position, but be stricter by allowing for an error of a certain degree of the player.
|
||||||
|
- NormalizedPosition; // Subtract HitObject position to obtain offset
|
||||||
|
|
||||||
|
LastMovement = DistanceTo(previousHitObject);
|
||||||
|
double addition = spacingWeight(LastMovement);
|
||||||
|
|
||||||
|
if (NormalizedPosition < previousHitObject.NormalizedPosition)
|
||||||
|
{
|
||||||
|
LastMovement = -LastMovement;
|
||||||
|
}
|
||||||
|
|
||||||
|
CatchHitObject previousHitCircle = previousHitObject.BaseHitObject;
|
||||||
|
|
||||||
|
double additionBonus = 0;
|
||||||
|
double sqrtTime = Math.Sqrt(Math.Max(timeElapsed, 25));
|
||||||
|
|
||||||
|
// Direction changes give an extra point!
|
||||||
|
if (Math.Abs(LastMovement) > 0.1)
|
||||||
|
{
|
||||||
|
if (Math.Abs(previousHitObject.LastMovement) > 0.1 && Math.Sign(LastMovement) != Math.Sign(previousHitObject.LastMovement))
|
||||||
|
{
|
||||||
|
double bonus = direction_change_bonus / sqrtTime;
|
||||||
|
|
||||||
|
// Weight bonus by how
|
||||||
|
double bonusFactor = Math.Min(playerPositioningError, Math.Abs(LastMovement)) / playerPositioningError;
|
||||||
|
|
||||||
|
// We want time to play a role twice here!
|
||||||
|
addition += bonus * bonusFactor;
|
||||||
|
|
||||||
|
// Bonus for tougher direction switches and "almost" hyperdashes at this point
|
||||||
|
if (previousHitCircle != null && previousHitCircle.DistanceToHyperDash <= 10.0f / CatchPlayfield.BASE_WIDTH)
|
||||||
|
{
|
||||||
|
additionBonus += 0.3 * bonusFactor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Base bonus for every movement, giving some weight to streams.
|
||||||
|
addition += 7.5 * Math.Min(Math.Abs(LastMovement), normalized_hitobject_radius * 2) / (normalized_hitobject_radius * 6) / sqrtTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bonus for "almost" hyperdashes at corner points
|
||||||
|
if (previousHitCircle != null && previousHitCircle.DistanceToHyperDash <= 10.0f / CatchPlayfield.BASE_WIDTH)
|
||||||
|
{
|
||||||
|
if (!previousHitCircle.HyperDash)
|
||||||
|
{
|
||||||
|
additionBonus += 1.0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// After a hyperdash we ARE in the correct position. Always!
|
||||||
|
PlayerPositionOffset = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
addition *= 1.0 + additionBonus * ((10 - previousHitCircle.DistanceToHyperDash * CatchPlayfield.BASE_WIDTH) / 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
addition *= 850.0 / Math.Max(timeElapsed, 25);
|
||||||
|
|
||||||
|
Strain = previousHitObject.Strain * decay + addition;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static double spacingWeight(float distance)
|
||||||
|
{
|
||||||
|
return Math.Pow(distance, 1.3) / 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal float DistanceTo(CatchDifficultyHitObject other)
|
||||||
|
{
|
||||||
|
return Math.Abs(ActualNormalizedPosition - other.ActualNormalizedPosition);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -24,6 +24,11 @@ namespace osu.Game.Rulesets.Catch.Objects
|
|||||||
|
|
||||||
public int ComboIndex { get; set; }
|
public int ComboIndex { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The distance for a fruit to to next hyper if it's not a hyper.
|
||||||
|
/// </summary>
|
||||||
|
public float DistanceToHyperDash { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The next fruit starts a new combo. Used for explodey.
|
/// The next fruit starts a new combo. Used for explodey.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -42,6 +42,8 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
|
|||||||
{
|
{
|
||||||
public virtual bool CanBePlated => false;
|
public virtual bool CanBePlated => false;
|
||||||
|
|
||||||
|
public virtual bool StaysOnPlate => CanBePlated;
|
||||||
|
|
||||||
protected DrawableCatchHitObject(CatchHitObject hitObject)
|
protected DrawableCatchHitObject(CatchHitObject hitObject)
|
||||||
: base(hitObject)
|
: base(hitObject)
|
||||||
{
|
{
|
||||||
|
@ -13,6 +13,8 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
|
|||||||
{
|
{
|
||||||
private Pulp pulp;
|
private Pulp pulp;
|
||||||
|
|
||||||
|
public override bool StaysOnPlate => false;
|
||||||
|
|
||||||
public DrawableDroplet(Droplet h)
|
public DrawableDroplet(Droplet h)
|
||||||
: base(h)
|
: base(h)
|
||||||
{
|
{
|
||||||
|
@ -124,6 +124,9 @@ namespace osu.Game.Rulesets.Catch.Objects
|
|||||||
X = X + Curve.PositionAt(reversed ? 0 : 1).X / CatchPlayfield.BASE_WIDTH
|
X = X + Curve.PositionAt(reversed ? 0 : 1).X / CatchPlayfield.BASE_WIDTH
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (NestedHitObjects.LastOrDefault() is IHasComboInformation lastNested)
|
||||||
|
lastNested.LastInCombo = LastInCombo;
|
||||||
}
|
}
|
||||||
|
|
||||||
public double EndTime => StartTime + this.SpanCount() * Curve.Distance / Velocity;
|
public double EndTime => StartTime + this.SpanCount() * Curve.Distance / Velocity;
|
||||||
|
@ -28,9 +28,9 @@ namespace osu.Game.Rulesets.Catch.Replays
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override List<InputState> GetPendingStates()
|
public override List<IInput> GetPendingInputs()
|
||||||
{
|
{
|
||||||
if (!Position.HasValue) return new List<InputState>();
|
if (!Position.HasValue) return new List<IInput>();
|
||||||
|
|
||||||
var actions = new List<CatchAction>();
|
var actions = new List<CatchAction>();
|
||||||
|
|
||||||
@ -42,7 +42,7 @@ namespace osu.Game.Rulesets.Catch.Replays
|
|||||||
else if (Position.Value < CurrentFrame.Position)
|
else if (Position.Value < CurrentFrame.Position)
|
||||||
actions.Add(CatchAction.MoveLeft);
|
actions.Add(CatchAction.MoveLeft);
|
||||||
|
|
||||||
return new List<InputState>
|
return new List<IInput>
|
||||||
{
|
{
|
||||||
new CatchReplayState
|
new CatchReplayState
|
||||||
{
|
{
|
||||||
|
@ -16,6 +16,7 @@ using osu.Game.Rulesets.Catch.Objects.Drawable;
|
|||||||
using osu.Game.Rulesets.Catch.Replays;
|
using osu.Game.Rulesets.Catch.Replays;
|
||||||
using osu.Game.Rulesets.Judgements;
|
using osu.Game.Rulesets.Judgements;
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
|
using osu.Game.Rulesets.UI;
|
||||||
using OpenTK;
|
using OpenTK;
|
||||||
using OpenTK.Graphics;
|
using OpenTK.Graphics;
|
||||||
|
|
||||||
@ -48,6 +49,16 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
|
|
||||||
public void OnJudgement(DrawableCatchHitObject fruit, Judgement judgement)
|
public void OnJudgement(DrawableCatchHitObject fruit, Judgement judgement)
|
||||||
{
|
{
|
||||||
|
void runAfterLoaded(Action action)
|
||||||
|
{
|
||||||
|
// this is required to make this run after the last caught fruit runs UpdateState at least once.
|
||||||
|
// TODO: find a better alternative
|
||||||
|
if (lastPlateableFruit.IsLoaded)
|
||||||
|
action();
|
||||||
|
else
|
||||||
|
lastPlateableFruit.OnLoadComplete = _ => action();
|
||||||
|
}
|
||||||
|
|
||||||
if (judgement.IsHit && fruit.CanBePlated)
|
if (judgement.IsHit && fruit.CanBePlated)
|
||||||
{
|
{
|
||||||
var caughtFruit = (DrawableCatchHitObject)GetVisualRepresentation?.Invoke(fruit.HitObject);
|
var caughtFruit = (DrawableCatchHitObject)GetVisualRepresentation?.Invoke(fruit.HitObject);
|
||||||
@ -63,21 +74,17 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
caughtFruit.LifetimeEnd = double.MaxValue;
|
caughtFruit.LifetimeEnd = double.MaxValue;
|
||||||
|
|
||||||
MovableCatcher.Add(caughtFruit);
|
MovableCatcher.Add(caughtFruit);
|
||||||
|
|
||||||
lastPlateableFruit = caughtFruit;
|
lastPlateableFruit = caughtFruit;
|
||||||
|
|
||||||
|
if (!fruit.StaysOnPlate)
|
||||||
|
runAfterLoaded(() => MovableCatcher.Explode(caughtFruit));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fruit.HitObject.LastInCombo)
|
if (fruit.HitObject.LastInCombo)
|
||||||
{
|
{
|
||||||
if (judgement.IsHit)
|
if (judgement.IsHit)
|
||||||
{
|
runAfterLoaded(() => MovableCatcher.Explode());
|
||||||
// this is required to make this run after the last caught fruit runs UpdateState at least once.
|
|
||||||
// TODO: find a better alternative
|
|
||||||
if (lastPlateableFruit.IsLoaded)
|
|
||||||
MovableCatcher.Explode();
|
|
||||||
else
|
|
||||||
lastPlateableFruit.OnLoadComplete = _ => { MovableCatcher.Explode(); };
|
|
||||||
}
|
|
||||||
else
|
else
|
||||||
MovableCatcher.Drop();
|
MovableCatcher.Drop();
|
||||||
}
|
}
|
||||||
@ -87,7 +94,7 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
{
|
{
|
||||||
base.UpdateAfterChildren();
|
base.UpdateAfterChildren();
|
||||||
|
|
||||||
var state = GetContainingInputManager().CurrentState as CatchFramedReplayInputHandler.CatchReplayState;
|
var state = (GetContainingInputManager().CurrentState as RulesetInputManagerInputState<CatchAction>)?.LastReplayState as CatchFramedReplayInputHandler.CatchReplayState;
|
||||||
|
|
||||||
if (state?.CatcherX != null)
|
if (state?.CatcherX != null)
|
||||||
MovableCatcher.X = state.CatcherX.Value;
|
MovableCatcher.X = state.CatcherX.Value;
|
||||||
@ -99,6 +106,11 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
|
|
||||||
public class Catcher : Container, IKeyBindingHandler<CatchAction>
|
public class Catcher : Container, IKeyBindingHandler<CatchAction>
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Width of the area that can be used to attempt catches during gameplay.
|
||||||
|
/// </summary>
|
||||||
|
internal float CatchWidth => CATCHER_SIZE * Math.Abs(Scale.X);
|
||||||
|
|
||||||
private Container<DrawableHitObject> caughtFruit;
|
private Container<DrawableHitObject> caughtFruit;
|
||||||
|
|
||||||
public Container ExplodingFruitTarget;
|
public Container ExplodingFruitTarget;
|
||||||
@ -226,15 +238,15 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
/// <returns>Whether the catch is possible.</returns>
|
/// <returns>Whether the catch is possible.</returns>
|
||||||
public bool AttemptCatch(CatchHitObject fruit)
|
public bool AttemptCatch(CatchHitObject fruit)
|
||||||
{
|
{
|
||||||
double halfCatcherWidth = CATCHER_SIZE * Math.Abs(Scale.X) * 0.5f;
|
float halfCatchWidth = CatchWidth * 0.5f;
|
||||||
|
|
||||||
// this stuff wil disappear once we move fruit to non-relative coordinate space in the future.
|
// this stuff wil disappear once we move fruit to non-relative coordinate space in the future.
|
||||||
var catchObjectPosition = fruit.X * CatchPlayfield.BASE_WIDTH;
|
var catchObjectPosition = fruit.X * CatchPlayfield.BASE_WIDTH;
|
||||||
var catcherPosition = Position.X * CatchPlayfield.BASE_WIDTH;
|
var catcherPosition = Position.X * CatchPlayfield.BASE_WIDTH;
|
||||||
|
|
||||||
var validCatch =
|
var validCatch =
|
||||||
catchObjectPosition >= catcherPosition - halfCatcherWidth &&
|
catchObjectPosition >= catcherPosition - halfCatchWidth &&
|
||||||
catchObjectPosition <= catcherPosition + halfCatcherWidth;
|
catchObjectPosition <= catcherPosition + halfCatchWidth;
|
||||||
|
|
||||||
if (validCatch && fruit.HyperDash)
|
if (validCatch && fruit.HyperDash)
|
||||||
{
|
{
|
||||||
@ -378,28 +390,31 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
var fruit = caughtFruit.ToArray();
|
var fruit = caughtFruit.ToArray();
|
||||||
|
|
||||||
foreach (var f in fruit)
|
foreach (var f in fruit)
|
||||||
|
Explode(f);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Explode(DrawableHitObject fruit)
|
||||||
|
{
|
||||||
|
var originalX = fruit.X * Scale.X;
|
||||||
|
|
||||||
|
if (ExplodingFruitTarget != null)
|
||||||
{
|
{
|
||||||
var originalX = f.X * Scale.X;
|
fruit.Anchor = Anchor.TopLeft;
|
||||||
|
fruit.Position = caughtFruit.ToSpaceOfOtherDrawable(fruit.DrawPosition, ExplodingFruitTarget);
|
||||||
|
|
||||||
if (ExplodingFruitTarget != null)
|
caughtFruit.Remove(fruit);
|
||||||
{
|
|
||||||
f.Anchor = Anchor.TopLeft;
|
|
||||||
f.Position = caughtFruit.ToSpaceOfOtherDrawable(f.DrawPosition, ExplodingFruitTarget);
|
|
||||||
|
|
||||||
caughtFruit.Remove(f);
|
ExplodingFruitTarget.Add(fruit);
|
||||||
|
|
||||||
ExplodingFruitTarget.Add(f);
|
|
||||||
}
|
|
||||||
|
|
||||||
f.MoveToY(f.Y - 50, 250, Easing.OutSine)
|
|
||||||
.Then()
|
|
||||||
.MoveToY(f.Y + 50, 500, Easing.InSine);
|
|
||||||
|
|
||||||
f.MoveToX(f.X + originalX * 6, 1000);
|
|
||||||
f.FadeOut(750);
|
|
||||||
|
|
||||||
f.Expire();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fruit.MoveToY(fruit.Y - 50, 250, Easing.OutSine)
|
||||||
|
.Then()
|
||||||
|
.MoveToY(fruit.Y + 50, 500, Easing.InSine);
|
||||||
|
|
||||||
|
fruit.MoveToX(fruit.X + originalX * 6, 1000);
|
||||||
|
fruit.FadeOut(750);
|
||||||
|
|
||||||
|
fruit.Expire();
|
||||||
}
|
}
|
||||||
|
|
||||||
private class CatcherSprite : Sprite
|
private class CatcherSprite : Sprite
|
||||||
|
@ -1,16 +0,0 @@
|
|||||||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
|
||||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
|
||||||
|
|
||||||
using NUnit.Framework;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.Tests
|
|
||||||
{
|
|
||||||
[TestFixture]
|
|
||||||
public class TestCasePerformancePoints : Game.Tests.Visual.TestCasePerformancePoints
|
|
||||||
{
|
|
||||||
public TestCasePerformancePoints()
|
|
||||||
: base(new ManiaRuleset())
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -58,6 +58,13 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
|||||||
|
|
||||||
public override Pattern Generate()
|
public override Pattern Generate()
|
||||||
{
|
{
|
||||||
|
if (TotalColumns == 1)
|
||||||
|
{
|
||||||
|
var pattern = new Pattern();
|
||||||
|
addToPattern(pattern, 0, HitObject.StartTime, endTime);
|
||||||
|
return pattern;
|
||||||
|
}
|
||||||
|
|
||||||
if (spanCount > 1)
|
if (spanCount > 1)
|
||||||
{
|
{
|
||||||
if (segmentDuration <= 90)
|
if (segmentDuration <= 90)
|
||||||
@ -322,7 +329,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool isDoubleSample(SampleInfo sample) => sample.Name == SampleInfo.HIT_CLAP && sample.Name == SampleInfo.HIT_FINISH;
|
bool isDoubleSample(SampleInfo sample) => sample.Name == SampleInfo.HIT_CLAP || sample.Name == SampleInfo.HIT_FINISH;
|
||||||
|
|
||||||
bool canGenerateTwoNotes = (convertType & PatternType.LowProbability) == 0;
|
bool canGenerateTwoNotes = (convertType & PatternType.LowProbability) == 0;
|
||||||
canGenerateTwoNotes &= HitObject.Samples.Any(isDoubleSample) || sampleInfoListAt(HitObject.StartTime).Any(isDoubleSample);
|
canGenerateTwoNotes &= HitObject.Samples.Any(isDoubleSample) || sampleInfoListAt(HitObject.StartTime).Any(isDoubleSample);
|
||||||
|
@ -77,10 +77,25 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
convertType |= PatternType.LowProbability;
|
convertType |= PatternType.LowProbability;
|
||||||
|
|
||||||
|
if ((convertType & PatternType.KeepSingle) == 0)
|
||||||
|
{
|
||||||
|
if (HitObject.Samples.Any(s => s.Name == SampleInfo.HIT_FINISH) && TotalColumns != 8)
|
||||||
|
convertType |= PatternType.Mirror;
|
||||||
|
else
|
||||||
|
convertType |= PatternType.Gathered;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override Pattern Generate()
|
public override Pattern Generate()
|
||||||
{
|
{
|
||||||
|
if (TotalColumns == 1)
|
||||||
|
{
|
||||||
|
var pattern = new Pattern();
|
||||||
|
addToPattern(pattern, 0);
|
||||||
|
return pattern;
|
||||||
|
}
|
||||||
|
|
||||||
int lastColumn = PreviousPattern.HitObjects.FirstOrDefault()?.Column ?? 0;
|
int lastColumn = PreviousPattern.HitObjects.FirstOrDefault()?.Column ?? 0;
|
||||||
|
|
||||||
if ((convertType & PatternType.Reverse) > 0 && PreviousPattern.HitObjects.Any())
|
if ((convertType & PatternType.Reverse) > 0 && PreviousPattern.HitObjects.Any())
|
||||||
@ -346,7 +361,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
|||||||
addToCentre = false;
|
addToCentre = false;
|
||||||
|
|
||||||
if ((convertType & PatternType.ForceNotStack) > 0)
|
if ((convertType & PatternType.ForceNotStack) > 0)
|
||||||
return getRandomNoteCount(p2 / 2, p2, (p2 + p3) / 2, p3);
|
return getRandomNoteCount(1 / 2f + p2 / 2, p2, (p2 + p3) / 2, p3);
|
||||||
|
|
||||||
switch (TotalColumns)
|
switch (TotalColumns)
|
||||||
{
|
{
|
||||||
|
@ -9,7 +9,7 @@ namespace osu.Game.Rulesets.Mania.Configuration
|
|||||||
{
|
{
|
||||||
public class ManiaConfigManager : RulesetConfigManager<ManiaSetting>
|
public class ManiaConfigManager : RulesetConfigManager<ManiaSetting>
|
||||||
{
|
{
|
||||||
public ManiaConfigManager(SettingsStore settings, RulesetInfo ruleset, int variant)
|
public ManiaConfigManager(SettingsStore settings, RulesetInfo ruleset, int? variant = null)
|
||||||
: base(settings, ruleset, variant)
|
: base(settings, ruleset, variant)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
@ -29,47 +29,36 @@ namespace osu.Game.Rulesets.Mania.Difficulty
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private const double decay_weight = 0.9;
|
private const double decay_weight = 0.9;
|
||||||
|
|
||||||
/// <summary>
|
private readonly bool isForCurrentRuleset;
|
||||||
/// HitObjects are stored as a member variable.
|
|
||||||
/// </summary>
|
|
||||||
private readonly List<ManiaHitObjectDifficulty> difficultyHitObjects = new List<ManiaHitObjectDifficulty>();
|
|
||||||
|
|
||||||
public ManiaDifficultyCalculator(IBeatmap beatmap)
|
public ManiaDifficultyCalculator(Ruleset ruleset, WorkingBeatmap beatmap)
|
||||||
: base(beatmap)
|
: base(ruleset, beatmap)
|
||||||
{
|
{
|
||||||
|
isForCurrentRuleset = beatmap.BeatmapInfo.Ruleset.Equals(ruleset.RulesetInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ManiaDifficultyCalculator(IBeatmap beatmap, Mod[] mods)
|
protected override DifficultyAttributes Calculate(IBeatmap beatmap, Mod[] mods, double timeRate)
|
||||||
: base(beatmap, mods)
|
|
||||||
{
|
{
|
||||||
}
|
var difficultyHitObjects = new List<ManiaHitObjectDifficulty>();
|
||||||
|
|
||||||
public override double Calculate(Dictionary<string, double> categoryDifficulty = null)
|
int columnCount = ((ManiaBeatmap)beatmap).TotalColumns;
|
||||||
{
|
|
||||||
// Fill our custom DifficultyHitObject class, that carries additional information
|
|
||||||
difficultyHitObjects.Clear();
|
|
||||||
|
|
||||||
int columnCount = (Beatmap as ManiaBeatmap)?.TotalColumns ?? 7;
|
|
||||||
|
|
||||||
// Sort DifficultyHitObjects by StartTime of the HitObjects - just to make sure.
|
// Sort DifficultyHitObjects by StartTime of the HitObjects - just to make sure.
|
||||||
// Note: Stable sort is done so that the ordering of hitobjects with equal start times doesn't change
|
// Note: Stable sort is done so that the ordering of hitobjects with equal start times doesn't change
|
||||||
difficultyHitObjects.AddRange(Beatmap.HitObjects.Select(h => new ManiaHitObjectDifficulty((ManiaHitObject)h, columnCount)).OrderBy(h => h.BaseHitObject.StartTime));
|
difficultyHitObjects.AddRange(beatmap.HitObjects.Select(h => new ManiaHitObjectDifficulty((ManiaHitObject)h, columnCount)).OrderBy(h => h.BaseHitObject.StartTime));
|
||||||
|
|
||||||
if (!calculateStrainValues())
|
if (!calculateStrainValues(difficultyHitObjects, timeRate))
|
||||||
return 0;
|
return new DifficultyAttributes(mods, 0);
|
||||||
|
|
||||||
double starRating = calculateDifficulty() * star_scaling_factor;
|
double starRating = calculateDifficulty(difficultyHitObjects, timeRate) * star_scaling_factor;
|
||||||
|
|
||||||
if (categoryDifficulty != null)
|
return new DifficultyAttributes(mods, starRating);
|
||||||
categoryDifficulty["Strain"] = starRating;
|
|
||||||
|
|
||||||
return starRating;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool calculateStrainValues()
|
private bool calculateStrainValues(List<ManiaHitObjectDifficulty> objects, double timeRate)
|
||||||
{
|
{
|
||||||
// Traverse hitObjects in pairs to calculate the strain value of NextHitObject from the strain value of CurrentHitObject and environment.
|
// Traverse hitObjects in pairs to calculate the strain value of NextHitObject from the strain value of CurrentHitObject and environment.
|
||||||
using (List<ManiaHitObjectDifficulty>.Enumerator hitObjectsEnumerator = difficultyHitObjects.GetEnumerator())
|
using (var hitObjectsEnumerator = objects.GetEnumerator())
|
||||||
{
|
{
|
||||||
if (!hitObjectsEnumerator.MoveNext())
|
if (!hitObjectsEnumerator.MoveNext())
|
||||||
return false;
|
return false;
|
||||||
@ -80,7 +69,7 @@ namespace osu.Game.Rulesets.Mania.Difficulty
|
|||||||
while (hitObjectsEnumerator.MoveNext())
|
while (hitObjectsEnumerator.MoveNext())
|
||||||
{
|
{
|
||||||
var next = hitObjectsEnumerator.Current;
|
var next = hitObjectsEnumerator.Current;
|
||||||
next?.CalculateStrains(current, TimeRate);
|
next?.CalculateStrains(current, timeRate);
|
||||||
current = next;
|
current = next;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -88,9 +77,9 @@ namespace osu.Game.Rulesets.Mania.Difficulty
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private double calculateDifficulty()
|
private double calculateDifficulty(List<ManiaHitObjectDifficulty> objects, double timeRate)
|
||||||
{
|
{
|
||||||
double actualStrainStep = strain_step * TimeRate;
|
double actualStrainStep = strain_step * timeRate;
|
||||||
|
|
||||||
// Find the highest strain value within each strain step
|
// Find the highest strain value within each strain step
|
||||||
List<double> highestStrains = new List<double>();
|
List<double> highestStrains = new List<double>();
|
||||||
@ -98,7 +87,7 @@ namespace osu.Game.Rulesets.Mania.Difficulty
|
|||||||
double maximumStrain = 0; // We need to keep track of the maximum strain in the current interval
|
double maximumStrain = 0; // We need to keep track of the maximum strain in the current interval
|
||||||
|
|
||||||
ManiaHitObjectDifficulty previousHitObject = null;
|
ManiaHitObjectDifficulty previousHitObject = null;
|
||||||
foreach (var hitObject in difficultyHitObjects)
|
foreach (var hitObject in objects)
|
||||||
{
|
{
|
||||||
// While we are beyond the current interval push the currently available maximum to our strain list
|
// While we are beyond the current interval push the currently available maximum to our strain list
|
||||||
while (hitObject.BaseHitObject.StartTime > intervalEndTime)
|
while (hitObject.BaseHitObject.StartTime > intervalEndTime)
|
||||||
@ -143,21 +132,35 @@ namespace osu.Game.Rulesets.Mania.Difficulty
|
|||||||
return difficulty;
|
return difficulty;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override Mod[] DifficultyAdjustmentMods => new Mod[]
|
protected override Mod[] DifficultyAdjustmentMods
|
||||||
{
|
{
|
||||||
new ManiaModDoubleTime(),
|
get
|
||||||
new ManiaModHalfTime(),
|
{
|
||||||
new ManiaModEasy(),
|
var mods = new Mod[]
|
||||||
new ManiaModHardRock(),
|
{
|
||||||
new ManiaModKey1(),
|
new ManiaModDoubleTime(),
|
||||||
new ManiaModKey2(),
|
new ManiaModHalfTime(),
|
||||||
new ManiaModKey3(),
|
new ManiaModEasy(),
|
||||||
new ManiaModKey4(),
|
new ManiaModHardRock(),
|
||||||
new ManiaModKey5(),
|
};
|
||||||
new ManiaModKey6(),
|
|
||||||
new ManiaModKey7(),
|
if (isForCurrentRuleset)
|
||||||
new ManiaModKey8(),
|
return mods;
|
||||||
new ManiaModKey9(),
|
|
||||||
};
|
// if we are a convert, we can be played in any key mod.
|
||||||
|
return mods.Concat(new Mod[]
|
||||||
|
{
|
||||||
|
new ManiaModKey1(),
|
||||||
|
new ManiaModKey2(),
|
||||||
|
new ManiaModKey3(),
|
||||||
|
new ManiaModKey4(),
|
||||||
|
new ManiaModKey5(),
|
||||||
|
new ManiaModKey6(),
|
||||||
|
new ManiaModKey7(),
|
||||||
|
new ManiaModKey8(),
|
||||||
|
new ManiaModKey9(),
|
||||||
|
}).ToArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -25,7 +25,7 @@ namespace osu.Game.Rulesets.Mania.Difficulty
|
|||||||
private int countMeh;
|
private int countMeh;
|
||||||
private int countMiss;
|
private int countMiss;
|
||||||
|
|
||||||
public ManiaPerformanceCalculator(Ruleset ruleset, IBeatmap beatmap, Score score)
|
public ManiaPerformanceCalculator(Ruleset ruleset, WorkingBeatmap beatmap, Score score)
|
||||||
: base(ruleset, beatmap, score)
|
: base(ruleset, beatmap, score)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
@ -82,7 +82,7 @@ namespace osu.Game.Rulesets.Mania.Difficulty
|
|||||||
private double computeStrainValue()
|
private double computeStrainValue()
|
||||||
{
|
{
|
||||||
// Obtain strain difficulty
|
// Obtain strain difficulty
|
||||||
double strainValue = Math.Pow(5 * Math.Max(1, Attributes["Strain"] / 0.2) - 4.0, 2.2) / 135.0;
|
double strainValue = Math.Pow(5 * Math.Max(1, Attributes.StarRating / 0.2) - 4.0, 2.2) / 135.0;
|
||||||
|
|
||||||
// Longer maps are worth more
|
// Longer maps are worth more
|
||||||
strainValue *= 1.0 + 0.1 * Math.Min(1.0, totalHits / 1500.0);
|
strainValue *= 1.0 + 0.1 * Math.Min(1.0, totalHits / 1500.0);
|
||||||
|
@ -15,8 +15,11 @@ using osu.Game.Graphics;
|
|||||||
using osu.Game.Rulesets.Mania.Replays;
|
using osu.Game.Rulesets.Mania.Replays;
|
||||||
using osu.Game.Rulesets.Replays.Types;
|
using osu.Game.Rulesets.Replays.Types;
|
||||||
using osu.Game.Beatmaps.Legacy;
|
using osu.Game.Beatmaps.Legacy;
|
||||||
|
using osu.Game.Configuration;
|
||||||
|
using osu.Game.Rulesets.Configuration;
|
||||||
using osu.Game.Rulesets.Difficulty;
|
using osu.Game.Rulesets.Difficulty;
|
||||||
using osu.Game.Rulesets.Mania.Beatmaps;
|
using osu.Game.Rulesets.Mania.Beatmaps;
|
||||||
|
using osu.Game.Rulesets.Mania.Configuration;
|
||||||
using osu.Game.Rulesets.Mania.Difficulty;
|
using osu.Game.Rulesets.Mania.Difficulty;
|
||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
|
|
||||||
@ -26,7 +29,7 @@ namespace osu.Game.Rulesets.Mania
|
|||||||
{
|
{
|
||||||
public override RulesetContainer CreateRulesetContainerWith(WorkingBeatmap beatmap) => new ManiaRulesetContainer(this, beatmap);
|
public override RulesetContainer CreateRulesetContainerWith(WorkingBeatmap beatmap) => new ManiaRulesetContainer(this, beatmap);
|
||||||
public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new ManiaBeatmapConverter(beatmap);
|
public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new ManiaBeatmapConverter(beatmap);
|
||||||
public override PerformanceCalculator CreatePerformanceCalculator(IBeatmap beatmap, Score score) => new ManiaPerformanceCalculator(this, beatmap, score);
|
public override PerformanceCalculator CreatePerformanceCalculator(WorkingBeatmap beatmap, Score score) => new ManiaPerformanceCalculator(this, beatmap, score);
|
||||||
|
|
||||||
public override IEnumerable<Mod> ConvertLegacyMods(LegacyMods mods)
|
public override IEnumerable<Mod> ConvertLegacyMods(LegacyMods mods)
|
||||||
{
|
{
|
||||||
@ -144,12 +147,14 @@ namespace osu.Game.Rulesets.Mania
|
|||||||
|
|
||||||
public override Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.fa_osu_mania_o };
|
public override Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.fa_osu_mania_o };
|
||||||
|
|
||||||
public override DifficultyCalculator CreateDifficultyCalculator(IBeatmap beatmap, Mod[] mods = null) => new ManiaDifficultyCalculator(beatmap, mods);
|
public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new ManiaDifficultyCalculator(this, beatmap);
|
||||||
|
|
||||||
public override int? LegacyID => 3;
|
public override int? LegacyID => 3;
|
||||||
|
|
||||||
public override IConvertibleReplayFrame CreateConvertibleReplayFrame() => new ManiaReplayFrame();
|
public override IConvertibleReplayFrame CreateConvertibleReplayFrame() => new ManiaReplayFrame();
|
||||||
|
|
||||||
|
public override IRulesetConfigManager CreateConfig(SettingsStore settings) => new ManiaConfigManager(settings, RulesetInfo);
|
||||||
|
|
||||||
public ManiaRuleset(RulesetInfo rulesetInfo = null)
|
public ManiaRuleset(RulesetInfo rulesetInfo = null)
|
||||||
: base(rulesetInfo)
|
: base(rulesetInfo)
|
||||||
{
|
{
|
||||||
|
@ -17,6 +17,6 @@ namespace osu.Game.Rulesets.Mania.Replays
|
|||||||
|
|
||||||
protected override bool IsImportant(ManiaReplayFrame frame) => frame.Actions.Any();
|
protected override bool IsImportant(ManiaReplayFrame frame) => frame.Actions.Any();
|
||||||
|
|
||||||
public override List<InputState> GetPendingStates() => new List<InputState> { new ReplayState<ManiaAction> { PressedActions = CurrentFrame.Actions } };
|
public override List<IInput> GetPendingInputs() => new List<IInput> { new ReplayState<ManiaAction> { PressedActions = CurrentFrame.Actions } };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,8 @@ namespace osu.Game.Rulesets.Mania.UI
|
|||||||
{
|
{
|
||||||
internal class HitExplosion : CompositeDrawable
|
internal class HitExplosion : CompositeDrawable
|
||||||
{
|
{
|
||||||
|
public override bool RemoveWhenNotAlive => true;
|
||||||
|
|
||||||
private readonly CircularContainer circle;
|
private readonly CircularContainer circle;
|
||||||
|
|
||||||
public HitExplosion(DrawableHitObject judgedObject)
|
public HitExplosion(DrawableHitObject judgedObject)
|
||||||
|
@ -10,12 +10,9 @@ using osu.Framework.Input;
|
|||||||
using osu.Framework.MathUtils;
|
using osu.Framework.MathUtils;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Beatmaps.ControlPoints;
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
using osu.Game.Configuration;
|
|
||||||
using osu.Game.Input.Handlers;
|
using osu.Game.Input.Handlers;
|
||||||
using osu.Game.Rulesets.Configuration;
|
|
||||||
using osu.Game.Rulesets.Mania.Beatmaps;
|
using osu.Game.Rulesets.Mania.Beatmaps;
|
||||||
using osu.Game.Rulesets.Mania.Mods;
|
using osu.Game.Rulesets.Mania.Mods;
|
||||||
using osu.Game.Rulesets.Mania.Configuration;
|
|
||||||
using osu.Game.Rulesets.Mania.Objects;
|
using osu.Game.Rulesets.Mania.Objects;
|
||||||
using osu.Game.Rulesets.Mania.Objects.Drawables;
|
using osu.Game.Rulesets.Mania.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.Mania.Replays;
|
using osu.Game.Rulesets.Mania.Replays;
|
||||||
@ -103,7 +100,5 @@ namespace osu.Game.Rulesets.Mania.UI
|
|||||||
protected override Vector2 PlayfieldArea => new Vector2(1, 0.8f);
|
protected override Vector2 PlayfieldArea => new Vector2(1, 0.8f);
|
||||||
|
|
||||||
protected override ReplayInputHandler CreateReplayInputHandler(Replay replay) => new ManiaFramedReplayInputHandler(replay);
|
protected override ReplayInputHandler CreateReplayInputHandler(Replay replay) => new ManiaFramedReplayInputHandler(replay);
|
||||||
|
|
||||||
protected override IRulesetConfigManager CreateConfig(Ruleset ruleset, SettingsStore settings) => new ManiaConfigManager(settings, Ruleset.RulesetInfo, Variant);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,16 +0,0 @@
|
|||||||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
|
||||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
|
||||||
|
|
||||||
using NUnit.Framework;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Tests
|
|
||||||
{
|
|
||||||
[TestFixture]
|
|
||||||
public class TestCasePerformancePoints : Game.Tests.Visual.TestCasePerformancePoints
|
|
||||||
{
|
|
||||||
public TestCasePerformancePoints()
|
|
||||||
: base(new OsuRuleset())
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
19
osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs
Normal file
19
osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||||
|
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||||
|
|
||||||
|
using osu.Game.Rulesets.Difficulty;
|
||||||
|
using osu.Game.Rulesets.Mods;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Difficulty
|
||||||
|
{
|
||||||
|
public class OsuDifficultyAttributes : DifficultyAttributes
|
||||||
|
{
|
||||||
|
public double AimStrain;
|
||||||
|
public double SpeedStrain;
|
||||||
|
|
||||||
|
public OsuDifficultyAttributes(Mod[] mods, double starRating)
|
||||||
|
: base(mods, starRating)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -2,7 +2,7 @@
|
|||||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Linq;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Rulesets.Difficulty;
|
using osu.Game.Rulesets.Difficulty;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
@ -18,31 +18,26 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
|||||||
private const int section_length = 400;
|
private const int section_length = 400;
|
||||||
private const double difficulty_multiplier = 0.0675;
|
private const double difficulty_multiplier = 0.0675;
|
||||||
|
|
||||||
public OsuDifficultyCalculator(IBeatmap beatmap)
|
public OsuDifficultyCalculator(Ruleset ruleset, WorkingBeatmap beatmap)
|
||||||
: base(beatmap)
|
: base(ruleset, beatmap)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public OsuDifficultyCalculator(IBeatmap beatmap, Mod[] mods)
|
protected override DifficultyAttributes Calculate(IBeatmap beatmap, Mod[] mods, double timeRate)
|
||||||
: base(beatmap, mods)
|
|
||||||
{
|
{
|
||||||
}
|
OsuDifficultyBeatmap difficultyBeatmap = new OsuDifficultyBeatmap(beatmap.HitObjects.Cast<OsuHitObject>().ToList(), timeRate);
|
||||||
|
|
||||||
public override double Calculate(Dictionary<string, double> categoryDifficulty = null)
|
|
||||||
{
|
|
||||||
OsuDifficultyBeatmap beatmap = new OsuDifficultyBeatmap((List<OsuHitObject>)Beatmap.HitObjects, TimeRate);
|
|
||||||
Skill[] skills =
|
Skill[] skills =
|
||||||
{
|
{
|
||||||
new Aim(),
|
new Aim(),
|
||||||
new Speed()
|
new Speed()
|
||||||
};
|
};
|
||||||
|
|
||||||
double sectionLength = section_length * TimeRate;
|
double sectionLength = section_length * timeRate;
|
||||||
|
|
||||||
// The first object doesn't generate a strain, so we begin with an incremented section end
|
// The first object doesn't generate a strain, so we begin with an incremented section end
|
||||||
double currentSectionEnd = 2 * sectionLength;
|
double currentSectionEnd = 2 * sectionLength;
|
||||||
|
|
||||||
foreach (OsuDifficultyHitObject h in beatmap)
|
foreach (OsuDifficultyHitObject h in difficultyBeatmap)
|
||||||
{
|
{
|
||||||
while (h.BaseObject.StartTime > currentSectionEnd)
|
while (h.BaseObject.StartTime > currentSectionEnd)
|
||||||
{
|
{
|
||||||
@ -61,16 +56,13 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
|||||||
|
|
||||||
double aimRating = Math.Sqrt(skills[0].DifficultyValue()) * difficulty_multiplier;
|
double aimRating = Math.Sqrt(skills[0].DifficultyValue()) * difficulty_multiplier;
|
||||||
double speedRating = Math.Sqrt(skills[1].DifficultyValue()) * difficulty_multiplier;
|
double speedRating = Math.Sqrt(skills[1].DifficultyValue()) * difficulty_multiplier;
|
||||||
|
|
||||||
double starRating = aimRating + speedRating + Math.Abs(aimRating - speedRating) / 2;
|
double starRating = aimRating + speedRating + Math.Abs(aimRating - speedRating) / 2;
|
||||||
|
|
||||||
if (categoryDifficulty != null)
|
return new OsuDifficultyAttributes(mods, starRating)
|
||||||
{
|
{
|
||||||
categoryDifficulty.Add("Aim", aimRating);
|
AimStrain = aimRating,
|
||||||
categoryDifficulty.Add("Speed", speedRating);
|
SpeedStrain = speedRating
|
||||||
}
|
};
|
||||||
|
|
||||||
return starRating;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override Mod[] DifficultyAdjustmentMods => new Mod[]
|
protected override Mod[] DifficultyAdjustmentMods => new Mod[]
|
||||||
|
@ -15,6 +15,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
|||||||
{
|
{
|
||||||
public class OsuPerformanceCalculator : PerformanceCalculator
|
public class OsuPerformanceCalculator : PerformanceCalculator
|
||||||
{
|
{
|
||||||
|
public new OsuDifficultyAttributes Attributes => (OsuDifficultyAttributes)base.Attributes;
|
||||||
|
|
||||||
private readonly int countHitCircles;
|
private readonly int countHitCircles;
|
||||||
private readonly int beatmapMaxCombo;
|
private readonly int beatmapMaxCombo;
|
||||||
|
|
||||||
@ -37,7 +39,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
|||||||
private int countMeh;
|
private int countMeh;
|
||||||
private int countMiss;
|
private int countMiss;
|
||||||
|
|
||||||
public OsuPerformanceCalculator(Ruleset ruleset, IBeatmap beatmap, Score score)
|
public OsuPerformanceCalculator(Ruleset ruleset, WorkingBeatmap beatmap, Score score)
|
||||||
: base(ruleset, beatmap, score)
|
: base(ruleset, beatmap, score)
|
||||||
{
|
{
|
||||||
countHitCircles = Beatmap.HitObjects.Count(h => h is HitCircle);
|
countHitCircles = Beatmap.HitObjects.Count(h => h is HitCircle);
|
||||||
@ -102,7 +104,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
|||||||
|
|
||||||
private double computeAimValue()
|
private double computeAimValue()
|
||||||
{
|
{
|
||||||
double aimValue = Math.Pow(5.0f * Math.Max(1.0f, Attributes["Aim"] / 0.0675f) - 4.0f, 3.0f) / 100000.0f;
|
double aimValue = Math.Pow(5.0f * Math.Max(1.0f, Attributes.AimStrain / 0.0675f) - 4.0f, 3.0f) / 100000.0f;
|
||||||
|
|
||||||
// Longer maps are worth more
|
// Longer maps are worth more
|
||||||
double lengthBonus = 0.95f + 0.4f * Math.Min(1.0f, totalHits / 2000.0f) +
|
double lengthBonus = 0.95f + 0.4f * Math.Min(1.0f, totalHits / 2000.0f) +
|
||||||
@ -151,7 +153,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
|||||||
|
|
||||||
private double computeSpeedValue()
|
private double computeSpeedValue()
|
||||||
{
|
{
|
||||||
double speedValue = Math.Pow(5.0f * Math.Max(1.0f, Attributes["Speed"] / 0.0675f) - 4.0f, 3.0f) / 100000.0f;
|
double speedValue = Math.Pow(5.0f * Math.Max(1.0f, Attributes.SpeedStrain / 0.0675f) - 4.0f, 3.0f) / 100000.0f;
|
||||||
|
|
||||||
// Longer maps are worth more
|
// Longer maps are worth more
|
||||||
speedValue *= 0.95f + 0.4f * Math.Min(1.0f, totalHits / 2000.0f) +
|
speedValue *= 0.95f + 0.4f * Math.Min(1.0f, totalHits / 2000.0f) +
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.MathUtils;
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
using OpenTK;
|
using OpenTK;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
@ -89,7 +90,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
// find the next vector2 in the curve which is not equal to our current position to infer a rotation.
|
// find the next vector2 in the curve which is not equal to our current position to infer a rotation.
|
||||||
for (int i = searchStart; i >= 0 && i < curve.Count; i += direction)
|
for (int i = searchStart; i >= 0 && i < curve.Count; i += direction)
|
||||||
{
|
{
|
||||||
if (curve[i] == Position)
|
if (Precision.AlmostEquals(curve[i], Position))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
Rotation = MathHelper.RadiansToDegrees((float)Math.Atan2(curve[i].Y - Position.Y, curve[i].X - Position.X));
|
Rotation = MathHelper.RadiansToDegrees((float)Math.Atan2(curve[i].Y - Position.Y, curve[i].X - Position.X));
|
||||||
|
@ -139,8 +139,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
|
|||||||
var texture = new Texture(textureWidth, 1);
|
var texture = new Texture(textureWidth, 1);
|
||||||
|
|
||||||
//initialise background
|
//initialise background
|
||||||
var upload = new TextureUpload(textureWidth * 4);
|
var raw = new RawTexture(textureWidth, 1);
|
||||||
var bytes = upload.Data;
|
var bytes = raw.Data;
|
||||||
|
|
||||||
const float aa_portion = 0.02f;
|
const float aa_portion = 0.02f;
|
||||||
const float border_portion = 0.128f;
|
const float border_portion = 0.128f;
|
||||||
@ -171,7 +171,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
texture.SetData(upload);
|
texture.SetData(new TextureUpload(raw));
|
||||||
path.Texture = texture;
|
path.Texture = texture;
|
||||||
|
|
||||||
container.ForceRedraw();
|
container.ForceRedraw();
|
||||||
|
@ -120,9 +120,9 @@ namespace osu.Game.Rulesets.Osu
|
|||||||
|
|
||||||
public override Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.fa_osu_osu_o };
|
public override Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.fa_osu_osu_o };
|
||||||
|
|
||||||
public override DifficultyCalculator CreateDifficultyCalculator(IBeatmap beatmap, Mod[] mods = null) => new OsuDifficultyCalculator(beatmap, mods);
|
public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new OsuDifficultyCalculator(this, beatmap);
|
||||||
|
|
||||||
public override PerformanceCalculator CreatePerformanceCalculator(IBeatmap beatmap, Score score) => new OsuPerformanceCalculator(this, beatmap, score);
|
public override PerformanceCalculator CreatePerformanceCalculator(WorkingBeatmap beatmap, Score score) => new OsuPerformanceCalculator(this, beatmap, score);
|
||||||
|
|
||||||
public override HitObjectComposer CreateHitObjectComposer() => new OsuHitObjectComposer(this);
|
public override HitObjectComposer CreateHitObjectComposer() => new OsuHitObjectComposer(this);
|
||||||
|
|
||||||
@ -130,7 +130,7 @@ namespace osu.Game.Rulesets.Osu
|
|||||||
|
|
||||||
public override string ShortName => "osu";
|
public override string ShortName => "osu";
|
||||||
|
|
||||||
public override SettingsSubsection CreateSettings() => new OsuSettings();
|
public override RulesetSettingsSubsection CreateSettings() => new OsuSettings(this);
|
||||||
|
|
||||||
public override int? LegacyID => 0;
|
public override int? LegacyID => 0;
|
||||||
|
|
||||||
|
@ -30,13 +30,16 @@ namespace osu.Game.Rulesets.Osu.Replays
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override List<InputState> GetPendingStates()
|
public override List<IInput> GetPendingInputs()
|
||||||
{
|
{
|
||||||
return new List<InputState>
|
return new List<IInput>
|
||||||
{
|
{
|
||||||
|
new MousePositionAbsoluteInput
|
||||||
|
{
|
||||||
|
Position = GamefieldToScreenSpace(Position ?? Vector2.Zero)
|
||||||
|
},
|
||||||
new ReplayState<OsuAction>
|
new ReplayState<OsuAction>
|
||||||
{
|
{
|
||||||
Mouse = new ReplayMouseState(GamefieldToScreenSpace(Position ?? Vector2.Zero)),
|
|
||||||
PressedActions = CurrentFrame.Actions
|
PressedActions = CurrentFrame.Actions
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -161,7 +161,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
|
|||||||
};
|
};
|
||||||
|
|
||||||
this.beatmap.BindTo(beatmap);
|
this.beatmap.BindTo(beatmap);
|
||||||
beatmap.ValueChanged += v => calculateScale();
|
this.beatmap.ValueChanged += v => calculateScale();
|
||||||
|
|
||||||
cursorScale = config.GetBindable<double>(OsuSetting.GameplayCursorSize);
|
cursorScale = config.GetBindable<double>(OsuSetting.GameplayCursorSize);
|
||||||
cursorScale.ValueChanged += v => calculateScale();
|
cursorScale.ValueChanged += v => calculateScale();
|
||||||
|
@ -18,7 +18,7 @@ using osu.Game.Rulesets.Replays;
|
|||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.UI
|
namespace osu.Game.Rulesets.Osu.UI
|
||||||
{
|
{
|
||||||
public class OsuRulesetContainer : RulesetContainer<OsuHitObject>
|
public class OsuRulesetContainer : RulesetContainer<OsuPlayfield, OsuHitObject>
|
||||||
{
|
{
|
||||||
public OsuRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap)
|
public OsuRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap)
|
||||||
: base(ruleset, beatmap)
|
: base(ruleset, beatmap)
|
||||||
|
@ -8,10 +8,15 @@ using osu.Game.Overlays.Settings;
|
|||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.UI
|
namespace osu.Game.Rulesets.Osu.UI
|
||||||
{
|
{
|
||||||
public class OsuSettings : SettingsSubsection
|
public class OsuSettings : RulesetSettingsSubsection
|
||||||
{
|
{
|
||||||
protected override string Header => "osu!";
|
protected override string Header => "osu!";
|
||||||
|
|
||||||
|
public OsuSettings(Ruleset ruleset)
|
||||||
|
: base(ruleset)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(OsuConfigManager config)
|
private void load(OsuConfigManager config)
|
||||||
{
|
{
|
||||||
|
@ -1,16 +0,0 @@
|
|||||||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
|
||||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
|
||||||
|
|
||||||
using NUnit.Framework;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Taiko.Tests
|
|
||||||
{
|
|
||||||
[TestFixture]
|
|
||||||
public class TestCasePerformancePoints : Game.Tests.Visual.TestCasePerformancePoints
|
|
||||||
{
|
|
||||||
public TestCasePerformancePoints()
|
|
||||||
: base(new TaikoRuleset())
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -27,54 +27,33 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private const double decay_weight = 0.9;
|
private const double decay_weight = 0.9;
|
||||||
|
|
||||||
/// <summary>
|
public TaikoDifficultyCalculator(Ruleset ruleset, WorkingBeatmap beatmap)
|
||||||
/// HitObjects are stored as a member variable.
|
: base(ruleset, beatmap)
|
||||||
/// </summary>
|
|
||||||
private readonly List<TaikoHitObjectDifficulty> difficultyHitObjects = new List<TaikoHitObjectDifficulty>();
|
|
||||||
|
|
||||||
public TaikoDifficultyCalculator(IBeatmap beatmap)
|
|
||||||
: base(beatmap)
|
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public TaikoDifficultyCalculator(IBeatmap beatmap, Mod[] mods)
|
protected override DifficultyAttributes Calculate(IBeatmap beatmap, Mod[] mods, double timeRate)
|
||||||
: base(beatmap, mods)
|
|
||||||
{
|
{
|
||||||
}
|
var difficultyHitObjects = new List<TaikoHitObjectDifficulty>();
|
||||||
|
|
||||||
public override double Calculate(Dictionary<string, double> categoryDifficulty = null)
|
foreach (var hitObject in beatmap.HitObjects)
|
||||||
{
|
|
||||||
// Fill our custom DifficultyHitObject class, that carries additional information
|
|
||||||
difficultyHitObjects.Clear();
|
|
||||||
|
|
||||||
foreach (var hitObject in Beatmap.HitObjects)
|
|
||||||
difficultyHitObjects.Add(new TaikoHitObjectDifficulty((TaikoHitObject)hitObject));
|
difficultyHitObjects.Add(new TaikoHitObjectDifficulty((TaikoHitObject)hitObject));
|
||||||
|
|
||||||
// Sort DifficultyHitObjects by StartTime of the HitObjects - just to make sure.
|
// Sort DifficultyHitObjects by StartTime of the HitObjects - just to make sure.
|
||||||
difficultyHitObjects.Sort((a, b) => a.BaseHitObject.StartTime.CompareTo(b.BaseHitObject.StartTime));
|
difficultyHitObjects.Sort((a, b) => a.BaseHitObject.StartTime.CompareTo(b.BaseHitObject.StartTime));
|
||||||
|
|
||||||
if (!calculateStrainValues()) return 0;
|
if (!calculateStrainValues(difficultyHitObjects, timeRate))
|
||||||
|
return new DifficultyAttributes(mods, 0);
|
||||||
|
|
||||||
double starRating = calculateDifficulty() * star_scaling_factor;
|
double starRating = calculateDifficulty(difficultyHitObjects, timeRate) * star_scaling_factor;
|
||||||
|
|
||||||
if (categoryDifficulty != null)
|
return new DifficultyAttributes(mods, starRating);
|
||||||
categoryDifficulty["Strain"] = starRating;
|
|
||||||
|
|
||||||
return starRating;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override Mod[] DifficultyAdjustmentMods => new Mod[]
|
private bool calculateStrainValues(List<TaikoHitObjectDifficulty> objects, double timeRate)
|
||||||
{
|
|
||||||
new TaikoModDoubleTime(),
|
|
||||||
new TaikoModHalfTime(),
|
|
||||||
new TaikoModEasy(),
|
|
||||||
new TaikoModHardRock(),
|
|
||||||
};
|
|
||||||
|
|
||||||
private bool calculateStrainValues()
|
|
||||||
{
|
{
|
||||||
// Traverse hitObjects in pairs to calculate the strain value of NextHitObject from the strain value of CurrentHitObject and environment.
|
// Traverse hitObjects in pairs to calculate the strain value of NextHitObject from the strain value of CurrentHitObject and environment.
|
||||||
using (List<TaikoHitObjectDifficulty>.Enumerator hitObjectsEnumerator = difficultyHitObjects.GetEnumerator())
|
using (var hitObjectsEnumerator = objects.GetEnumerator())
|
||||||
{
|
{
|
||||||
if (!hitObjectsEnumerator.MoveNext()) return false;
|
if (!hitObjectsEnumerator.MoveNext()) return false;
|
||||||
|
|
||||||
@ -84,7 +63,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
|
|||||||
while (hitObjectsEnumerator.MoveNext())
|
while (hitObjectsEnumerator.MoveNext())
|
||||||
{
|
{
|
||||||
var next = hitObjectsEnumerator.Current;
|
var next = hitObjectsEnumerator.Current;
|
||||||
next?.CalculateStrains(current, TimeRate);
|
next?.CalculateStrains(current, timeRate);
|
||||||
current = next;
|
current = next;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -92,9 +71,9 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private double calculateDifficulty()
|
private double calculateDifficulty(List<TaikoHitObjectDifficulty> objects, double timeRate)
|
||||||
{
|
{
|
||||||
double actualStrainStep = strain_step * TimeRate;
|
double actualStrainStep = strain_step * timeRate;
|
||||||
|
|
||||||
// Find the highest strain value within each strain step
|
// Find the highest strain value within each strain step
|
||||||
List<double> highestStrains = new List<double>();
|
List<double> highestStrains = new List<double>();
|
||||||
@ -102,7 +81,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
|
|||||||
double maximumStrain = 0; // We need to keep track of the maximum strain in the current interval
|
double maximumStrain = 0; // We need to keep track of the maximum strain in the current interval
|
||||||
|
|
||||||
TaikoHitObjectDifficulty previousHitObject = null;
|
TaikoHitObjectDifficulty previousHitObject = null;
|
||||||
foreach (var hitObject in difficultyHitObjects)
|
foreach (var hitObject in objects)
|
||||||
{
|
{
|
||||||
// While we are beyond the current interval push the currently available maximum to our strain list
|
// While we are beyond the current interval push the currently available maximum to our strain list
|
||||||
while (hitObject.BaseHitObject.StartTime > intervalEndTime)
|
while (hitObject.BaseHitObject.StartTime > intervalEndTime)
|
||||||
@ -144,5 +123,13 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
|
|||||||
|
|
||||||
return difficulty;
|
return difficulty;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override Mod[] DifficultyAdjustmentMods => new Mod[]
|
||||||
|
{
|
||||||
|
new TaikoModDoubleTime(),
|
||||||
|
new TaikoModHalfTime(),
|
||||||
|
new TaikoModEasy(),
|
||||||
|
new TaikoModHardRock(),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -22,10 +22,10 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
|
|||||||
private int countMeh;
|
private int countMeh;
|
||||||
private int countMiss;
|
private int countMiss;
|
||||||
|
|
||||||
public TaikoPerformanceCalculator(Ruleset ruleset, IBeatmap beatmap, Score score)
|
public TaikoPerformanceCalculator(Ruleset ruleset, WorkingBeatmap beatmap, Score score)
|
||||||
: base(ruleset, beatmap, score)
|
: base(ruleset, beatmap, score)
|
||||||
{
|
{
|
||||||
beatmapMaxCombo = beatmap.HitObjects.Count(h => h is Hit);
|
beatmapMaxCombo = Beatmap.HitObjects.Count(h => h is Hit);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override double Calculate(Dictionary<string, double> categoryDifficulty = null)
|
public override double Calculate(Dictionary<string, double> categoryDifficulty = null)
|
||||||
@ -68,7 +68,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
|
|||||||
|
|
||||||
private double computeStrainValue()
|
private double computeStrainValue()
|
||||||
{
|
{
|
||||||
double strainValue = Math.Pow(5.0 * Math.Max(1.0, Attributes["Strain"] / 0.0075) - 4.0, 2.0) / 100000.0;
|
double strainValue = Math.Pow(5.0 * Math.Max(1.0, Attributes.StarRating / 0.0075) - 4.0, 2.0) / 100000.0;
|
||||||
|
|
||||||
// Longer maps are worth more
|
// Longer maps are worth more
|
||||||
double lengthBonus = 1 + 0.1f * Math.Min(1.0, totalHits / 1500.0);
|
double lengthBonus = 1 + 0.1f * Math.Min(1.0, totalHits / 1500.0);
|
||||||
|
@ -89,6 +89,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
|||||||
SecondHitAllowed = false;
|
SecondHitAllowed = false;
|
||||||
validKeyPressed = false;
|
validKeyPressed = false;
|
||||||
|
|
||||||
|
UnproxyContent();
|
||||||
this.Delay(HitObject.HitWindows.HalfWindowFor(HitResult.Miss)).Expire();
|
this.Delay(HitObject.HitWindows.HalfWindowFor(HitResult.Miss)).Expire();
|
||||||
break;
|
break;
|
||||||
case ArmedState.Miss:
|
case ArmedState.Miss:
|
||||||
@ -96,6 +97,10 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
|||||||
.Expire();
|
.Expire();
|
||||||
break;
|
break;
|
||||||
case ArmedState.Hit:
|
case ArmedState.Hit:
|
||||||
|
// If we're far enough away from the left stage, we should bring outselves in front of it
|
||||||
|
if (X >= -0.05f)
|
||||||
|
ProxyContent();
|
||||||
|
|
||||||
var flash = circlePiece?.FlashBox;
|
var flash = circlePiece?.FlashBox;
|
||||||
if (flash != null)
|
if (flash != null)
|
||||||
{
|
{
|
||||||
|
@ -19,12 +19,6 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
|||||||
{
|
{
|
||||||
public class DrawableSwell : DrawableTaikoHitObject<Swell>
|
public class DrawableSwell : DrawableTaikoHitObject<Swell>
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// Invoked when the swell has reached the hit target, i.e. when CurrentTime >= StartTime.
|
|
||||||
/// This is only ever invoked once.
|
|
||||||
/// </summary>
|
|
||||||
public event Action OnStart;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A judgement is only displayed when the user has complete the swell (either a hit or miss).
|
/// A judgement is only displayed when the user has complete the swell (either a hit or miss).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -40,6 +34,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
|||||||
private readonly CircularContainer expandingRing;
|
private readonly CircularContainer expandingRing;
|
||||||
|
|
||||||
private bool hasStarted;
|
private bool hasStarted;
|
||||||
|
|
||||||
private readonly SwellSymbolPiece symbol;
|
private readonly SwellSymbolPiece symbol;
|
||||||
|
|
||||||
public DrawableSwell(Swell swell)
|
public DrawableSwell(Swell swell)
|
||||||
@ -47,7 +42,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
|||||||
{
|
{
|
||||||
FillMode = FillMode.Fit;
|
FillMode = FillMode.Fit;
|
||||||
|
|
||||||
AddInternal(bodyContainer = new Container
|
Content.Add(bodyContainer = new Container
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Depth = 1,
|
Depth = 1,
|
||||||
@ -171,6 +166,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
|||||||
switch (state)
|
switch (state)
|
||||||
{
|
{
|
||||||
case ArmedState.Idle:
|
case ArmedState.Idle:
|
||||||
|
UnproxyContent();
|
||||||
expandingRing.FadeTo(0);
|
expandingRing.FadeTo(0);
|
||||||
using (BeginAbsoluteSequence(HitObject.StartTime - preempt, true))
|
using (BeginAbsoluteSequence(HitObject.StartTime - preempt, true))
|
||||||
targetRing.ScaleTo(target_ring_scale, preempt * 4, Easing.OutQuint);
|
targetRing.ScaleTo(target_ring_scale, preempt * 4, Easing.OutQuint);
|
||||||
@ -195,11 +191,8 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
|||||||
X = Math.Max(0, X);
|
X = Math.Max(0, X);
|
||||||
|
|
||||||
double t = Math.Min(HitObject.StartTime, Time.Current);
|
double t = Math.Min(HitObject.StartTime, Time.Current);
|
||||||
if (t == HitObject.StartTime && !hasStarted)
|
if (t == HitObject.StartTime)
|
||||||
{
|
ProxyContent();
|
||||||
OnStart?.Invoke();
|
|
||||||
hasStarted = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool? lastWasCentre;
|
private bool? lastWasCentre;
|
||||||
|
@ -9,10 +9,75 @@ using OpenTK;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Game.Audio;
|
using osu.Game.Audio;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Primitives;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
||||||
{
|
{
|
||||||
public abstract class DrawableTaikoHitObject<TaikoHitType> : DrawableHitObject<TaikoHitObject>, IKeyBindingHandler<TaikoAction>
|
public abstract class DrawableTaikoHitObject : DrawableHitObject<TaikoHitObject>, IKeyBindingHandler<TaikoAction>
|
||||||
|
{
|
||||||
|
protected readonly Container Content;
|
||||||
|
private readonly Container proxiedContent;
|
||||||
|
|
||||||
|
private readonly Container nonProxiedContent;
|
||||||
|
|
||||||
|
protected DrawableTaikoHitObject(TaikoHitObject hitObject)
|
||||||
|
: base(hitObject)
|
||||||
|
{
|
||||||
|
InternalChildren = new[]
|
||||||
|
{
|
||||||
|
nonProxiedContent = new Container
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Child = Content = new Container { RelativeSizeAxes = Axes.Both }
|
||||||
|
},
|
||||||
|
proxiedContent = new Container { RelativeSizeAxes = Axes.Both }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// <see cref="proxiedContent"/> is proxied into an upper layer. We don't want to get masked away otherwise <see cref="proxiedContent"/> would too.
|
||||||
|
/// </summary>
|
||||||
|
protected override bool ComputeIsMaskedAway(RectangleF maskingBounds) => false;
|
||||||
|
|
||||||
|
private bool isProxied;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Moves <see cref="Content"/> to a layer proxied above the playfield.
|
||||||
|
/// Does nothing is content is already proxied.
|
||||||
|
/// </summary>
|
||||||
|
protected void ProxyContent()
|
||||||
|
{
|
||||||
|
if (isProxied) return;
|
||||||
|
isProxied = true;
|
||||||
|
|
||||||
|
nonProxiedContent.Remove(Content);
|
||||||
|
proxiedContent.Add(Content);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Moves <see cref="Content"/> to the normal hitobject layer.
|
||||||
|
/// Does nothing is content is not currently proxied.
|
||||||
|
/// </summary>
|
||||||
|
protected void UnproxyContent()
|
||||||
|
{
|
||||||
|
if (!isProxied) return;
|
||||||
|
isProxied = false;
|
||||||
|
|
||||||
|
proxiedContent.Remove(Content);
|
||||||
|
nonProxiedContent.Add(Content);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a proxy for the content of this <see cref="DrawableTaikoHitObject"/>.
|
||||||
|
/// </summary>
|
||||||
|
public Drawable CreateProxiedContent() => proxiedContent.CreateProxy();
|
||||||
|
|
||||||
|
public abstract bool OnPressed(TaikoAction action);
|
||||||
|
public virtual bool OnReleased(TaikoAction action) => false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract class DrawableTaikoHitObject<TaikoHitType> : DrawableTaikoHitObject
|
||||||
where TaikoHitType : TaikoHitObject
|
where TaikoHitType : TaikoHitObject
|
||||||
{
|
{
|
||||||
public override Vector2 OriginPosition => new Vector2(DrawHeight / 2);
|
public override Vector2 OriginPosition => new Vector2(DrawHeight / 2);
|
||||||
@ -34,7 +99,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
|||||||
RelativeSizeAxes = Axes.Both;
|
RelativeSizeAxes = Axes.Both;
|
||||||
Size = BaseSize = new Vector2(HitObject.IsStrong ? TaikoHitObject.DEFAULT_STRONG_SIZE : TaikoHitObject.DEFAULT_SIZE);
|
Size = BaseSize = new Vector2(HitObject.IsStrong ? TaikoHitObject.DEFAULT_STRONG_SIZE : TaikoHitObject.DEFAULT_SIZE);
|
||||||
|
|
||||||
InternalChild = MainPiece = CreateMainPiece();
|
Content.Add(MainPiece = CreateMainPiece());
|
||||||
MainPiece.KiaiMode = HitObject.Kiai;
|
MainPiece.KiaiMode = HitObject.Kiai;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -44,9 +109,5 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
|||||||
protected override string SampleNamespace => "Taiko";
|
protected override string SampleNamespace => "Taiko";
|
||||||
|
|
||||||
protected virtual TaikoPiece CreateMainPiece() => new CirclePiece();
|
protected virtual TaikoPiece CreateMainPiece() => new CirclePiece();
|
||||||
|
|
||||||
public abstract bool OnPressed(TaikoAction action);
|
|
||||||
|
|
||||||
public virtual bool OnReleased(TaikoAction action) => false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,6 @@ namespace osu.Game.Rulesets.Taiko.Replays
|
|||||||
|
|
||||||
protected override bool IsImportant(TaikoReplayFrame frame) => frame.Actions.Any();
|
protected override bool IsImportant(TaikoReplayFrame frame) => frame.Actions.Any();
|
||||||
|
|
||||||
public override List<InputState> GetPendingStates() => new List<InputState> { new ReplayState<TaikoAction> { PressedActions = CurrentFrame.Actions } };
|
public override List<IInput> GetPendingInputs() => new List<IInput> { new ReplayState<TaikoAction> { PressedActions = CurrentFrame.Actions } };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -27,14 +27,12 @@ namespace osu.Game.Rulesets.Taiko
|
|||||||
|
|
||||||
public override IEnumerable<KeyBinding> GetDefaultKeyBindings(int variant = 0) => new[]
|
public override IEnumerable<KeyBinding> GetDefaultKeyBindings(int variant = 0) => new[]
|
||||||
{
|
{
|
||||||
|
new KeyBinding(InputKey.MouseLeft, TaikoAction.LeftCentre),
|
||||||
|
new KeyBinding(InputKey.MouseRight, TaikoAction.LeftRim),
|
||||||
new KeyBinding(InputKey.D, TaikoAction.LeftRim),
|
new KeyBinding(InputKey.D, TaikoAction.LeftRim),
|
||||||
new KeyBinding(InputKey.F, TaikoAction.LeftCentre),
|
new KeyBinding(InputKey.F, TaikoAction.LeftCentre),
|
||||||
new KeyBinding(InputKey.J, TaikoAction.RightCentre),
|
new KeyBinding(InputKey.J, TaikoAction.RightCentre),
|
||||||
new KeyBinding(InputKey.K, TaikoAction.RightRim),
|
new KeyBinding(InputKey.K, TaikoAction.RightRim),
|
||||||
new KeyBinding(InputKey.MouseLeft, TaikoAction.LeftCentre),
|
|
||||||
new KeyBinding(InputKey.MouseLeft, TaikoAction.RightCentre),
|
|
||||||
new KeyBinding(InputKey.MouseRight, TaikoAction.LeftRim),
|
|
||||||
new KeyBinding(InputKey.MouseRight, TaikoAction.RightRim),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
public override IEnumerable<Mod> ConvertLegacyMods(LegacyMods mods)
|
public override IEnumerable<Mod> ConvertLegacyMods(LegacyMods mods)
|
||||||
@ -91,7 +89,7 @@ namespace osu.Game.Rulesets.Taiko
|
|||||||
{
|
{
|
||||||
new TaikoModHardRock(),
|
new TaikoModHardRock(),
|
||||||
new MultiMod(new TaikoModSuddenDeath(), new TaikoModPerfect()),
|
new MultiMod(new TaikoModSuddenDeath(), new TaikoModPerfect()),
|
||||||
new MultiMod(new TaikoModDoubleTime(), new TaikoModDaycore()),
|
new MultiMod(new TaikoModDoubleTime(), new TaikoModNightcore()),
|
||||||
new TaikoModHidden(),
|
new TaikoModHidden(),
|
||||||
new TaikoModFlashlight(),
|
new TaikoModFlashlight(),
|
||||||
};
|
};
|
||||||
@ -114,9 +112,9 @@ namespace osu.Game.Rulesets.Taiko
|
|||||||
|
|
||||||
public override Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.fa_osu_taiko_o };
|
public override Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.fa_osu_taiko_o };
|
||||||
|
|
||||||
public override DifficultyCalculator CreateDifficultyCalculator(IBeatmap beatmap, Mod[] mods = null) => new TaikoDifficultyCalculator(beatmap, mods);
|
public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new TaikoDifficultyCalculator(this, beatmap);
|
||||||
|
|
||||||
public override PerformanceCalculator CreatePerformanceCalculator(IBeatmap beatmap, Score score) => new TaikoPerformanceCalculator(this, beatmap, score);
|
public override PerformanceCalculator CreatePerformanceCalculator(WorkingBeatmap beatmap, Score score) => new TaikoPerformanceCalculator(this, beatmap, score);
|
||||||
|
|
||||||
public override int? LegacyID => 1;
|
public override int? LegacyID => 1;
|
||||||
|
|
||||||
|
@ -18,6 +18,8 @@ namespace osu.Game.Rulesets.Taiko.UI
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
internal class HitExplosion : CircularContainer
|
internal class HitExplosion : CircularContainer
|
||||||
{
|
{
|
||||||
|
public override bool RemoveWhenNotAlive => true;
|
||||||
|
|
||||||
public readonly DrawableHitObject JudgedObject;
|
public readonly DrawableHitObject JudgedObject;
|
||||||
|
|
||||||
private readonly Box innerFill;
|
private readonly Box innerFill;
|
||||||
@ -66,7 +68,7 @@ namespace osu.Game.Rulesets.Taiko.UI
|
|||||||
this.ScaleTo(3f, 1000, Easing.OutQuint);
|
this.ScaleTo(3f, 1000, Easing.OutQuint);
|
||||||
this.FadeOut(500);
|
this.FadeOut(500);
|
||||||
|
|
||||||
Expire();
|
Expire(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -14,6 +14,8 @@ namespace osu.Game.Rulesets.Taiko.UI
|
|||||||
{
|
{
|
||||||
public class KiaiHitExplosion : CircularContainer
|
public class KiaiHitExplosion : CircularContainer
|
||||||
{
|
{
|
||||||
|
public override bool RemoveWhenNotAlive => true;
|
||||||
|
|
||||||
public readonly DrawableHitObject JudgedObject;
|
public readonly DrawableHitObject JudgedObject;
|
||||||
|
|
||||||
private readonly bool isRim;
|
private readonly bool isRim;
|
||||||
@ -62,7 +64,7 @@ namespace osu.Game.Rulesets.Taiko.UI
|
|||||||
this.ScaleTo(new Vector2(1, 3f), 500, Easing.OutQuint);
|
this.ScaleTo(new Vector2(1, 3f), 500, Easing.OutQuint);
|
||||||
this.FadeOut(250);
|
this.FadeOut(250);
|
||||||
|
|
||||||
Expire();
|
Expire(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -216,10 +216,9 @@ namespace osu.Game.Rulesets.Taiko.UI
|
|||||||
if (barline != null)
|
if (barline != null)
|
||||||
barlineContainer.Add(barline.CreateProxy());
|
barlineContainer.Add(barline.CreateProxy());
|
||||||
|
|
||||||
// Swells should be moved at the very top of the playfield when they reach the hit target
|
var taikoObject = h as DrawableTaikoHitObject;
|
||||||
var swell = h as DrawableSwell;
|
if (taikoObject != null)
|
||||||
if (swell != null)
|
topLevelHitContainer.Add(taikoObject.CreateProxiedContent());
|
||||||
swell.OnStart += () => topLevelHitContainer.Add(swell.CreateProxy());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void OnJudgement(DrawableHitObject judgedObject, Judgement judgement)
|
internal void OnJudgement(DrawableHitObject judgedObject, Judgement judgement)
|
||||||
@ -244,19 +243,6 @@ namespace osu.Game.Rulesets.Taiko.UI
|
|||||||
hitExplosionContainer.Children.FirstOrDefault(e => e.JudgedObject == judgedObject)?.VisualiseSecondHit();
|
hitExplosionContainer.Children.FirstOrDefault(e => e.JudgedObject == judgedObject)?.VisualiseSecondHit();
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (judgedObject.X >= -0.05f && judgedObject is DrawableHit)
|
|
||||||
{
|
|
||||||
// If we're far enough away from the left stage, we should bring outselves in front of it
|
|
||||||
// Todo: The following try-catch is temporary for replay rewinding support
|
|
||||||
try
|
|
||||||
{
|
|
||||||
topLevelHitContainer.Add(judgedObject.CreateProxy());
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
hitExplosionContainer.Add(new HitExplosion(judgedObject, isRim));
|
hitExplosionContainer.Add(new HitExplosion(judgedObject, isRim));
|
||||||
|
|
||||||
if (judgedObject.HitObject.Kiai)
|
if (judgedObject.HitObject.Kiai)
|
||||||
|
@ -86,7 +86,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
|||||||
Assert.AreEqual(string.Empty, metadata.Source);
|
Assert.AreEqual(string.Empty, metadata.Source);
|
||||||
Assert.AreEqual("MBC7 Unisphere 地球ヤバイEP Chikyu Yabai", metadata.Tags);
|
Assert.AreEqual("MBC7 Unisphere 地球ヤバイEP Chikyu Yabai", metadata.Tags);
|
||||||
Assert.AreEqual(557821, beatmapInfo.OnlineBeatmapID);
|
Assert.AreEqual(557821, beatmapInfo.OnlineBeatmapID);
|
||||||
Assert.AreEqual(241526, metadata.OnlineBeatmapSetID);
|
Assert.AreEqual(241526, beatmapInfo.BeatmapSet.OnlineBeatmapSetID);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -28,7 +28,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
|||||||
{
|
{
|
||||||
var beatmap = decodeAsJson(normal);
|
var beatmap = decodeAsJson(normal);
|
||||||
var meta = beatmap.BeatmapInfo.Metadata;
|
var meta = beatmap.BeatmapInfo.Metadata;
|
||||||
Assert.AreEqual(241526, meta.OnlineBeatmapSetID);
|
Assert.AreEqual(241526, beatmap.BeatmapInfo.BeatmapSet.OnlineBeatmapSetID);
|
||||||
Assert.AreEqual("Soleily", meta.Artist);
|
Assert.AreEqual("Soleily", meta.Artist);
|
||||||
Assert.AreEqual("Soleily", meta.ArtistUnicode);
|
Assert.AreEqual("Soleily", meta.ArtistUnicode);
|
||||||
Assert.AreEqual("03. Renatus - Soleily 192kbps.mp3", meta.AudioFile);
|
Assert.AreEqual("03. Renatus - Soleily 192kbps.mp3", meta.AudioFile);
|
||||||
|
@ -19,7 +19,7 @@ namespace osu.Game.Tests.Beatmaps.IO
|
|||||||
[TestFixture]
|
[TestFixture]
|
||||||
public class ImportBeatmapTest
|
public class ImportBeatmapTest
|
||||||
{
|
{
|
||||||
private const string osz_path = @"../../../../osu-resources/osu.Game.Resources/Beatmaps/241526 Soleily - Renatus.osz";
|
public const string TEST_OSZ_PATH = @"../../../../osu-resources/osu.Game.Resources/Beatmaps/241526 Soleily - Renatus.osz";
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestImportWhenClosed()
|
public void TestImportWhenClosed()
|
||||||
@ -265,7 +265,7 @@ namespace osu.Game.Tests.Beatmaps.IO
|
|||||||
private string createTemporaryBeatmap()
|
private string createTemporaryBeatmap()
|
||||||
{
|
{
|
||||||
var temp = Path.GetTempFileName() + ".osz";
|
var temp = Path.GetTempFileName() + ".osz";
|
||||||
File.Copy(osz_path, temp, true);
|
File.Copy(TEST_OSZ_PATH, temp, true);
|
||||||
Assert.IsTrue(File.Exists(temp));
|
Assert.IsTrue(File.Exists(temp));
|
||||||
return temp;
|
return temp;
|
||||||
}
|
}
|
||||||
|
@ -48,11 +48,14 @@ namespace osu.Game.Tests.Beatmaps.IO
|
|||||||
{
|
{
|
||||||
var reader = new ZipArchiveReader(osz);
|
var reader = new ZipArchiveReader(osz);
|
||||||
|
|
||||||
BeatmapMetadata meta;
|
Beatmap beatmap;
|
||||||
using (var stream = new StreamReader(reader.GetStream("Soleily - Renatus (Deif) [Platter].osu")))
|
|
||||||
meta = Decoder.GetDecoder<Beatmap>(stream).Decode(stream).Metadata;
|
|
||||||
|
|
||||||
Assert.AreEqual(241526, meta.OnlineBeatmapSetID);
|
using (var stream = new StreamReader(reader.GetStream("Soleily - Renatus (Deif) [Platter].osu")))
|
||||||
|
beatmap = Decoder.GetDecoder<Beatmap>(stream).Decode(stream);
|
||||||
|
|
||||||
|
var meta = beatmap.Metadata;
|
||||||
|
|
||||||
|
Assert.AreEqual(241526, beatmap.BeatmapInfo.BeatmapSet.OnlineBeatmapSetID);
|
||||||
Assert.AreEqual("Soleily", meta.Artist);
|
Assert.AreEqual("Soleily", meta.Artist);
|
||||||
Assert.AreEqual("Soleily", meta.ArtistUnicode);
|
Assert.AreEqual("Soleily", meta.ArtistUnicode);
|
||||||
Assert.AreEqual("03. Renatus - Soleily 192kbps.mp3", meta.AudioFile);
|
Assert.AreEqual("03. Renatus - Soleily 192kbps.mp3", meta.AudioFile);
|
||||||
|
@ -2,8 +2,8 @@
|
|||||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Rulesets.Difficulty;
|
using osu.Game.Rulesets.Difficulty;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
|
|
||||||
@ -139,14 +139,14 @@ namespace osu.Game.Tests.NonVisual
|
|||||||
private class TestDifficultyCalculator : DifficultyCalculator
|
private class TestDifficultyCalculator : DifficultyCalculator
|
||||||
{
|
{
|
||||||
public TestDifficultyCalculator(params Mod[] mods)
|
public TestDifficultyCalculator(params Mod[] mods)
|
||||||
: base(null)
|
: base(null, null)
|
||||||
{
|
{
|
||||||
DifficultyAdjustmentMods = mods;
|
DifficultyAdjustmentMods = mods;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override double Calculate(Dictionary<string, double> categoryDifficulty = null) => throw new NotImplementedException();
|
|
||||||
|
|
||||||
protected override Mod[] DifficultyAdjustmentMods { get; }
|
protected override Mod[] DifficultyAdjustmentMods { get; }
|
||||||
|
|
||||||
|
protected override DifficultyAttributes Calculate(IBeatmap beatmap, Mod[] mods, double timeRate) => throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -449,7 +449,6 @@ namespace osu.Game.Tests.Visual
|
|||||||
Hash = new MemoryStream(Encoding.UTF8.GetBytes(Guid.NewGuid().ToString())).ComputeMD5Hash(),
|
Hash = new MemoryStream(Encoding.UTF8.GetBytes(Guid.NewGuid().ToString())).ComputeMD5Hash(),
|
||||||
Metadata = new BeatmapMetadata
|
Metadata = new BeatmapMetadata
|
||||||
{
|
{
|
||||||
OnlineBeatmapSetID = id,
|
|
||||||
// Create random metadata, then we can check if sorting works based on these
|
// Create random metadata, then we can check if sorting works based on these
|
||||||
Artist = $"peppy{id.ToString().PadLeft(6, '0')}",
|
Artist = $"peppy{id.ToString().PadLeft(6, '0')}",
|
||||||
Title = $"test set #{id}!",
|
Title = $"test set #{id}!",
|
||||||
@ -503,7 +502,6 @@ namespace osu.Game.Tests.Visual
|
|||||||
Hash = new MemoryStream(Encoding.UTF8.GetBytes(Guid.NewGuid().ToString())).ComputeMD5Hash(),
|
Hash = new MemoryStream(Encoding.UTF8.GetBytes(Guid.NewGuid().ToString())).ComputeMD5Hash(),
|
||||||
Metadata = new BeatmapMetadata
|
Metadata = new BeatmapMetadata
|
||||||
{
|
{
|
||||||
OnlineBeatmapSetID = id,
|
|
||||||
// Create random metadata, then we can check if sorting works based on these
|
// Create random metadata, then we can check if sorting works based on these
|
||||||
Artist = $"peppy{id.ToString().PadLeft(6, '0')}",
|
Artist = $"peppy{id.ToString().PadLeft(6, '0')}",
|
||||||
Title = $"test set #{id}!",
|
Title = $"test set #{id}!",
|
||||||
|
@ -52,7 +52,7 @@ namespace osu.Game.Tests.Visual
|
|||||||
AddStep("remove scores", () => scoresContainer.Scores = null);
|
AddStep("remove scores", () => scoresContainer.Scores = null);
|
||||||
AddStep("resize to big", () => container.ResizeWidthTo(1, 300));
|
AddStep("resize to big", () => container.ResizeWidthTo(1, 300));
|
||||||
AddStep("resize to normal", () => container.ResizeWidthTo(0.8f, 300));
|
AddStep("resize to normal", () => container.ResizeWidthTo(0.8f, 300));
|
||||||
AddStep("online scores", () => scoresContainer.Beatmap = new BeatmapInfo { OnlineBeatmapSetID = 1, OnlineBeatmapID = 75, Ruleset = new OsuRuleset().RulesetInfo });
|
AddStep("online scores", () => scoresContainer.Beatmap = new BeatmapInfo { OnlineBeatmapID = 75, Ruleset = new OsuRuleset().RulesetInfo });
|
||||||
|
|
||||||
|
|
||||||
scores = new[]
|
scores = new[]
|
||||||
|
@ -4,28 +4,48 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Configuration;
|
||||||
using OpenTK;
|
using OpenTK;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Game.Overlays;
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Framework.Graphics.UserInterface;
|
||||||
|
using osu.Framework.Timing;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Screens.Edit.Screens.Compose.Timeline;
|
using osu.Game.Screens.Edit.Screens.Compose.Timeline;
|
||||||
|
using OpenTK.Graphics;
|
||||||
|
|
||||||
namespace osu.Game.Tests.Visual
|
namespace osu.Game.Tests.Visual
|
||||||
{
|
{
|
||||||
[TestFixture]
|
[TestFixture]
|
||||||
public class TestCaseEditorComposeTimeline : OsuTestCase
|
public class TestCaseEditorComposeTimeline : EditorClockTestCase
|
||||||
{
|
{
|
||||||
public override IReadOnlyList<Type> RequiredTypes => new[] { typeof(TimelineArea), typeof(Timeline), typeof(TimelineButton) };
|
public override IReadOnlyList<Type> RequiredTypes => new[]
|
||||||
|
|
||||||
public TestCaseEditorComposeTimeline()
|
|
||||||
{
|
{
|
||||||
|
typeof(TimelineArea),
|
||||||
|
typeof(Timeline),
|
||||||
|
typeof(TimelineButton),
|
||||||
|
typeof(CentreMarker)
|
||||||
|
};
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
Beatmap.Value = new WaveformTestBeatmap();
|
||||||
|
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
new MusicController
|
new FillFlowContainer
|
||||||
{
|
{
|
||||||
Anchor = Anchor.TopCentre,
|
AutoSizeAxes = Axes.Both,
|
||||||
Origin = Anchor.TopCentre,
|
Direction = FillDirection.Vertical,
|
||||||
State = Visibility.Visible
|
Spacing = new Vector2(0, 5),
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new StartStopButton(),
|
||||||
|
new AudioVisualiser(),
|
||||||
|
}
|
||||||
},
|
},
|
||||||
new TimelineArea
|
new TimelineArea
|
||||||
{
|
{
|
||||||
@ -36,5 +56,85 @@ namespace osu.Game.Tests.Visual
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class AudioVisualiser : CompositeDrawable
|
||||||
|
{
|
||||||
|
private readonly Drawable marker;
|
||||||
|
|
||||||
|
private readonly IBindable<WorkingBeatmap> beatmap = new Bindable<WorkingBeatmap>();
|
||||||
|
private IAdjustableClock adjustableClock;
|
||||||
|
|
||||||
|
public AudioVisualiser()
|
||||||
|
{
|
||||||
|
Size = new Vector2(250, 25);
|
||||||
|
|
||||||
|
InternalChildren = new[]
|
||||||
|
{
|
||||||
|
new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Alpha = 0.25f,
|
||||||
|
},
|
||||||
|
marker = new Box
|
||||||
|
{
|
||||||
|
RelativePositionAxes = Axes.X,
|
||||||
|
RelativeSizeAxes = Axes.Y,
|
||||||
|
Width = 2,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(IAdjustableClock adjustableClock, IBindableBeatmap beatmap)
|
||||||
|
{
|
||||||
|
this.adjustableClock = adjustableClock;
|
||||||
|
this.beatmap.BindTo(beatmap);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Update()
|
||||||
|
{
|
||||||
|
base.Update();
|
||||||
|
|
||||||
|
if (beatmap.Value.Track.IsLoaded)
|
||||||
|
marker.X = (float)(adjustableClock.CurrentTime / beatmap.Value.Track.Length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class StartStopButton : Button
|
||||||
|
{
|
||||||
|
private IAdjustableClock adjustableClock;
|
||||||
|
private bool started;
|
||||||
|
|
||||||
|
public StartStopButton()
|
||||||
|
{
|
||||||
|
BackgroundColour = Color4.SlateGray;
|
||||||
|
Size = new Vector2(100, 50);
|
||||||
|
Text = "Start";
|
||||||
|
|
||||||
|
Action = onClick;
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(IAdjustableClock adjustableClock)
|
||||||
|
{
|
||||||
|
this.adjustableClock = adjustableClock;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onClick()
|
||||||
|
{
|
||||||
|
if (started)
|
||||||
|
{
|
||||||
|
adjustableClock.Stop();
|
||||||
|
Text = "Start";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
adjustableClock.Start();
|
||||||
|
Text = "Stop";
|
||||||
|
}
|
||||||
|
|
||||||
|
started = !started;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,8 @@ using osu.Framework.Allocation;
|
|||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Online.Multiplayer;
|
using osu.Game.Online.Multiplayer;
|
||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
|
using osu.Game.Screens;
|
||||||
|
using osu.Game.Screens.Backgrounds;
|
||||||
using osu.Game.Screens.Multi.Components;
|
using osu.Game.Screens.Multi.Components;
|
||||||
using osu.Game.Screens.Multi.Screens.Lounge;
|
using osu.Game.Screens.Multi.Screens.Lounge;
|
||||||
using osu.Game.Users;
|
using osu.Game.Users;
|
||||||
@ -165,6 +167,7 @@ namespace osu.Game.Tests.Visual
|
|||||||
AddStep(@"set rooms", () => lounge.Rooms = rooms);
|
AddStep(@"set rooms", () => lounge.Rooms = rooms);
|
||||||
selectAssert(1);
|
selectAssert(1);
|
||||||
AddStep(@"open room 1", () => clickRoom(1));
|
AddStep(@"open room 1", () => clickRoom(1));
|
||||||
|
AddUntilStep(() => lounge.ChildScreen?.IsCurrentScreen == true, "wait until room current");
|
||||||
AddStep(@"make lounge current", lounge.MakeCurrent);
|
AddStep(@"make lounge current", lounge.MakeCurrent);
|
||||||
filterAssert(@"THE FINAL", LoungeTab.Public, 1);
|
filterAssert(@"THE FINAL", LoungeTab.Public, 1);
|
||||||
filterAssert(string.Empty, LoungeTab.Public, 2);
|
filterAssert(string.Empty, LoungeTab.Public, 2);
|
||||||
@ -198,6 +201,8 @@ namespace osu.Game.Tests.Visual
|
|||||||
|
|
||||||
private class TestLounge : Lounge
|
private class TestLounge : Lounge
|
||||||
{
|
{
|
||||||
|
protected override BackgroundScreen CreateBackground() => new BackgroundScreenDefault();
|
||||||
|
|
||||||
public IEnumerable<DrawableRoom> ChildRooms => RoomsContainer.Children.Where(r => r.MatchingFilter);
|
public IEnumerable<DrawableRoom> ChildRooms => RoomsContainer.Children.Where(r => r.MatchingFilter);
|
||||||
public Room SelectedRoom => Inspector.Room;
|
public Room SelectedRoom => Inspector.Room;
|
||||||
|
|
||||||
|
142
osu.Game.Tests/Visual/TestCaseMatch.cs
Normal file
142
osu.Game.Tests/Visual/TestCaseMatch.cs
Normal file
@ -0,0 +1,142 @@
|
|||||||
|
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||||
|
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||||
|
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Online.Multiplayer;
|
||||||
|
using osu.Game.Rulesets;
|
||||||
|
using osu.Game.Screens.Multi.Screens.Match;
|
||||||
|
using osu.Game.Users;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Visual
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class TestCaseMatch : OsuTestCase
|
||||||
|
{
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(RulesetStore rulesets)
|
||||||
|
{
|
||||||
|
Room room = new Room
|
||||||
|
{
|
||||||
|
Name = { Value = @"One Awesome Room" },
|
||||||
|
Status = { Value = new RoomStatusOpen() },
|
||||||
|
Availability = { Value = RoomAvailability.Public },
|
||||||
|
Type = { Value = new GameTypeTeamVersus() },
|
||||||
|
Beatmap =
|
||||||
|
{
|
||||||
|
Value = new BeatmapInfo
|
||||||
|
{
|
||||||
|
StarDifficulty = 5.02,
|
||||||
|
Ruleset = rulesets.GetRuleset(1),
|
||||||
|
Metadata = new BeatmapMetadata
|
||||||
|
{
|
||||||
|
Title = @"Paradigm Shift",
|
||||||
|
Artist = @"Morimori Atsushi",
|
||||||
|
AuthorString = @"eiri-",
|
||||||
|
},
|
||||||
|
BeatmapSet = new BeatmapSetInfo
|
||||||
|
{
|
||||||
|
OnlineInfo = new BeatmapSetOnlineInfo
|
||||||
|
{
|
||||||
|
Covers = new BeatmapSetOnlineCovers
|
||||||
|
{
|
||||||
|
Cover = @"https://assets.ppy.sh/beatmaps/765055/covers/cover.jpg?1526955337",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
MaxParticipants = { Value = 5 },
|
||||||
|
Participants =
|
||||||
|
{
|
||||||
|
Value = new[]
|
||||||
|
{
|
||||||
|
new User
|
||||||
|
{
|
||||||
|
Username = @"eiri-",
|
||||||
|
Id = 3388410,
|
||||||
|
Country = new Country { FlagName = @"US" },
|
||||||
|
CoverUrl = @"https://assets.ppy.sh/user-profile-covers/3388410/00a8486a247831e1cc4375db519f611ac970bda8bc0057d78b0f540ea38c3e58.jpeg",
|
||||||
|
IsSupporter = true,
|
||||||
|
},
|
||||||
|
new User
|
||||||
|
{
|
||||||
|
Username = @"Nepuri",
|
||||||
|
Id = 6637817,
|
||||||
|
Country = new Country { FlagName = @"DE" },
|
||||||
|
CoverUrl = @"https://assets.ppy.sh/user-profile-covers/6637817/9085fc60248b6b5327a72c1dcdecf2dbedba810ae0ab6bcf7224e46b1339632a.jpeg",
|
||||||
|
IsSupporter = true,
|
||||||
|
},
|
||||||
|
new User
|
||||||
|
{
|
||||||
|
Username = @"goheegy",
|
||||||
|
Id = 8057655,
|
||||||
|
Country = new Country { FlagName = @"GB" },
|
||||||
|
CoverUrl = @"https://assets.ppy.sh/user-profile-covers/8057655/21cec27c25a11dc197a4ec6a74253dbabb495949b0e0697113352f12007018c5.jpeg",
|
||||||
|
},
|
||||||
|
new User
|
||||||
|
{
|
||||||
|
Username = @"Alumetri",
|
||||||
|
Id = 5371497,
|
||||||
|
Country = new Country { FlagName = @"RU" },
|
||||||
|
CoverUrl = @"https://assets.ppy.sh/user-profile-covers/5371497/e023b8c7fbe3613e64bd4856703517ea50fbed8a5805dc9acda9efe9897c67e2.jpeg",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
Match match = new Match(room);
|
||||||
|
|
||||||
|
AddStep(@"show", () => Add(match));
|
||||||
|
AddStep(@"null beatmap", () => room.Beatmap.Value = null);
|
||||||
|
AddStep(@"change name", () => room.Name.Value = @"Two Awesome Rooms");
|
||||||
|
AddStep(@"change status", () => room.Status.Value = new RoomStatusPlaying());
|
||||||
|
AddStep(@"change availability", () => room.Availability.Value = RoomAvailability.FriendsOnly);
|
||||||
|
AddStep(@"change type", () => room.Type.Value = new GameTypeTag());
|
||||||
|
AddStep(@"change beatmap", () => room.Beatmap.Value = new BeatmapInfo
|
||||||
|
{
|
||||||
|
StarDifficulty = 4.33,
|
||||||
|
Ruleset = rulesets.GetRuleset(2),
|
||||||
|
Metadata = new BeatmapMetadata
|
||||||
|
{
|
||||||
|
Title = @"Yasashisa no Riyuu",
|
||||||
|
Artist = @"ChouCho",
|
||||||
|
AuthorString = @"celerih",
|
||||||
|
},
|
||||||
|
BeatmapSet = new BeatmapSetInfo
|
||||||
|
{
|
||||||
|
OnlineInfo = new BeatmapSetOnlineInfo
|
||||||
|
{
|
||||||
|
Covers = new BeatmapSetOnlineCovers
|
||||||
|
{
|
||||||
|
Cover = @"https://assets.ppy.sh/beatmaps/685391/covers/cover.jpg?1524597970",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep(@"null max participants", () => room.MaxParticipants.Value = null);
|
||||||
|
AddStep(@"change participants", () => room.Participants.Value = new[]
|
||||||
|
{
|
||||||
|
new User
|
||||||
|
{
|
||||||
|
Username = @"Spectator",
|
||||||
|
Id = 702598,
|
||||||
|
Country = new Country { FlagName = @"KR" },
|
||||||
|
CoverUrl = @"https://assets.ppy.sh/user-profile-covers/702598/3bbf4cb8b8d2cf8b03145000a975ff27e191ab99b0920832e7dd67386280e288.jpeg",
|
||||||
|
IsSupporter = true,
|
||||||
|
},
|
||||||
|
new User
|
||||||
|
{
|
||||||
|
Username = @"celerih",
|
||||||
|
Id = 4696296,
|
||||||
|
Country = new Country { FlagName = @"CA" },
|
||||||
|
CoverUrl = @"https://assets.ppy.sh/user-profile-covers/4696296/7f8500731d0ac66d5472569d146a7be07d9460273361913f22c038867baddaef.jpeg",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep(@"exit", match.Exit);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
43
osu.Game.Tests/Visual/TestCaseMatchHeader.cs
Normal file
43
osu.Game.Tests/Visual/TestCaseMatchHeader.cs
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||||
|
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||||
|
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Screens.Multi.Screens.Match;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Visual
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class TestCaseMatchHeader : OsuTestCase
|
||||||
|
{
|
||||||
|
public TestCaseMatchHeader()
|
||||||
|
{
|
||||||
|
Header header = new Header();
|
||||||
|
Add(header);
|
||||||
|
|
||||||
|
AddStep(@"set beatmap set", () => header.BeatmapSet = new BeatmapSetInfo
|
||||||
|
{
|
||||||
|
OnlineInfo = new BeatmapSetOnlineInfo
|
||||||
|
{
|
||||||
|
Covers = new BeatmapSetOnlineCovers
|
||||||
|
{
|
||||||
|
Cover = @"https://assets.ppy.sh/beatmaps/760757/covers/cover.jpg?1526944540",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep(@"change beatmap set", () => header.BeatmapSet = new BeatmapSetInfo
|
||||||
|
{
|
||||||
|
OnlineInfo = new BeatmapSetOnlineInfo
|
||||||
|
{
|
||||||
|
Covers = new BeatmapSetOnlineCovers
|
||||||
|
{
|
||||||
|
Cover = @"https://assets.ppy.sh/beatmaps/761883/covers/cover.jpg?1525557400",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep(@"null beatmap set", () => header.BeatmapSet = null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
57
osu.Game.Tests/Visual/TestCaseMatchInfo.cs
Normal file
57
osu.Game.Tests/Visual/TestCaseMatchInfo.cs
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||||
|
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||||
|
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Online.Multiplayer;
|
||||||
|
using osu.Game.Rulesets;
|
||||||
|
using osu.Game.Screens.Multi.Screens.Match;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Visual
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class TestCaseMatchInfo : OsuTestCase
|
||||||
|
{
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(RulesetStore rulesets)
|
||||||
|
{
|
||||||
|
Info info = new Info();
|
||||||
|
Add(info);
|
||||||
|
|
||||||
|
AddStep(@"set name", () => info.Name = @"Room Name?");
|
||||||
|
AddStep(@"set availability", () => info.Availability = RoomAvailability.FriendsOnly);
|
||||||
|
AddStep(@"set status", () => info.Status = new RoomStatusPlaying());
|
||||||
|
AddStep(@"set beatmap", () => info.Beatmap = new BeatmapInfo
|
||||||
|
{
|
||||||
|
StarDifficulty = 2.4,
|
||||||
|
Ruleset = rulesets.GetRuleset(0),
|
||||||
|
Metadata = new BeatmapMetadata
|
||||||
|
{
|
||||||
|
Title = @"My Song",
|
||||||
|
Artist = @"VisualTests",
|
||||||
|
AuthorString = @"osu!lazer",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep(@"set type", () => info.Type = new GameTypeTagTeam());
|
||||||
|
|
||||||
|
AddStep(@"change name", () => info.Name = @"Room Name!");
|
||||||
|
AddStep(@"change availability", () => info.Availability = RoomAvailability.InviteOnly);
|
||||||
|
AddStep(@"change status", () => info.Status = new RoomStatusOpen());
|
||||||
|
AddStep(@"null beatmap", () => info.Beatmap = null);
|
||||||
|
AddStep(@"change type", () => info.Type = new GameTypeTeamVersus());
|
||||||
|
AddStep(@"change beatmap", () => info.Beatmap = new BeatmapInfo
|
||||||
|
{
|
||||||
|
StarDifficulty = 4.2,
|
||||||
|
Ruleset = rulesets.GetRuleset(3),
|
||||||
|
Metadata = new BeatmapMetadata
|
||||||
|
{
|
||||||
|
Title = @"Your Song",
|
||||||
|
Artist = @"Tester",
|
||||||
|
AuthorString = @"Someone",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
56
osu.Game.Tests/Visual/TestCaseMatchParticipants.cs
Normal file
56
osu.Game.Tests/Visual/TestCaseMatchParticipants.cs
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||||
|
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||||
|
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Game.Screens.Multi.Screens.Match;
|
||||||
|
using osu.Game.Users;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Visual
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class TestCaseMatchParticipants : OsuTestCase
|
||||||
|
{
|
||||||
|
public TestCaseMatchParticipants()
|
||||||
|
{
|
||||||
|
Participants participants;
|
||||||
|
Add(participants = new Participants
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep(@"set max to null", () => participants.Max = null);
|
||||||
|
AddStep(@"set users", () => participants.Users = new[]
|
||||||
|
{
|
||||||
|
new User
|
||||||
|
{
|
||||||
|
Username = @"Feppla",
|
||||||
|
Id = 4271601,
|
||||||
|
Country = new Country { FlagName = @"SE" },
|
||||||
|
CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c2.jpg",
|
||||||
|
IsSupporter = true,
|
||||||
|
},
|
||||||
|
new User
|
||||||
|
{
|
||||||
|
Username = @"Xilver",
|
||||||
|
Id = 3099689,
|
||||||
|
Country = new Country { FlagName = @"IL" },
|
||||||
|
CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c2.jpg",
|
||||||
|
IsSupporter = true,
|
||||||
|
},
|
||||||
|
new User
|
||||||
|
{
|
||||||
|
Username = @"Wucki",
|
||||||
|
Id = 5287410,
|
||||||
|
Country = new Country { FlagName = @"FI" },
|
||||||
|
CoverUrl = @"https://assets.ppy.sh/user-profile-covers/5287410/5cfeaa9dd41cbce038ecdc9d781396ed4b0108089170bf7f50492ef8eadeb368.jpeg",
|
||||||
|
IsSupporter = true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep(@"set max", () => participants.Max = 10);
|
||||||
|
AddStep(@"clear users", () => participants.Users = new User[] { });
|
||||||
|
AddStep(@"set max to null", () => participants.Max = null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
26
osu.Game.Tests/Visual/TestCaseParallaxContainer.cs
Normal file
26
osu.Game.Tests/Visual/TestCaseParallaxContainer.cs
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||||
|
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||||
|
|
||||||
|
using osu.Game.Graphics.Containers;
|
||||||
|
using osu.Game.Screens.Backgrounds;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Visual
|
||||||
|
{
|
||||||
|
public class TestCaseParallaxContainer : OsuTestCase
|
||||||
|
{
|
||||||
|
public TestCaseParallaxContainer()
|
||||||
|
{
|
||||||
|
ParallaxContainer parallax;
|
||||||
|
|
||||||
|
Add(parallax = new ParallaxContainer
|
||||||
|
{
|
||||||
|
Child = new BackgroundScreenDefault { Alpha = 0.8f }
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("default parallax", () => parallax.ParallaxAmount = ParallaxContainer.DEFAULT_PARALLAX_AMOUNT);
|
||||||
|
AddStep("high parallax", () => parallax.ParallaxAmount = ParallaxContainer.DEFAULT_PARALLAX_AMOUNT * 10);
|
||||||
|
AddStep("no parallax", () => parallax.ParallaxAmount = 0);
|
||||||
|
AddStep("negative parallax", () => parallax.ParallaxAmount = -ParallaxContainer.DEFAULT_PARALLAX_AMOUNT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -122,7 +122,6 @@ namespace osu.Game.Tests.Visual
|
|||||||
Hash = new MemoryStream(Encoding.UTF8.GetBytes(Guid.NewGuid().ToString())).ComputeMD5Hash(),
|
Hash = new MemoryStream(Encoding.UTF8.GetBytes(Guid.NewGuid().ToString())).ComputeMD5Hash(),
|
||||||
Metadata = new BeatmapMetadata
|
Metadata = new BeatmapMetadata
|
||||||
{
|
{
|
||||||
OnlineBeatmapSetID = 1234 + i,
|
|
||||||
// Create random metadata, then we can check if sorting works based on these
|
// Create random metadata, then we can check if sorting works based on these
|
||||||
Artist = "MONACA " + RNG.Next(0, 9),
|
Artist = "MONACA " + RNG.Next(0, 9),
|
||||||
Title = "Black Song " + RNG.Next(0, 9),
|
Title = "Black Song " + RNG.Next(0, 9),
|
||||||
|
127
osu.Game.Tests/Visual/TestCasePreviewTrackManager.cs
Normal file
127
osu.Game.Tests/Visual/TestCasePreviewTrackManager.cs
Normal file
@ -0,0 +1,127 @@
|
|||||||
|
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||||
|
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||||
|
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Audio.Track;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Game.Audio;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Visual
|
||||||
|
{
|
||||||
|
public class TestCasePreviewTrackManager : OsuTestCase, IPreviewTrackOwner
|
||||||
|
{
|
||||||
|
private readonly PreviewTrackManager trackManager = new TestPreviewTrackManager();
|
||||||
|
|
||||||
|
protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent)
|
||||||
|
{
|
||||||
|
var dependencies = new DependencyContainer(base.CreateLocalDependencies(parent));
|
||||||
|
dependencies.CacheAs(trackManager);
|
||||||
|
dependencies.CacheAs<IPreviewTrackOwner>(this);
|
||||||
|
return dependencies;
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
AddInternal(trackManager);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestStartStop()
|
||||||
|
{
|
||||||
|
PreviewTrack track = null;
|
||||||
|
|
||||||
|
AddStep("get track", () => track = getOwnedTrack());
|
||||||
|
AddStep("start", () => track.Start());
|
||||||
|
AddAssert("started", () => track.IsRunning);
|
||||||
|
AddStep("stop", () => track.Stop());
|
||||||
|
AddAssert("stopped", () => !track.IsRunning);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestStartMultipleTracks()
|
||||||
|
{
|
||||||
|
PreviewTrack track1 = null;
|
||||||
|
PreviewTrack track2 = null;
|
||||||
|
|
||||||
|
AddStep("get tracks", () =>
|
||||||
|
{
|
||||||
|
track1 = getOwnedTrack();
|
||||||
|
track2 = getOwnedTrack();
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("start track 1", () => track1.Start());
|
||||||
|
AddStep("start track 2", () => track2.Start());
|
||||||
|
AddAssert("track 1 stopped", () => !track1.IsRunning);
|
||||||
|
AddAssert("track 2 started", () => track2.IsRunning);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestCancelFromOwner()
|
||||||
|
{
|
||||||
|
PreviewTrack track = null;
|
||||||
|
|
||||||
|
AddStep("get track", () => track = getOwnedTrack());
|
||||||
|
AddStep("start", () => track.Start());
|
||||||
|
AddStep("stop by owner", () => trackManager.StopAnyPlaying(this));
|
||||||
|
AddAssert("stopped", () => !track.IsRunning);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestCancelFromNonOwner()
|
||||||
|
{
|
||||||
|
TestTrackOwner owner = null;
|
||||||
|
PreviewTrack track = null;
|
||||||
|
|
||||||
|
AddStep("get track", () => AddInternal(owner = new TestTrackOwner(track = getTrack())));
|
||||||
|
AddStep("start", () => track.Start());
|
||||||
|
AddStep("attempt stop", () => trackManager.StopAnyPlaying(this));
|
||||||
|
AddAssert("not stopped", () => track.IsRunning);
|
||||||
|
AddStep("stop by true owner", () => trackManager.StopAnyPlaying(owner));
|
||||||
|
AddAssert("stopped", () => !track.IsRunning);
|
||||||
|
}
|
||||||
|
|
||||||
|
private PreviewTrack getTrack() => trackManager.Get(null);
|
||||||
|
|
||||||
|
private PreviewTrack getOwnedTrack()
|
||||||
|
{
|
||||||
|
var track = getTrack();
|
||||||
|
|
||||||
|
AddInternal(track);
|
||||||
|
|
||||||
|
return track;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TestTrackOwner : CompositeDrawable, IPreviewTrackOwner
|
||||||
|
{
|
||||||
|
public TestTrackOwner(PreviewTrack track)
|
||||||
|
{
|
||||||
|
AddInternal(track);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent)
|
||||||
|
{
|
||||||
|
var dependencies = new DependencyContainer(base.CreateLocalDependencies(parent));
|
||||||
|
dependencies.CacheAs<IPreviewTrackOwner>(this);
|
||||||
|
return dependencies;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TestPreviewTrackManager : PreviewTrackManager
|
||||||
|
{
|
||||||
|
protected override TrackManagerPreviewTrack CreatePreviewTrack(BeatmapSetInfo beatmapSetInfo, TrackManager trackManager) => new TestPreviewTrack(beatmapSetInfo, trackManager);
|
||||||
|
|
||||||
|
protected class TestPreviewTrack : TrackManagerPreviewTrack
|
||||||
|
{
|
||||||
|
public TestPreviewTrack(BeatmapSetInfo beatmapSetInfo, TrackManager trackManager)
|
||||||
|
: base(beatmapSetInfo, trackManager)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override Track GetTrack() => new TrackVirtual { Length = 100000 };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -5,6 +5,7 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Game.Overlays.Volume;
|
using osu.Game.Overlays.Volume;
|
||||||
|
using OpenTK;
|
||||||
using OpenTK.Graphics;
|
using OpenTK.Graphics;
|
||||||
|
|
||||||
namespace osu.Game.Tests.Visual
|
namespace osu.Game.Tests.Visual
|
||||||
@ -17,13 +18,21 @@ namespace osu.Game.Tests.Visual
|
|||||||
{
|
{
|
||||||
VolumeMeter meter;
|
VolumeMeter meter;
|
||||||
MuteButton mute;
|
MuteButton mute;
|
||||||
Add(meter = new VolumeMeter("MASTER", 125, Color4.Blue));
|
Add(meter = new VolumeMeter("MASTER", 125, Color4.Blue) { Position = new Vector2(10) });
|
||||||
|
AddSliderStep("master volume", 0, 10, 0, i => meter.Bindable.Value = i * 0.1);
|
||||||
|
|
||||||
|
Add(new VolumeMeter("BIG", 250, Color4.Red)
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Position = new Vector2(10),
|
||||||
|
});
|
||||||
|
|
||||||
Add(mute = new MuteButton
|
Add(mute = new MuteButton
|
||||||
{
|
{
|
||||||
Margin = new MarginPadding { Top = 200 }
|
Margin = new MarginPadding { Top = 200 }
|
||||||
});
|
});
|
||||||
|
|
||||||
AddSliderStep("master volume", 0, 10, 0, i => meter.Bindable.Value = i * 0.1);
|
|
||||||
AddToggleStep("mute", b => mute.Current.Value = b);
|
AddToggleStep("mute", b => mute.Current.Value = b);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,6 @@ using osu.Framework.Graphics.Audio;
|
|||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
using osu.Game.Graphics.Sprites;
|
using osu.Game.Graphics.Sprites;
|
||||||
using osu.Game.Overlays;
|
|
||||||
|
|
||||||
namespace osu.Game.Tests.Visual
|
namespace osu.Game.Tests.Visual
|
||||||
{
|
{
|
||||||
@ -20,22 +19,14 @@ namespace osu.Game.Tests.Visual
|
|||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load()
|
||||||
{
|
{
|
||||||
|
Beatmap.Value = new WaveformTestBeatmap();
|
||||||
|
|
||||||
FillFlowContainer flow;
|
FillFlowContainer flow;
|
||||||
Child = flow = new FillFlowContainer
|
Child = flow = new FillFlowContainer
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Direction = FillDirection.Vertical,
|
Direction = FillDirection.Vertical,
|
||||||
Spacing = new Vector2(0, 10),
|
Spacing = new Vector2(0, 10),
|
||||||
Children = new Drawable[]
|
|
||||||
{
|
|
||||||
new MusicController
|
|
||||||
{
|
|
||||||
Anchor = Anchor.TopCentre,
|
|
||||||
Origin = Anchor.TopCentre,
|
|
||||||
Y = 100,
|
|
||||||
State = Visibility.Visible
|
|
||||||
},
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
for (int i = 1; i <= 16; i *= 2)
|
for (int i = 1; i <= 16; i *= 2)
|
||||||
@ -44,10 +35,9 @@ namespace osu.Game.Tests.Visual
|
|||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Resolution = 1f / i,
|
Resolution = 1f / i,
|
||||||
|
Waveform = Beatmap.Value.Waveform,
|
||||||
};
|
};
|
||||||
|
|
||||||
Beatmap.ValueChanged += b => newDisplay.Waveform = b.Waveform;
|
|
||||||
|
|
||||||
flow.Add(new Container
|
flow.Add(new Container
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
|
@ -13,7 +13,6 @@ using osu.Game.Graphics.Cursor;
|
|||||||
using osu.Game.Screens.Edit.Screens.Compose.Timeline;
|
using osu.Game.Screens.Edit.Screens.Compose.Timeline;
|
||||||
using OpenTK;
|
using OpenTK;
|
||||||
using OpenTK.Graphics;
|
using OpenTK.Graphics;
|
||||||
using OpenTK.Input;
|
|
||||||
|
|
||||||
namespace osu.Game.Tests.Visual
|
namespace osu.Game.Tests.Visual
|
||||||
{
|
{
|
||||||
@ -66,7 +65,7 @@ namespace osu.Game.Tests.Visual
|
|||||||
{
|
{
|
||||||
reset();
|
reset();
|
||||||
AddStep("Set zoom = 10", () => scrollContainer.Zoom = 10);
|
AddStep("Set zoom = 10", () => scrollContainer.Zoom = 10);
|
||||||
AddAssert("Box at 1/2", () => Precision.AlmostEquals(boxQuad.Centre, scrollQuad.Centre));
|
AddAssert("Box at 1/2", () => Precision.AlmostEquals(boxQuad.Centre, scrollQuad.Centre, 1));
|
||||||
AddAssert("Box width = 10x", () => Precision.AlmostEquals(boxQuad.Size.X, 10 * scrollQuad.Size.X));
|
AddAssert("Box width = 10x", () => Precision.AlmostEquals(boxQuad.Size.X, 10 * scrollQuad.Size.X));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -77,16 +76,12 @@ namespace osu.Game.Tests.Visual
|
|||||||
|
|
||||||
// Scroll in at 0.25
|
// Scroll in at 0.25
|
||||||
AddStep("Move mouse to 0.25x", () => InputManager.MoveMouseTo(new Vector2(scrollQuad.TopLeft.X + 0.25f * scrollQuad.Size.X, scrollQuad.Centre.Y)));
|
AddStep("Move mouse to 0.25x", () => InputManager.MoveMouseTo(new Vector2(scrollQuad.TopLeft.X + 0.25f * scrollQuad.Size.X, scrollQuad.Centre.Y)));
|
||||||
AddStep("Press ctrl", () => InputManager.PressKey(Key.LControl));
|
AddStep("Scroll by 3", () => InputManager.ScrollBy(new Vector2(0, 3)));
|
||||||
AddStep("Scroll by 3", () => InputManager.ScrollBy(new Vector2(3, 0)));
|
|
||||||
AddStep("Release ctrl", () => InputManager.ReleaseKey(Key.LControl));
|
|
||||||
AddAssert("Box not at 0", () => !Precision.AlmostEquals(boxQuad.TopLeft, scrollQuad.TopLeft));
|
AddAssert("Box not at 0", () => !Precision.AlmostEquals(boxQuad.TopLeft, scrollQuad.TopLeft));
|
||||||
AddAssert("Box 1/4 at 1/4", () => Precision.AlmostEquals(boxQuad.TopLeft.X + 0.25f * boxQuad.Size.X, scrollQuad.TopLeft.X + 0.25f * scrollQuad.Size.X));
|
AddAssert("Box 1/4 at 1/4", () => Precision.AlmostEquals(boxQuad.TopLeft.X + 0.25f * boxQuad.Size.X, scrollQuad.TopLeft.X + 0.25f * scrollQuad.Size.X));
|
||||||
|
|
||||||
// Scroll out at 0.25
|
// Scroll out at 0.25
|
||||||
AddStep("Press ctrl", () => InputManager.PressKey(Key.LControl));
|
AddStep("Scroll by -3", () => InputManager.ScrollBy(new Vector2(0, -3)));
|
||||||
AddStep("Scroll by -3", () => InputManager.ScrollBy(new Vector2(-3, 0)));
|
|
||||||
AddStep("Release ctrl", () => InputManager.ReleaseKey(Key.LControl));
|
|
||||||
AddAssert("Box at 0", () => Precision.AlmostEquals(boxQuad.TopLeft, scrollQuad.TopLeft));
|
AddAssert("Box at 0", () => Precision.AlmostEquals(boxQuad.TopLeft, scrollQuad.TopLeft));
|
||||||
AddAssert("Box 1/4 at 1/4", () => Precision.AlmostEquals(boxQuad.TopLeft.X + 0.25f * boxQuad.Size.X, scrollQuad.TopLeft.X + 0.25f * scrollQuad.Size.X));
|
AddAssert("Box 1/4 at 1/4", () => Precision.AlmostEquals(boxQuad.TopLeft.X + 0.25f * boxQuad.Size.X, scrollQuad.TopLeft.X + 0.25f * scrollQuad.Size.X));
|
||||||
}
|
}
|
||||||
@ -98,15 +93,11 @@ namespace osu.Game.Tests.Visual
|
|||||||
|
|
||||||
// Scroll in at 0.25
|
// Scroll in at 0.25
|
||||||
AddStep("Move mouse to 0.25x", () => InputManager.MoveMouseTo(new Vector2(scrollQuad.TopLeft.X + 0.25f * scrollQuad.Size.X, scrollQuad.Centre.Y)));
|
AddStep("Move mouse to 0.25x", () => InputManager.MoveMouseTo(new Vector2(scrollQuad.TopLeft.X + 0.25f * scrollQuad.Size.X, scrollQuad.Centre.Y)));
|
||||||
AddStep("Press ctrl", () => InputManager.PressKey(Key.LControl));
|
AddStep("Scroll by 1", () => InputManager.ScrollBy(new Vector2(0, 1)));
|
||||||
AddStep("Scroll by 1", () => InputManager.ScrollBy(new Vector2(1, 0)));
|
|
||||||
AddStep("Release ctrl", () => InputManager.ReleaseKey(Key.LControl));
|
|
||||||
|
|
||||||
// Scroll in at 0.6
|
// Scroll in at 0.6
|
||||||
AddStep("Move mouse to 0.75x", () => InputManager.MoveMouseTo(new Vector2(scrollQuad.TopLeft.X + 0.75f * scrollQuad.Size.X, scrollQuad.Centre.Y)));
|
AddStep("Move mouse to 0.75x", () => InputManager.MoveMouseTo(new Vector2(scrollQuad.TopLeft.X + 0.75f * scrollQuad.Size.X, scrollQuad.Centre.Y)));
|
||||||
AddStep("Press ctrl", () => InputManager.PressKey(Key.LControl));
|
AddStep("Scroll by 1", () => InputManager.ScrollBy(new Vector2(0, 1)));
|
||||||
AddStep("Scroll by 1", () => InputManager.ScrollBy(new Vector2(1, 0)));
|
|
||||||
AddStep("Release ctrl", () => InputManager.ReleaseKey(Key.LControl));
|
|
||||||
AddAssert("Box not at 0", () => !Precision.AlmostEquals(boxQuad.TopLeft, scrollQuad.TopLeft));
|
AddAssert("Box not at 0", () => !Precision.AlmostEquals(boxQuad.TopLeft, scrollQuad.TopLeft));
|
||||||
|
|
||||||
// Very hard to determine actual position, so approximate
|
// Very hard to determine actual position, so approximate
|
||||||
@ -115,15 +106,11 @@ namespace osu.Game.Tests.Visual
|
|||||||
AddAssert("Box at correct position (3)", () => Precision.DefinitelyBigger(boxQuad.TopLeft.X + 0.6f * boxQuad.Size.X, scrollQuad.TopLeft.X + 0.6f * scrollQuad.Size.X));
|
AddAssert("Box at correct position (3)", () => Precision.DefinitelyBigger(boxQuad.TopLeft.X + 0.6f * boxQuad.Size.X, scrollQuad.TopLeft.X + 0.6f * scrollQuad.Size.X));
|
||||||
|
|
||||||
// Scroll out at 0.6
|
// Scroll out at 0.6
|
||||||
AddStep("Press ctrl", () => InputManager.PressKey(Key.LControl));
|
AddStep("Scroll by -1", () => InputManager.ScrollBy(new Vector2(0, -1)));
|
||||||
AddStep("Scroll by -1", () => InputManager.ScrollBy(new Vector2(-1, 0)));
|
|
||||||
AddStep("Release ctrl", () => InputManager.ReleaseKey(Key.LControl));
|
|
||||||
|
|
||||||
// Scroll out at 0.25
|
// Scroll out at 0.25
|
||||||
AddStep("Move mouse to 0.25x", () => InputManager.MoveMouseTo(new Vector2(scrollQuad.TopLeft.X + 0.25f * scrollQuad.Size.X, scrollQuad.Centre.Y)));
|
AddStep("Move mouse to 0.25x", () => InputManager.MoveMouseTo(new Vector2(scrollQuad.TopLeft.X + 0.25f * scrollQuad.Size.X, scrollQuad.Centre.Y)));
|
||||||
AddStep("Press ctrl", () => InputManager.PressKey(Key.LControl));
|
AddStep("Scroll by -1", () => InputManager.ScrollBy(new Vector2(0, -1)));
|
||||||
AddStep("Scroll by -1", () => InputManager.ScrollBy(new Vector2(-1, 0)));
|
|
||||||
AddStep("Release ctrl", () => InputManager.ReleaseKey(Key.LControl));
|
|
||||||
AddAssert("Box at 0", () => Precision.AlmostEquals(boxQuad.TopLeft, scrollQuad.TopLeft));
|
AddAssert("Box at 0", () => Precision.AlmostEquals(boxQuad.TopLeft, scrollQuad.TopLeft));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
55
osu.Game.Tests/WaveformTestBeatmap.cs
Normal file
55
osu.Game.Tests/WaveformTestBeatmap.cs
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||||
|
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||||
|
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using osu.Framework.Audio.Track;
|
||||||
|
using osu.Framework.Graphics.Textures;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Beatmaps.Formats;
|
||||||
|
using osu.Game.IO.Archives;
|
||||||
|
using osu.Game.Tests.Beatmaps.IO;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A <see cref="WorkingBeatmap"/> that is used for testcases that include waveforms.
|
||||||
|
/// </summary>
|
||||||
|
public class WaveformTestBeatmap : WorkingBeatmap
|
||||||
|
{
|
||||||
|
private readonly ZipArchiveReader reader;
|
||||||
|
private readonly FileStream stream;
|
||||||
|
|
||||||
|
public WaveformTestBeatmap()
|
||||||
|
: base(new BeatmapInfo())
|
||||||
|
{
|
||||||
|
stream = File.OpenRead(ImportBeatmapTest.TEST_OSZ_PATH);
|
||||||
|
reader = new ZipArchiveReader(stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Dispose()
|
||||||
|
{
|
||||||
|
base.Dispose();
|
||||||
|
stream?.Dispose();
|
||||||
|
reader?.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override IBeatmap GetBeatmap() => createTestBeatmap();
|
||||||
|
|
||||||
|
protected override Texture GetBackground() => null;
|
||||||
|
|
||||||
|
protected override Waveform GetWaveform() => new Waveform(getAudioStream());
|
||||||
|
|
||||||
|
protected override Track GetTrack() => new TrackBass(getAudioStream());
|
||||||
|
|
||||||
|
private Stream getAudioStream() => reader.GetStream(reader.Filenames.First(f => f.EndsWith(".mp3")));
|
||||||
|
private Stream getBeatmapStream() => reader.GetStream(reader.Filenames.First(f => f.EndsWith(".osu")));
|
||||||
|
|
||||||
|
private Beatmap createTestBeatmap()
|
||||||
|
{
|
||||||
|
using (var beatmapStream = getBeatmapStream())
|
||||||
|
using (var beatmapReader = new StreamReader(beatmapStream))
|
||||||
|
return Decoder.GetDecoder<Beatmap>(beatmapReader).Decode(beatmapReader);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
16
osu.Game/Audio/IPreviewTrackOwner.cs
Normal file
16
osu.Game/Audio/IPreviewTrackOwner.cs
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||||
|
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||||
|
|
||||||
|
namespace osu.Game.Audio
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Interface for objects that can own <see cref="IPreviewTrack"/>s.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// <see cref="IPreviewTrackOwner"/>s can cancel the currently playing <see cref="PreviewTrack"/> through the
|
||||||
|
/// global <see cref="PreviewTrackManager"/> if they're the owner of the playing <see cref="PreviewTrack"/>.
|
||||||
|
/// </remarks>
|
||||||
|
public interface IPreviewTrackOwner
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
103
osu.Game/Audio/PreviewTrack.cs
Normal file
103
osu.Game/Audio/PreviewTrack.cs
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||||
|
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Audio.Track;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Threading;
|
||||||
|
|
||||||
|
namespace osu.Game.Audio
|
||||||
|
{
|
||||||
|
public abstract class PreviewTrack : Component
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Invoked when this <see cref="PreviewTrack"/> has stopped playing.
|
||||||
|
/// </summary>
|
||||||
|
public event Action Stopped;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Invoked when this <see cref="PreviewTrack"/> has started playing.
|
||||||
|
/// </summary>
|
||||||
|
public event Action Started;
|
||||||
|
|
||||||
|
private Track track;
|
||||||
|
private bool hasStarted;
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
track = GetTrack();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Length of the track.
|
||||||
|
/// </summary>
|
||||||
|
public double Length => track?.Length ?? 0;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The current track time.
|
||||||
|
/// </summary>
|
||||||
|
public double CurrentTime => track?.CurrentTime ?? 0;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether the track is loaded.
|
||||||
|
/// </summary>
|
||||||
|
public bool TrackLoaded => track?.IsLoaded ?? false;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether the track is playing.
|
||||||
|
/// </summary>
|
||||||
|
public bool IsRunning => track?.IsRunning ?? false;
|
||||||
|
|
||||||
|
protected override void Update()
|
||||||
|
{
|
||||||
|
base.Update();
|
||||||
|
|
||||||
|
// Todo: Track currently doesn't signal its completion, so we have to handle it manually
|
||||||
|
if (hasStarted && track.HasCompleted)
|
||||||
|
Stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
private ScheduledDelegate startDelegate;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Starts playing this <see cref="PreviewTrack"/>.
|
||||||
|
/// </summary>
|
||||||
|
public void Start() => startDelegate = Schedule(() =>
|
||||||
|
{
|
||||||
|
if (track == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (hasStarted)
|
||||||
|
return;
|
||||||
|
hasStarted = true;
|
||||||
|
|
||||||
|
track.Restart();
|
||||||
|
Started?.Invoke();
|
||||||
|
});
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Stops playing this <see cref="PreviewTrack"/>.
|
||||||
|
/// </summary>
|
||||||
|
public void Stop()
|
||||||
|
{
|
||||||
|
startDelegate?.Cancel();
|
||||||
|
|
||||||
|
if (track == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!hasStarted)
|
||||||
|
return;
|
||||||
|
hasStarted = false;
|
||||||
|
|
||||||
|
track.Stop();
|
||||||
|
Stopped?.Invoke();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieves the audio track.
|
||||||
|
/// </summary>
|
||||||
|
protected abstract Track GetTrack();
|
||||||
|
}
|
||||||
|
}
|
107
osu.Game/Audio/PreviewTrackManager.cs
Normal file
107
osu.Game/Audio/PreviewTrackManager.cs
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||||
|
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||||
|
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Audio;
|
||||||
|
using osu.Framework.Audio.Track;
|
||||||
|
using osu.Framework.Configuration;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.IO.Stores;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
|
||||||
|
namespace osu.Game.Audio
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A central store for the retrieval of <see cref="PreviewTrack"/>s.
|
||||||
|
/// </summary>
|
||||||
|
public class PreviewTrackManager : Component
|
||||||
|
{
|
||||||
|
private readonly BindableDouble muteBindable = new BindableDouble();
|
||||||
|
|
||||||
|
private AudioManager audio;
|
||||||
|
private TrackManager trackManager;
|
||||||
|
|
||||||
|
private TrackManagerPreviewTrack current;
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(AudioManager audio, FrameworkConfigManager config)
|
||||||
|
{
|
||||||
|
trackManager = new TrackManager(new OnlineStore());
|
||||||
|
|
||||||
|
this.audio = audio;
|
||||||
|
audio.AddItem(trackManager);
|
||||||
|
|
||||||
|
config.BindWith(FrameworkSetting.VolumeMusic, trackManager.Volume);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieves a <see cref="PreviewTrack"/> for a <see cref="BeatmapSetInfo"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="beatmapSetInfo">The <see cref="BeatmapSetInfo"/> to retrieve the preview track for.</param>
|
||||||
|
/// <returns>The playable <see cref="PreviewTrack"/>.</returns>
|
||||||
|
public PreviewTrack Get(BeatmapSetInfo beatmapSetInfo)
|
||||||
|
{
|
||||||
|
var track = CreatePreviewTrack(beatmapSetInfo, trackManager);
|
||||||
|
|
||||||
|
track.Started += () =>
|
||||||
|
{
|
||||||
|
current?.Stop();
|
||||||
|
current = track;
|
||||||
|
audio.Track.AddAdjustment(AdjustableProperty.Volume, muteBindable);
|
||||||
|
};
|
||||||
|
|
||||||
|
track.Stopped += () =>
|
||||||
|
{
|
||||||
|
current = null;
|
||||||
|
audio.Track.RemoveAdjustment(AdjustableProperty.Volume, muteBindable);
|
||||||
|
};
|
||||||
|
|
||||||
|
return track;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Stops any currently playing <see cref="PreviewTrack"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Only the immediate owner (an object that implements <see cref="IPreviewTrackOwner"/>) of the playing <see cref="PreviewTrack"/>
|
||||||
|
/// can globally stop the currently playing <see cref="PreviewTrack"/>. The object holding a reference to the <see cref="PreviewTrack"/>
|
||||||
|
/// can always stop the <see cref="PreviewTrack"/> themselves through <see cref="PreviewTrack.Stop()"/>.
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="source">The <see cref="IPreviewTrackOwner"/> which may be the owner of the <see cref="PreviewTrack"/>.</param>
|
||||||
|
public void StopAnyPlaying(IPreviewTrackOwner source)
|
||||||
|
{
|
||||||
|
if (current == null || current.Owner != source)
|
||||||
|
return;
|
||||||
|
|
||||||
|
current.Stop();
|
||||||
|
current = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates the <see cref="TrackManagerPreviewTrack"/>.
|
||||||
|
/// </summary>
|
||||||
|
protected virtual TrackManagerPreviewTrack CreatePreviewTrack(BeatmapSetInfo beatmapSetInfo, TrackManager trackManager) => new TrackManagerPreviewTrack(beatmapSetInfo, trackManager);
|
||||||
|
|
||||||
|
protected class TrackManagerPreviewTrack : PreviewTrack
|
||||||
|
{
|
||||||
|
public IPreviewTrackOwner Owner { get; private set; }
|
||||||
|
|
||||||
|
private readonly BeatmapSetInfo beatmapSetInfo;
|
||||||
|
private readonly TrackManager trackManager;
|
||||||
|
|
||||||
|
public TrackManagerPreviewTrack(BeatmapSetInfo beatmapSetInfo, TrackManager trackManager)
|
||||||
|
{
|
||||||
|
this.beatmapSetInfo = beatmapSetInfo;
|
||||||
|
this.trackManager = trackManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(IPreviewTrackOwner owner)
|
||||||
|
{
|
||||||
|
Owner = owner;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override Track GetTrack() => trackManager.Get($"https://b.ppy.sh/preview/{beatmapSetInfo?.OnlineBeatmapSetID}.mp3");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -23,7 +23,6 @@ namespace osu.Game.Beatmaps
|
|||||||
public int BeatmapVersion;
|
public int BeatmapVersion;
|
||||||
|
|
||||||
private int? onlineBeatmapID;
|
private int? onlineBeatmapID;
|
||||||
private int? onlineBeatmapSetID;
|
|
||||||
|
|
||||||
[JsonProperty("id")]
|
[JsonProperty("id")]
|
||||||
public int? OnlineBeatmapID
|
public int? OnlineBeatmapID
|
||||||
@ -32,19 +31,10 @@ namespace osu.Game.Beatmaps
|
|||||||
set { onlineBeatmapID = value > 0 ? value : null; }
|
set { onlineBeatmapID = value > 0 ? value : null; }
|
||||||
}
|
}
|
||||||
|
|
||||||
[JsonProperty("beatmapset_id")]
|
|
||||||
[NotMapped]
|
|
||||||
public int? OnlineBeatmapSetID
|
|
||||||
{
|
|
||||||
get { return onlineBeatmapSetID; }
|
|
||||||
set { onlineBeatmapSetID = value > 0 ? value : null; }
|
|
||||||
}
|
|
||||||
|
|
||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
public int BeatmapSetInfoID { get; set; }
|
public int BeatmapSetInfoID { get; set; }
|
||||||
|
|
||||||
[Required]
|
[Required]
|
||||||
[JsonIgnore]
|
|
||||||
public BeatmapSetInfo BeatmapSet { get; set; }
|
public BeatmapSetInfo BeatmapSet { get; set; }
|
||||||
|
|
||||||
public BeatmapMetadata Metadata { get; set; }
|
public BeatmapMetadata Metadata { get; set; }
|
||||||
@ -141,8 +131,8 @@ namespace osu.Game.Beatmaps
|
|||||||
(Metadata ?? BeatmapSet.Metadata).AudioFile == (other.Metadata ?? other.BeatmapSet.Metadata).AudioFile;
|
(Metadata ?? BeatmapSet.Metadata).AudioFile == (other.Metadata ?? other.BeatmapSet.Metadata).AudioFile;
|
||||||
|
|
||||||
public bool BackgroundEquals(BeatmapInfo other) => other != null && BeatmapSet != null && other.BeatmapSet != null &&
|
public bool BackgroundEquals(BeatmapInfo other) => other != null && BeatmapSet != null && other.BeatmapSet != null &&
|
||||||
BeatmapSet.Hash == other.BeatmapSet.Hash &&
|
BeatmapSet.Hash == other.BeatmapSet.Hash &&
|
||||||
(Metadata ?? BeatmapSet.Metadata).BackgroundFile == (other.Metadata ?? other.BeatmapSet.Metadata).BackgroundFile;
|
(Metadata ?? BeatmapSet.Metadata).BackgroundFile == (other.Metadata ?? other.BeatmapSet.Metadata).BackgroundFile;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns a shallow-clone of this <see cref="BeatmapInfo"/>.
|
/// Returns a shallow-clone of this <see cref="BeatmapInfo"/>.
|
||||||
|
@ -81,12 +81,31 @@ namespace osu.Game.Beatmaps
|
|||||||
|
|
||||||
protected override void Populate(BeatmapSetInfo model, ArchiveReader archive)
|
protected override void Populate(BeatmapSetInfo model, ArchiveReader archive)
|
||||||
{
|
{
|
||||||
model.Beatmaps = createBeatmapDifficulties(model, archive);
|
model.Beatmaps = createBeatmapDifficulties(archive);
|
||||||
|
|
||||||
// remove metadata from difficulties where it matches the set
|
|
||||||
foreach (BeatmapInfo b in model.Beatmaps)
|
foreach (BeatmapInfo b in model.Beatmaps)
|
||||||
|
{
|
||||||
|
// remove metadata from difficulties where it matches the set
|
||||||
if (model.Metadata.Equals(b.Metadata))
|
if (model.Metadata.Equals(b.Metadata))
|
||||||
b.Metadata = null;
|
b.Metadata = null;
|
||||||
|
|
||||||
|
// by setting the model here, we can update the noline set id below.
|
||||||
|
b.BeatmapSet = model;
|
||||||
|
|
||||||
|
fetchAndPopulateOnlineIDs(b);
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if a set already exists with the same online id, delete if it does.
|
||||||
|
if (model.OnlineBeatmapSetID != null)
|
||||||
|
{
|
||||||
|
var existingOnlineId = beatmaps.ConsumableItems.FirstOrDefault(b => b.OnlineBeatmapSetID == model.OnlineBeatmapSetID);
|
||||||
|
if (existingOnlineId != null)
|
||||||
|
{
|
||||||
|
Delete(existingOnlineId);
|
||||||
|
beatmaps.PurgeDeletable(s => s.ID == existingOnlineId.ID);
|
||||||
|
Logger.Log($"Found existing beatmap set with same OnlineBeatmapSetID ({model.OnlineBeatmapSetID}). It has been purged.", LoggingTarget.Database);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override BeatmapSetInfo CheckForExisting(BeatmapSetInfo model)
|
protected override BeatmapSetInfo CheckForExisting(BeatmapSetInfo model)
|
||||||
@ -99,18 +118,6 @@ namespace osu.Game.Beatmaps
|
|||||||
return existingHashMatch;
|
return existingHashMatch;
|
||||||
}
|
}
|
||||||
|
|
||||||
// check if a set already exists with the same online id
|
|
||||||
if (model.OnlineBeatmapSetID != null)
|
|
||||||
{
|
|
||||||
var existingOnlineId = beatmaps.ConsumableItems.FirstOrDefault(b => b.OnlineBeatmapSetID == model.OnlineBeatmapSetID);
|
|
||||||
if (existingOnlineId != null)
|
|
||||||
{
|
|
||||||
Delete(existingOnlineId);
|
|
||||||
beatmaps.PurgeDeletable(s => s.ID == existingOnlineId.ID);
|
|
||||||
Logger.Log($"Found existing beatmap set with same OnlineBeatmapSetID ({model.OnlineBeatmapSetID}). It has been purged.", LoggingTarget.Database);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -306,29 +313,29 @@ namespace osu.Game.Beatmaps
|
|||||||
return hashable.ComputeSHA2Hash();
|
return hashable.ComputeSHA2Hash();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override BeatmapSetInfo CreateModel(ArchiveReader reader)
|
protected override BeatmapSetInfo CreateModel(ArchiveReader reader)
|
||||||
{
|
{
|
||||||
// let's make sure there are actually .osu files to import.
|
// let's make sure there are actually .osu files to import.
|
||||||
string mapName = reader.Filenames.FirstOrDefault(f => f.EndsWith(".osu"));
|
string mapName = reader.Filenames.FirstOrDefault(f => f.EndsWith(".osu"));
|
||||||
if (string.IsNullOrEmpty(mapName)) throw new InvalidOperationException("No beatmap files found in this beatmap archive.");
|
if (string.IsNullOrEmpty(mapName)) throw new InvalidOperationException("No beatmap files found in this beatmap archive.");
|
||||||
|
|
||||||
BeatmapMetadata metadata;
|
Beatmap beatmap;
|
||||||
using (var stream = new StreamReader(reader.GetStream(mapName)))
|
using (var stream = new StreamReader(reader.GetStream(mapName)))
|
||||||
metadata = Decoder.GetDecoder<Beatmap>(stream).Decode(stream).Metadata;
|
beatmap = Decoder.GetDecoder<Beatmap>(stream).Decode(stream);
|
||||||
|
|
||||||
return new BeatmapSetInfo
|
return new BeatmapSetInfo
|
||||||
{
|
{
|
||||||
OnlineBeatmapSetID = metadata.OnlineBeatmapSetID,
|
OnlineBeatmapSetID = beatmap.BeatmapInfo.BeatmapSet?.OnlineBeatmapSetID,
|
||||||
Beatmaps = new List<BeatmapInfo>(),
|
Beatmaps = new List<BeatmapInfo>(),
|
||||||
Hash = computeBeatmapSetHash(reader),
|
Hash = computeBeatmapSetHash(reader),
|
||||||
Metadata = metadata
|
Metadata = beatmap.Metadata
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Create all required <see cref="BeatmapInfo"/>s for the provided archive.
|
/// Create all required <see cref="BeatmapInfo"/>s for the provided archive.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private List<BeatmapInfo> createBeatmapDifficulties(BeatmapSetInfo model, ArchiveReader reader)
|
private List<BeatmapInfo> createBeatmapDifficulties(ArchiveReader reader)
|
||||||
{
|
{
|
||||||
var beatmapInfos = new List<BeatmapInfo>();
|
var beatmapInfos = new List<BeatmapInfo>();
|
||||||
|
|
||||||
@ -348,10 +355,6 @@ namespace osu.Game.Beatmaps
|
|||||||
beatmap.BeatmapInfo.Hash = ms.ComputeSHA2Hash();
|
beatmap.BeatmapInfo.Hash = ms.ComputeSHA2Hash();
|
||||||
beatmap.BeatmapInfo.MD5Hash = ms.ComputeMD5Hash();
|
beatmap.BeatmapInfo.MD5Hash = ms.ComputeMD5Hash();
|
||||||
|
|
||||||
// ensure we have the same online set ID as the set itself.
|
|
||||||
beatmap.BeatmapInfo.OnlineBeatmapSetID = model.OnlineBeatmapSetID;
|
|
||||||
beatmap.BeatmapInfo.Metadata.OnlineBeatmapSetID = model.OnlineBeatmapSetID;
|
|
||||||
|
|
||||||
// check that no existing beatmap exists that is imported with the same online beatmap ID. if so, give it precedence.
|
// check that no existing beatmap exists that is imported with the same online beatmap ID. if so, give it precedence.
|
||||||
if (beatmap.BeatmapInfo.OnlineBeatmapID.HasValue && QueryBeatmap(b => b.OnlineBeatmapID.Value == beatmap.BeatmapInfo.OnlineBeatmapID.Value) != null)
|
if (beatmap.BeatmapInfo.OnlineBeatmapID.HasValue && QueryBeatmap(b => b.OnlineBeatmapID.Value == beatmap.BeatmapInfo.OnlineBeatmapID.Value) != null)
|
||||||
beatmap.BeatmapInfo.OnlineBeatmapID = null;
|
beatmap.BeatmapInfo.OnlineBeatmapID = null;
|
||||||
@ -363,8 +366,7 @@ namespace osu.Game.Beatmaps
|
|||||||
if (ruleset != null)
|
if (ruleset != null)
|
||||||
{
|
{
|
||||||
// TODO: this should be done in a better place once we actually need to dynamically update it.
|
// TODO: this should be done in a better place once we actually need to dynamically update it.
|
||||||
var converted = new DummyConversionBeatmap(beatmap).GetPlayableBeatmap(ruleset);
|
beatmap.BeatmapInfo.StarDifficulty = ruleset.CreateInstance().CreateDifficultyCalculator(new DummyConversionBeatmap(beatmap)).Calculate().StarRating;
|
||||||
beatmap.BeatmapInfo.StarDifficulty = ruleset.CreateInstance().CreateDifficultyCalculator(converted).Calculate();
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
beatmap.BeatmapInfo.StarDifficulty = 0;
|
beatmap.BeatmapInfo.StarDifficulty = 0;
|
||||||
@ -376,6 +378,43 @@ namespace osu.Game.Beatmaps
|
|||||||
return beatmapInfos;
|
return beatmapInfos;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Query the API to populate mising OnlineBeatmapID / OnlineBeatmapSetID properties.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="beatmap">The beatmap to populate.</param>
|
||||||
|
/// <param name="force">Whether to re-query if the provided beatmap already has populated values.</param>
|
||||||
|
/// <returns>True if population was successful.</returns>
|
||||||
|
private bool fetchAndPopulateOnlineIDs(BeatmapInfo beatmap, bool force = false)
|
||||||
|
{
|
||||||
|
if (!force && beatmap.OnlineBeatmapID != null && beatmap.BeatmapSet.OnlineBeatmapSetID != null)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if (api.State != APIState.Online)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
Logger.Log("Attempting online lookup for IDs...", LoggingTarget.Database);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var req = new GetBeatmapRequest(beatmap);
|
||||||
|
|
||||||
|
req.Perform(api);
|
||||||
|
|
||||||
|
var res = req.Result;
|
||||||
|
|
||||||
|
Logger.Log($"Successfully mapped to {res.OnlineBeatmapSetID} / {res.OnlineBeatmapID}.", LoggingTarget.Database);
|
||||||
|
|
||||||
|
beatmap.BeatmapSet.OnlineBeatmapSetID = res.OnlineBeatmapSetID;
|
||||||
|
beatmap.OnlineBeatmapID = res.OnlineBeatmapID;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Logger.Log($"Failed ({e})", LoggingTarget.Database);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A dummy WorkingBeatmap for the purpose of retrieving a beatmap for star difficulty calculation.
|
/// A dummy WorkingBeatmap for the purpose of retrieving a beatmap for star difficulty calculation.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -17,16 +17,6 @@ namespace osu.Game.Beatmaps
|
|||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
public int ID { get; set; }
|
public int ID { get; set; }
|
||||||
|
|
||||||
private int? onlineBeatmapSetID;
|
|
||||||
|
|
||||||
[NotMapped]
|
|
||||||
[JsonProperty(@"id")]
|
|
||||||
public int? OnlineBeatmapSetID
|
|
||||||
{
|
|
||||||
get { return onlineBeatmapSetID; }
|
|
||||||
set { onlineBeatmapSetID = value > 0 ? value : null; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public string Title { get; set; }
|
public string Title { get; set; }
|
||||||
public string TitleUnicode { get; set; }
|
public string TitleUnicode { get; set; }
|
||||||
public string Artist { get; set; }
|
public string Artist { get; set; }
|
||||||
@ -82,8 +72,7 @@ namespace osu.Game.Beatmaps
|
|||||||
if (other == null)
|
if (other == null)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
return onlineBeatmapSetID == other.onlineBeatmapSetID
|
return Title == other.Title
|
||||||
&& Title == other.Title
|
|
||||||
&& TitleUnicode == other.TitleUnicode
|
&& TitleUnicode == other.TitleUnicode
|
||||||
&& Artist == other.Artist
|
&& Artist == other.Artist
|
||||||
&& ArtistUnicode == other.ArtistUnicode
|
&& ArtistUnicode == other.ArtistUnicode
|
||||||
|
@ -22,18 +22,18 @@ namespace osu.Game.Beatmaps
|
|||||||
[NotMapped]
|
[NotMapped]
|
||||||
public BeatmapSetOnlineInfo OnlineInfo { get; set; }
|
public BeatmapSetOnlineInfo OnlineInfo { get; set; }
|
||||||
|
|
||||||
public double MaxStarDifficulty => Beatmaps.Max(b => b.StarDifficulty);
|
public double MaxStarDifficulty => Beatmaps?.Max(b => b.StarDifficulty) ?? 0;
|
||||||
|
|
||||||
[NotMapped]
|
[NotMapped]
|
||||||
public bool DeletePending { get; set; }
|
public bool DeletePending { get; set; }
|
||||||
|
|
||||||
public string Hash { get; set; }
|
public string Hash { get; set; }
|
||||||
|
|
||||||
public string StoryboardFile => Files.FirstOrDefault(f => f.Filename.EndsWith(".osb"))?.Filename;
|
public string StoryboardFile => Files?.FirstOrDefault(f => f.Filename.EndsWith(".osb"))?.Filename;
|
||||||
|
|
||||||
public List<BeatmapSetFileInfo> Files { get; set; }
|
public List<BeatmapSetFileInfo> Files { get; set; }
|
||||||
|
|
||||||
public override string ToString() => Metadata.ToString();
|
public override string ToString() => Metadata?.ToString() ?? base.ToString();
|
||||||
|
|
||||||
public bool Protected { get; set; }
|
public bool Protected { get; set; }
|
||||||
}
|
}
|
||||||
|
@ -62,7 +62,7 @@ namespace osu.Game.Beatmaps
|
|||||||
|
|
||||||
public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new DummyBeatmapConverter { Beatmap = beatmap };
|
public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new DummyBeatmapConverter { Beatmap = beatmap };
|
||||||
|
|
||||||
public override DifficultyCalculator CreateDifficultyCalculator(IBeatmap beatmap, Mod[] mods = null) => null;
|
public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => null;
|
||||||
|
|
||||||
public override string Description => "dummy";
|
public override string Description => "dummy";
|
||||||
|
|
||||||
|
@ -34,7 +34,8 @@ namespace osu.Game.Beatmaps.Formats
|
|||||||
|
|
||||||
private readonly int offset;
|
private readonly int offset;
|
||||||
|
|
||||||
public LegacyBeatmapDecoder(int version = LATEST_VERSION) : base(version)
|
public LegacyBeatmapDecoder(int version = LATEST_VERSION)
|
||||||
|
: base(version)
|
||||||
{
|
{
|
||||||
// BeatmapVersion 4 and lower had an incorrect offset (stable has this set as 24ms off)
|
// BeatmapVersion 4 and lower had an incorrect offset (stable has this set as 24ms off)
|
||||||
offset = FormatVersion < 5 ? 24 : 0;
|
offset = FormatVersion < 5 ? 24 : 0;
|
||||||
@ -135,6 +136,7 @@ namespace osu.Game.Beatmaps.Formats
|
|||||||
parser = new Rulesets.Objects.Legacy.Mania.ConvertHitObjectParser();
|
parser = new Rulesets.Objects.Legacy.Mania.ConvertHitObjectParser();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case @"LetterboxInBreaks":
|
case @"LetterboxInBreaks":
|
||||||
beatmap.BeatmapInfo.LetterboxInBreaks = int.Parse(pair.Value) == 1;
|
beatmap.BeatmapInfo.LetterboxInBreaks = int.Parse(pair.Value) == 1;
|
||||||
@ -207,8 +209,7 @@ namespace osu.Game.Beatmaps.Formats
|
|||||||
beatmap.BeatmapInfo.OnlineBeatmapID = int.Parse(pair.Value);
|
beatmap.BeatmapInfo.OnlineBeatmapID = int.Parse(pair.Value);
|
||||||
break;
|
break;
|
||||||
case @"BeatmapSetID":
|
case @"BeatmapSetID":
|
||||||
beatmap.BeatmapInfo.OnlineBeatmapSetID = int.Parse(pair.Value);
|
beatmap.BeatmapInfo.BeatmapSet = new BeatmapSetInfo { OnlineBeatmapSetID = int.Parse(pair.Value) };
|
||||||
metadata.OnlineBeatmapSetID = int.Parse(pair.Value);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,7 +13,6 @@ using osu.Game.Storyboards;
|
|||||||
using osu.Framework.IO.File;
|
using osu.Framework.IO.File;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using osu.Game.IO.Serialization;
|
using osu.Game.IO.Serialization;
|
||||||
using System.Diagnostics;
|
|
||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
using osu.Game.Rulesets.UI;
|
using osu.Game.Rulesets.UI;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
@ -49,12 +48,13 @@ namespace osu.Game.Beatmaps
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Saves the <see cref="Beatmaps.Beatmap"/>.
|
/// Saves the <see cref="Beatmaps.Beatmap"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void Save()
|
/// <returns>The absolute path of the output file.</returns>
|
||||||
|
public string Save()
|
||||||
{
|
{
|
||||||
var path = FileSafety.GetTempPath(Guid.NewGuid().ToString().Replace("-", string.Empty) + ".json");
|
var path = FileSafety.GetTempPath(Guid.NewGuid().ToString().Replace("-", string.Empty) + ".json");
|
||||||
using (var sw = new StreamWriter(path))
|
using (var sw = new StreamWriter(path))
|
||||||
sw.WriteLine(Beatmap.Serialize());
|
sw.WriteLine(Beatmap.Serialize());
|
||||||
Process.Start(path);
|
return path;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract IBeatmap GetBeatmap();
|
protected abstract IBeatmap GetBeatmap();
|
||||||
|
@ -13,13 +13,13 @@ namespace osu.Game.Configuration
|
|||||||
{
|
{
|
||||||
private readonly SettingsStore settings;
|
private readonly SettingsStore settings;
|
||||||
|
|
||||||
private readonly int variant;
|
private readonly int? variant;
|
||||||
|
|
||||||
private readonly List<DatabasedSetting> databasedSettings;
|
private readonly List<DatabasedSetting> databasedSettings;
|
||||||
|
|
||||||
private readonly RulesetInfo ruleset;
|
private readonly RulesetInfo ruleset;
|
||||||
|
|
||||||
protected DatabasedConfigManager(SettingsStore settings, RulesetInfo ruleset = null, int variant = 0)
|
protected DatabasedConfigManager(SettingsStore settings, RulesetInfo ruleset = null, int? variant = null)
|
||||||
{
|
{
|
||||||
this.settings = settings;
|
this.settings = settings;
|
||||||
this.ruleset = ruleset;
|
this.ruleset = ruleset;
|
||||||
|
@ -181,24 +181,6 @@ namespace osu.Game.Database
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Migrate()
|
public void Migrate() => Database.Migrate();
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Database.Migrate();
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
throw new MigrationFailedException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class MigrationFailedException : Exception
|
|
||||||
{
|
|
||||||
public MigrationFailedException(Exception exception)
|
|
||||||
: base("sqlite-net migration failed", exception)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,11 +3,11 @@
|
|||||||
|
|
||||||
using osu.Game.Online.Chat;
|
using osu.Game.Online.Chat;
|
||||||
using System;
|
using System;
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using osu.Framework.Platform;
|
||||||
using osu.Game.Overlays;
|
using osu.Game.Overlays;
|
||||||
using osu.Game.Overlays.Notifications;
|
using osu.Game.Overlays.Notifications;
|
||||||
|
|
||||||
@ -25,12 +25,14 @@ namespace osu.Game.Graphics.Containers
|
|||||||
private OsuGame game;
|
private OsuGame game;
|
||||||
|
|
||||||
private Action showNotImplementedError;
|
private Action showNotImplementedError;
|
||||||
|
private GameHost host;
|
||||||
|
|
||||||
[BackgroundDependencyLoader(true)]
|
[BackgroundDependencyLoader(true)]
|
||||||
private void load(OsuGame game, NotificationOverlay notifications)
|
private void load(OsuGame game, NotificationOverlay notifications, GameHost host)
|
||||||
{
|
{
|
||||||
// will be null in tests
|
// will be null in tests
|
||||||
this.game = game;
|
this.game = game;
|
||||||
|
this.host = host;
|
||||||
|
|
||||||
showNotImplementedError = () => notifications?.Post(new SimpleNotification
|
showNotImplementedError = () => notifications?.Post(new SimpleNotification
|
||||||
{
|
{
|
||||||
@ -88,7 +90,7 @@ namespace osu.Game.Graphics.Containers
|
|||||||
showNotImplementedError?.Invoke();
|
showNotImplementedError?.Invoke();
|
||||||
break;
|
break;
|
||||||
case LinkAction.External:
|
case LinkAction.External:
|
||||||
Process.Start(url);
|
host.OpenUrlExternally(url);
|
||||||
break;
|
break;
|
||||||
case LinkAction.OpenUserProfile:
|
case LinkAction.OpenUserProfile:
|
||||||
if (long.TryParse(linkArgument, out long userId))
|
if (long.TryParse(linkArgument, out long userId))
|
||||||
|
@ -8,20 +8,32 @@ using osu.Framework.Graphics.Containers;
|
|||||||
using osu.Framework.Input;
|
using osu.Framework.Input;
|
||||||
using OpenTK;
|
using OpenTK;
|
||||||
using osu.Framework.Configuration;
|
using osu.Framework.Configuration;
|
||||||
|
using osu.Game.Audio;
|
||||||
using osu.Game.Overlays;
|
using osu.Game.Overlays;
|
||||||
|
|
||||||
namespace osu.Game.Graphics.Containers
|
namespace osu.Game.Graphics.Containers
|
||||||
{
|
{
|
||||||
public class OsuFocusedOverlayContainer : FocusedOverlayContainer
|
public class OsuFocusedOverlayContainer : FocusedOverlayContainer, IPreviewTrackOwner
|
||||||
{
|
{
|
||||||
private SampleChannel samplePopIn;
|
private SampleChannel samplePopIn;
|
||||||
private SampleChannel samplePopOut;
|
private SampleChannel samplePopOut;
|
||||||
|
|
||||||
|
private PreviewTrackManager previewTrackManager;
|
||||||
|
|
||||||
protected readonly Bindable<OverlayActivation> OverlayActivationMode = new Bindable<OverlayActivation>(OverlayActivation.All);
|
protected readonly Bindable<OverlayActivation> OverlayActivationMode = new Bindable<OverlayActivation>(OverlayActivation.All);
|
||||||
|
|
||||||
[BackgroundDependencyLoader(true)]
|
protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent)
|
||||||
private void load(OsuGame osuGame, AudioManager audio)
|
|
||||||
{
|
{
|
||||||
|
var dependencies = new DependencyContainer(base.CreateLocalDependencies(parent));
|
||||||
|
dependencies.CacheAs<IPreviewTrackOwner>(this);
|
||||||
|
return dependencies;
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader(true)]
|
||||||
|
private void load(OsuGame osuGame, AudioManager audio, PreviewTrackManager previewTrackManager)
|
||||||
|
{
|
||||||
|
this.previewTrackManager = previewTrackManager;
|
||||||
|
|
||||||
if (osuGame != null)
|
if (osuGame != null)
|
||||||
OverlayActivationMode.BindTo(osuGame.OverlayActivationMode);
|
OverlayActivationMode.BindTo(osuGame.OverlayActivationMode);
|
||||||
|
|
||||||
@ -33,7 +45,7 @@ namespace osu.Game.Graphics.Containers
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether mouse input should be blocked screen-wide while this overlay is visible.
|
/// Whether mouse input should be blocked screen-wide while this overlay is visible.
|
||||||
/// Performing mouse actions outside of the valid extents will hide the overlay but pass the events through.
|
/// Performing mouse actions outside of the valid extents will hide the overlay.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual bool BlockScreenWideMouse => BlockPassThroughMouse;
|
public virtual bool BlockScreenWideMouse => BlockPassThroughMouse;
|
||||||
|
|
||||||
@ -66,5 +78,11 @@ namespace osu.Game.Graphics.Containers
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void PopOut()
|
||||||
|
{
|
||||||
|
base.PopOut();
|
||||||
|
previewTrackManager.StopAnyPlaying(this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,9 @@ namespace osu.Game.Graphics.Containers
|
|||||||
{
|
{
|
||||||
public const float DEFAULT_PARALLAX_AMOUNT = 0.02f;
|
public const float DEFAULT_PARALLAX_AMOUNT = 0.02f;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The amount of parallax movement. Negative values will reverse the direction of parallax relative to user input.
|
||||||
|
/// </summary>
|
||||||
public float ParallaxAmount = DEFAULT_PARALLAX_AMOUNT;
|
public float ParallaxAmount = DEFAULT_PARALLAX_AMOUNT;
|
||||||
|
|
||||||
private Bindable<bool> parallaxEnabled;
|
private Bindable<bool> parallaxEnabled;
|
||||||
@ -45,7 +48,7 @@ namespace osu.Game.Graphics.Containers
|
|||||||
if (!parallaxEnabled)
|
if (!parallaxEnabled)
|
||||||
{
|
{
|
||||||
content.MoveTo(Vector2.Zero, firstUpdate ? 0 : 1000, Easing.OutQuint);
|
content.MoveTo(Vector2.Zero, firstUpdate ? 0 : 1000, Easing.OutQuint);
|
||||||
content.Scale = new Vector2(1 + ParallaxAmount);
|
content.Scale = new Vector2(1 + System.Math.Abs(ParallaxAmount));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -69,7 +72,7 @@ namespace osu.Game.Graphics.Containers
|
|||||||
double elapsed = MathHelper.Clamp(Clock.ElapsedFrameTime, 0, 1000);
|
double elapsed = MathHelper.Clamp(Clock.ElapsedFrameTime, 0, 1000);
|
||||||
|
|
||||||
content.Position = Interpolation.ValueAt(elapsed, content.Position, offset, 0, 1000, Easing.OutQuint);
|
content.Position = Interpolation.ValueAt(elapsed, content.Position, offset, 0, 1000, Easing.OutQuint);
|
||||||
content.Scale = Interpolation.ValueAt(elapsed, content.Scale, new Vector2(1 + ParallaxAmount), 0, 1000, Easing.OutQuint);
|
content.Scale = Interpolation.ValueAt(elapsed, content.Scale, new Vector2(1 + System.Math.Abs(ParallaxAmount)), 0, 1000, Easing.OutQuint);
|
||||||
}
|
}
|
||||||
|
|
||||||
firstUpdate = false;
|
firstUpdate = false;
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||||
|
|
||||||
using System.Diagnostics;
|
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Cursor;
|
using osu.Framework.Graphics.Cursor;
|
||||||
using osu.Framework.Input;
|
using osu.Framework.Input;
|
||||||
|
using osu.Framework.Platform;
|
||||||
using OpenTK;
|
using OpenTK;
|
||||||
using OpenTK.Graphics;
|
using OpenTK.Graphics;
|
||||||
|
|
||||||
@ -17,6 +17,7 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
public string Link { get; set; }
|
public string Link { get; set; }
|
||||||
|
|
||||||
private Color4 hoverColour;
|
private Color4 hoverColour;
|
||||||
|
private GameHost host;
|
||||||
|
|
||||||
public ExternalLinkButton(string link = null)
|
public ExternalLinkButton(string link = null)
|
||||||
{
|
{
|
||||||
@ -30,9 +31,10 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(OsuColour colours)
|
private void load(OsuColour colours, GameHost host)
|
||||||
{
|
{
|
||||||
hoverColour = colours.Yellow;
|
hoverColour = colours.Yellow;
|
||||||
|
this.host = host;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override bool OnHover(InputState state)
|
protected override bool OnHover(InputState state)
|
||||||
@ -50,11 +52,7 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
protected override bool OnClick(InputState state)
|
protected override bool OnClick(InputState state)
|
||||||
{
|
{
|
||||||
if(Link != null)
|
if(Link != null)
|
||||||
Process.Start(new ProcessStartInfo
|
host.OpenUrlExternally(Link);
|
||||||
{
|
|
||||||
FileName = Link,
|
|
||||||
UseShellExecute = true //see https://github.com/dotnet/corefx/issues/10361
|
|
||||||
});
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -32,16 +32,14 @@ namespace osu.Game.Input.Handlers
|
|||||||
|
|
||||||
public override int Priority => 0;
|
public override int Priority => 0;
|
||||||
|
|
||||||
public class ReplayState<T> : InputState
|
public class ReplayState<T> : IInput
|
||||||
where T : struct
|
where T : struct
|
||||||
{
|
{
|
||||||
public List<T> PressedActions;
|
public List<T> PressedActions;
|
||||||
|
|
||||||
public override InputState Clone()
|
public void Apply(InputState state, IInputStateChangeHandler handler)
|
||||||
{
|
{
|
||||||
var clone = (ReplayState<T>)base.Clone();
|
handler.HandleCustomInput(state, this);
|
||||||
clone.PressedActions = new List<T>(PressedActions);
|
|
||||||
return clone;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
376
osu.Game/Migrations/20180621044111_UpdateTaikoDefaultBindings.Designer.cs
generated
Normal file
376
osu.Game/Migrations/20180621044111_UpdateTaikoDefaultBindings.Designer.cs
generated
Normal file
@ -0,0 +1,376 @@
|
|||||||
|
// <auto-generated />
|
||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
|
using osu.Game.Database;
|
||||||
|
|
||||||
|
namespace osu.Game.Migrations
|
||||||
|
{
|
||||||
|
[DbContext(typeof(OsuDbContext))]
|
||||||
|
[Migration("20180621044111_UpdateTaikoDefaultBindings")]
|
||||||
|
partial class UpdateTaikoDefaultBindings
|
||||||
|
{
|
||||||
|
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||||
|
{
|
||||||
|
#pragma warning disable 612, 618
|
||||||
|
modelBuilder
|
||||||
|
.HasAnnotation("ProductVersion", "2.1.1-rtm-30846");
|
||||||
|
|
||||||
|
modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("ID")
|
||||||
|
.ValueGeneratedOnAdd();
|
||||||
|
|
||||||
|
b.Property<float>("ApproachRate");
|
||||||
|
|
||||||
|
b.Property<float>("CircleSize");
|
||||||
|
|
||||||
|
b.Property<float>("DrainRate");
|
||||||
|
|
||||||
|
b.Property<float>("OverallDifficulty");
|
||||||
|
|
||||||
|
b.Property<double>("SliderMultiplier");
|
||||||
|
|
||||||
|
b.Property<double>("SliderTickRate");
|
||||||
|
|
||||||
|
b.HasKey("ID");
|
||||||
|
|
||||||
|
b.ToTable("BeatmapDifficulty");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("ID")
|
||||||
|
.ValueGeneratedOnAdd();
|
||||||
|
|
||||||
|
b.Property<int>("AudioLeadIn");
|
||||||
|
|
||||||
|
b.Property<int>("BaseDifficultyID");
|
||||||
|
|
||||||
|
b.Property<int>("BeatDivisor");
|
||||||
|
|
||||||
|
b.Property<int>("BeatmapSetInfoID");
|
||||||
|
|
||||||
|
b.Property<bool>("Countdown");
|
||||||
|
|
||||||
|
b.Property<double>("DistanceSpacing");
|
||||||
|
|
||||||
|
b.Property<int>("GridSize");
|
||||||
|
|
||||||
|
b.Property<string>("Hash");
|
||||||
|
|
||||||
|
b.Property<bool>("Hidden");
|
||||||
|
|
||||||
|
b.Property<bool>("LetterboxInBreaks");
|
||||||
|
|
||||||
|
b.Property<string>("MD5Hash");
|
||||||
|
|
||||||
|
b.Property<int?>("MetadataID");
|
||||||
|
|
||||||
|
b.Property<int?>("OnlineBeatmapID");
|
||||||
|
|
||||||
|
b.Property<string>("Path");
|
||||||
|
|
||||||
|
b.Property<int>("RulesetID");
|
||||||
|
|
||||||
|
b.Property<bool>("SpecialStyle");
|
||||||
|
|
||||||
|
b.Property<float>("StackLeniency");
|
||||||
|
|
||||||
|
b.Property<double>("StarDifficulty");
|
||||||
|
|
||||||
|
b.Property<string>("StoredBookmarks");
|
||||||
|
|
||||||
|
b.Property<double>("TimelineZoom");
|
||||||
|
|
||||||
|
b.Property<string>("Version");
|
||||||
|
|
||||||
|
b.Property<bool>("WidescreenStoryboard");
|
||||||
|
|
||||||
|
b.HasKey("ID");
|
||||||
|
|
||||||
|
b.HasIndex("BaseDifficultyID");
|
||||||
|
|
||||||
|
b.HasIndex("BeatmapSetInfoID");
|
||||||
|
|
||||||
|
b.HasIndex("Hash");
|
||||||
|
|
||||||
|
b.HasIndex("MD5Hash");
|
||||||
|
|
||||||
|
b.HasIndex("MetadataID");
|
||||||
|
|
||||||
|
b.HasIndex("OnlineBeatmapID")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.HasIndex("RulesetID");
|
||||||
|
|
||||||
|
b.ToTable("BeatmapInfo");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("ID")
|
||||||
|
.ValueGeneratedOnAdd();
|
||||||
|
|
||||||
|
b.Property<string>("Artist");
|
||||||
|
|
||||||
|
b.Property<string>("ArtistUnicode");
|
||||||
|
|
||||||
|
b.Property<string>("AudioFile");
|
||||||
|
|
||||||
|
b.Property<string>("AuthorString")
|
||||||
|
.HasColumnName("Author");
|
||||||
|
|
||||||
|
b.Property<string>("BackgroundFile");
|
||||||
|
|
||||||
|
b.Property<int>("PreviewTime");
|
||||||
|
|
||||||
|
b.Property<string>("Source");
|
||||||
|
|
||||||
|
b.Property<string>("Tags");
|
||||||
|
|
||||||
|
b.Property<string>("Title");
|
||||||
|
|
||||||
|
b.Property<string>("TitleUnicode");
|
||||||
|
|
||||||
|
b.HasKey("ID");
|
||||||
|
|
||||||
|
b.ToTable("BeatmapMetadata");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("ID")
|
||||||
|
.ValueGeneratedOnAdd();
|
||||||
|
|
||||||
|
b.Property<int>("BeatmapSetInfoID");
|
||||||
|
|
||||||
|
b.Property<int>("FileInfoID");
|
||||||
|
|
||||||
|
b.Property<string>("Filename")
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasKey("ID");
|
||||||
|
|
||||||
|
b.HasIndex("BeatmapSetInfoID");
|
||||||
|
|
||||||
|
b.HasIndex("FileInfoID");
|
||||||
|
|
||||||
|
b.ToTable("BeatmapSetFileInfo");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("ID")
|
||||||
|
.ValueGeneratedOnAdd();
|
||||||
|
|
||||||
|
b.Property<bool>("DeletePending");
|
||||||
|
|
||||||
|
b.Property<string>("Hash");
|
||||||
|
|
||||||
|
b.Property<int?>("MetadataID");
|
||||||
|
|
||||||
|
b.Property<int?>("OnlineBeatmapSetID");
|
||||||
|
|
||||||
|
b.Property<bool>("Protected");
|
||||||
|
|
||||||
|
b.HasKey("ID");
|
||||||
|
|
||||||
|
b.HasIndex("DeletePending");
|
||||||
|
|
||||||
|
b.HasIndex("Hash")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.HasIndex("MetadataID");
|
||||||
|
|
||||||
|
b.HasIndex("OnlineBeatmapSetID")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.ToTable("BeatmapSetInfo");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("ID")
|
||||||
|
.ValueGeneratedOnAdd();
|
||||||
|
|
||||||
|
b.Property<int>("IntKey")
|
||||||
|
.HasColumnName("Key");
|
||||||
|
|
||||||
|
b.Property<int?>("RulesetID");
|
||||||
|
|
||||||
|
b.Property<string>("StringValue")
|
||||||
|
.HasColumnName("Value");
|
||||||
|
|
||||||
|
b.Property<int?>("Variant");
|
||||||
|
|
||||||
|
b.HasKey("ID");
|
||||||
|
|
||||||
|
b.HasIndex("RulesetID", "Variant");
|
||||||
|
|
||||||
|
b.ToTable("Settings");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("ID")
|
||||||
|
.ValueGeneratedOnAdd();
|
||||||
|
|
||||||
|
b.Property<int>("IntAction")
|
||||||
|
.HasColumnName("Action");
|
||||||
|
|
||||||
|
b.Property<string>("KeysString")
|
||||||
|
.HasColumnName("Keys");
|
||||||
|
|
||||||
|
b.Property<int?>("RulesetID");
|
||||||
|
|
||||||
|
b.Property<int?>("Variant");
|
||||||
|
|
||||||
|
b.HasKey("ID");
|
||||||
|
|
||||||
|
b.HasIndex("IntAction");
|
||||||
|
|
||||||
|
b.HasIndex("RulesetID", "Variant");
|
||||||
|
|
||||||
|
b.ToTable("KeyBinding");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("osu.Game.IO.FileInfo", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("ID")
|
||||||
|
.ValueGeneratedOnAdd();
|
||||||
|
|
||||||
|
b.Property<string>("Hash");
|
||||||
|
|
||||||
|
b.Property<int>("ReferenceCount");
|
||||||
|
|
||||||
|
b.HasKey("ID");
|
||||||
|
|
||||||
|
b.HasIndex("Hash")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.HasIndex("ReferenceCount");
|
||||||
|
|
||||||
|
b.ToTable("FileInfo");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b =>
|
||||||
|
{
|
||||||
|
b.Property<int?>("ID")
|
||||||
|
.ValueGeneratedOnAdd();
|
||||||
|
|
||||||
|
b.Property<bool>("Available");
|
||||||
|
|
||||||
|
b.Property<string>("InstantiationInfo");
|
||||||
|
|
||||||
|
b.Property<string>("Name");
|
||||||
|
|
||||||
|
b.Property<string>("ShortName");
|
||||||
|
|
||||||
|
b.HasKey("ID");
|
||||||
|
|
||||||
|
b.HasIndex("Available");
|
||||||
|
|
||||||
|
b.HasIndex("ShortName")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.ToTable("RulesetInfo");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("ID")
|
||||||
|
.ValueGeneratedOnAdd();
|
||||||
|
|
||||||
|
b.Property<int>("FileInfoID");
|
||||||
|
|
||||||
|
b.Property<string>("Filename")
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Property<int>("SkinInfoID");
|
||||||
|
|
||||||
|
b.HasKey("ID");
|
||||||
|
|
||||||
|
b.HasIndex("FileInfoID");
|
||||||
|
|
||||||
|
b.HasIndex("SkinInfoID");
|
||||||
|
|
||||||
|
b.ToTable("SkinFileInfo");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("osu.Game.Skinning.SkinInfo", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("ID")
|
||||||
|
.ValueGeneratedOnAdd();
|
||||||
|
|
||||||
|
b.Property<string>("Creator");
|
||||||
|
|
||||||
|
b.Property<bool>("DeletePending");
|
||||||
|
|
||||||
|
b.Property<string>("Name");
|
||||||
|
|
||||||
|
b.HasKey("ID");
|
||||||
|
|
||||||
|
b.ToTable("SkinInfo");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("BaseDifficultyID")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
|
|
||||||
|
b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet")
|
||||||
|
.WithMany("Beatmaps")
|
||||||
|
.HasForeignKey("BeatmapSetInfoID")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
|
|
||||||
|
b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata")
|
||||||
|
.WithMany("Beatmaps")
|
||||||
|
.HasForeignKey("MetadataID");
|
||||||
|
|
||||||
|
b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("RulesetID")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo")
|
||||||
|
.WithMany("Files")
|
||||||
|
.HasForeignKey("BeatmapSetInfoID")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
|
|
||||||
|
b.HasOne("osu.Game.IO.FileInfo", "FileInfo")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("FileInfoID")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata")
|
||||||
|
.WithMany("BeatmapSets")
|
||||||
|
.HasForeignKey("MetadataID");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("osu.Game.IO.FileInfo", "FileInfo")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("FileInfoID")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
|
|
||||||
|
b.HasOne("osu.Game.Skinning.SkinInfo")
|
||||||
|
.WithMany("Files")
|
||||||
|
.HasForeignKey("SkinInfoID")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
|
});
|
||||||
|
#pragma warning restore 612, 618
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,19 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using osu.Framework.Logging;
|
||||||
|
|
||||||
|
namespace osu.Game.Migrations
|
||||||
|
{
|
||||||
|
public partial class UpdateTaikoDefaultBindings : Migration
|
||||||
|
{
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.Sql("DELETE FROM KeyBinding WHERE RulesetID = 1");
|
||||||
|
Logger.Log("osu!taiko bindings have been reset due to new defaults", LoggingTarget.Runtime, LogLevel.Important);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
// we can't really tell if these should be restored or not, so let's just not do so.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,11 +1,9 @@
|
|||||||
// <auto-generated />
|
// <auto-generated />
|
||||||
|
using System;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
using Microsoft.EntityFrameworkCore.Metadata;
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
using Microsoft.EntityFrameworkCore.Migrations;
|
|
||||||
using Microsoft.EntityFrameworkCore.Storage;
|
|
||||||
using osu.Game.Database;
|
using osu.Game.Database;
|
||||||
using System;
|
|
||||||
|
|
||||||
namespace osu.Game.Migrations
|
namespace osu.Game.Migrations
|
||||||
{
|
{
|
||||||
@ -16,7 +14,7 @@ namespace osu.Game.Migrations
|
|||||||
{
|
{
|
||||||
#pragma warning disable 612, 618
|
#pragma warning disable 612, 618
|
||||||
modelBuilder
|
modelBuilder
|
||||||
.HasAnnotation("ProductVersion", "2.0.3-rtm-10026");
|
.HasAnnotation("ProductVersion", "2.1.1-rtm-30846");
|
||||||
|
|
||||||
modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b =>
|
modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b =>
|
||||||
{
|
{
|
||||||
|
@ -14,16 +14,19 @@ namespace osu.Game.Online.API
|
|||||||
{
|
{
|
||||||
protected override WebRequest CreateWebRequest() => new JsonWebRequest<T>(Uri);
|
protected override WebRequest CreateWebRequest() => new JsonWebRequest<T>(Uri);
|
||||||
|
|
||||||
|
public T Result => ((JsonWebRequest<T>)WebRequest).ResponseObject;
|
||||||
|
|
||||||
protected APIRequest()
|
protected APIRequest()
|
||||||
{
|
{
|
||||||
base.Success += onSuccess;
|
base.Success += onSuccess;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onSuccess()
|
private void onSuccess() => Success?.Invoke(Result);
|
||||||
{
|
|
||||||
Success?.Invoke(((JsonWebRequest<T>)WebRequest).ResponseObject);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Invoked on successful completion of an API request.
|
||||||
|
/// This will be scheduled to the API's internal scheduler (run on update thread automatically).
|
||||||
|
/// </summary>
|
||||||
public new event APISuccessHandler<T> Success;
|
public new event APISuccessHandler<T> Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -52,7 +55,16 @@ namespace osu.Game.Online.API
|
|||||||
protected APIAccess API;
|
protected APIAccess API;
|
||||||
protected WebRequest WebRequest;
|
protected WebRequest WebRequest;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Invoked on successful completion of an API request.
|
||||||
|
/// This will be scheduled to the API's internal scheduler (run on update thread automatically).
|
||||||
|
/// </summary>
|
||||||
public event APISuccessHandler Success;
|
public event APISuccessHandler Success;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Invoked on failure to complete an API request.
|
||||||
|
/// This will be scheduled to the API's internal scheduler (run on update thread automatically).
|
||||||
|
/// </summary>
|
||||||
public event APIFailureHandler Failure;
|
public event APIFailureHandler Failure;
|
||||||
|
|
||||||
private bool cancelled;
|
private bool cancelled;
|
||||||
|
@ -10,13 +10,11 @@ namespace osu.Game.Online.API.Requests
|
|||||||
{
|
{
|
||||||
private readonly BeatmapInfo beatmap;
|
private readonly BeatmapInfo beatmap;
|
||||||
|
|
||||||
private string lookupString => beatmap.OnlineBeatmapID > 0 ? beatmap.OnlineBeatmapID.ToString() : $@"lookup?checksum={beatmap.Hash}&filename={System.Uri.EscapeUriString(beatmap.Path)}";
|
|
||||||
|
|
||||||
public GetBeatmapDetailsRequest(BeatmapInfo beatmap)
|
public GetBeatmapDetailsRequest(BeatmapInfo beatmap)
|
||||||
{
|
{
|
||||||
this.beatmap = beatmap;
|
this.beatmap = beatmap;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override string Target => $@"beatmaps/{lookupString}";
|
protected override string Target => $@"beatmaps/{beatmap.OnlineBeatmapID}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
22
osu.Game/Online/API/Requests/GetBeatmapRequest.cs
Normal file
22
osu.Game/Online/API/Requests/GetBeatmapRequest.cs
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||||
|
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||||
|
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Online.API.Requests.Responses;
|
||||||
|
|
||||||
|
namespace osu.Game.Online.API.Requests
|
||||||
|
{
|
||||||
|
public class GetBeatmapRequest : APIRequest<APIBeatmap>
|
||||||
|
{
|
||||||
|
private readonly BeatmapInfo beatmap;
|
||||||
|
|
||||||
|
private string lookupString => beatmap.OnlineBeatmapID > 0 ? beatmap.OnlineBeatmapID.ToString() : $@"lookup?checksum={beatmap.MD5Hash}&filename={System.Uri.EscapeUriString(beatmap.Path)}";
|
||||||
|
|
||||||
|
public GetBeatmapRequest(BeatmapInfo beatmap)
|
||||||
|
{
|
||||||
|
this.beatmap = beatmap;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override string Target => $@"beatmaps/{lookupString}";
|
||||||
|
}
|
||||||
|
}
|
@ -10,7 +10,10 @@ namespace osu.Game.Online.API.Requests.Responses
|
|||||||
public class APIBeatmap : BeatmapMetadata
|
public class APIBeatmap : BeatmapMetadata
|
||||||
{
|
{
|
||||||
[JsonProperty(@"id")]
|
[JsonProperty(@"id")]
|
||||||
private int onlineBeatmapID { get; set; }
|
public int OnlineBeatmapID { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty(@"beatmapset_id")]
|
||||||
|
public int OnlineBeatmapSetID { get; set; }
|
||||||
|
|
||||||
[JsonProperty(@"playcount")]
|
[JsonProperty(@"playcount")]
|
||||||
private int playCount { get; set; }
|
private int playCount { get; set; }
|
||||||
@ -55,7 +58,11 @@ namespace osu.Game.Online.API.Requests.Responses
|
|||||||
Metadata = this,
|
Metadata = this,
|
||||||
Ruleset = rulesets.GetRuleset(ruleset),
|
Ruleset = rulesets.GetRuleset(ruleset),
|
||||||
StarDifficulty = starDifficulty,
|
StarDifficulty = starDifficulty,
|
||||||
OnlineBeatmapID = onlineBeatmapID,
|
OnlineBeatmapID = OnlineBeatmapID,
|
||||||
|
BeatmapSet = new BeatmapSetInfo
|
||||||
|
{
|
||||||
|
OnlineBeatmapSetID = OnlineBeatmapSetID,
|
||||||
|
},
|
||||||
Version = version,
|
Version = version,
|
||||||
BaseDifficulty = new BeatmapDifficulty
|
BaseDifficulty = new BeatmapDifficulty
|
||||||
{
|
{
|
||||||
|
@ -15,6 +15,15 @@ namespace osu.Game.Online.API.Requests.Responses
|
|||||||
[JsonProperty(@"covers")]
|
[JsonProperty(@"covers")]
|
||||||
private BeatmapSetOnlineCovers covers { get; set; }
|
private BeatmapSetOnlineCovers covers { get; set; }
|
||||||
|
|
||||||
|
private int? onlineBeatmapSetID;
|
||||||
|
|
||||||
|
[JsonProperty(@"id")]
|
||||||
|
public int? OnlineBeatmapSetID
|
||||||
|
{
|
||||||
|
get { return onlineBeatmapSetID; }
|
||||||
|
set { onlineBeatmapSetID = value > 0 ? value : null; }
|
||||||
|
}
|
||||||
|
|
||||||
[JsonProperty(@"preview_url")]
|
[JsonProperty(@"preview_url")]
|
||||||
private string preview { get; set; }
|
private string preview { get; set; }
|
||||||
|
|
||||||
|
@ -25,7 +25,6 @@ namespace osu.Game.Online.API.Requests.Responses
|
|||||||
{
|
{
|
||||||
BeatmapSetInfo setInfo = beatmapSet.ToBeatmapSet(rulesets);
|
BeatmapSetInfo setInfo = beatmapSet.ToBeatmapSet(rulesets);
|
||||||
beatmap.BeatmapSet = setInfo;
|
beatmap.BeatmapSet = setInfo;
|
||||||
beatmap.OnlineBeatmapSetID = setInfo.OnlineBeatmapSetID;
|
|
||||||
beatmap.Metadata = setInfo.Metadata;
|
beatmap.Metadata = setInfo.Metadata;
|
||||||
return beatmap;
|
return beatmap;
|
||||||
}
|
}
|
||||||
|
@ -101,6 +101,8 @@ namespace osu.Game
|
|||||||
public OsuGame(string[] args = null)
|
public OsuGame(string[] args = null)
|
||||||
{
|
{
|
||||||
this.args = args;
|
this.args = args;
|
||||||
|
|
||||||
|
forwardLoggedErrorsToNotifications();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ToggleSettings() => settings.ToggleVisibility();
|
public void ToggleSettings() => settings.ToggleVisibility();
|
||||||
@ -305,8 +307,6 @@ namespace osu.Game
|
|||||||
Depth = -6,
|
Depth = -6,
|
||||||
}, overlayContent.Add);
|
}, overlayContent.Add);
|
||||||
|
|
||||||
forwardLoggedErrorsToNotifications();
|
|
||||||
|
|
||||||
dependencies.Cache(settings);
|
dependencies.Cache(settings);
|
||||||
dependencies.Cache(onscreenDisplay);
|
dependencies.Cache(onscreenDisplay);
|
||||||
dependencies.Cache(social);
|
dependencies.Cache(social);
|
||||||
@ -394,31 +394,40 @@ namespace osu.Game
|
|||||||
|
|
||||||
private void forwardLoggedErrorsToNotifications()
|
private void forwardLoggedErrorsToNotifications()
|
||||||
{
|
{
|
||||||
int recentErrorCount = 0;
|
int recentLogCount = 0;
|
||||||
|
|
||||||
const double debounce = 5000;
|
const double debounce = 5000;
|
||||||
|
|
||||||
Logger.NewEntry += entry =>
|
Logger.NewEntry += entry =>
|
||||||
{
|
{
|
||||||
if (entry.Level < LogLevel.Error || entry.Target == null) return;
|
if (entry.Level < LogLevel.Important || entry.Target == null) return;
|
||||||
|
|
||||||
if (recentErrorCount < 2)
|
const int short_term_display_limit = 3;
|
||||||
|
|
||||||
|
if (recentLogCount < short_term_display_limit)
|
||||||
{
|
{
|
||||||
notifications.Post(new SimpleNotification
|
Schedule(() => notifications.Post(new SimpleNotification
|
||||||
{
|
{
|
||||||
Icon = FontAwesome.fa_bomb,
|
Icon = entry.Level == LogLevel.Important ? FontAwesome.fa_exclamation_circle : FontAwesome.fa_bomb,
|
||||||
Text = (recentErrorCount == 0 ? entry.Message : "Subsequent errors occurred and have been logged.") + "\nClick to view log files.",
|
Text = entry.Message,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
else if (recentLogCount == short_term_display_limit)
|
||||||
|
{
|
||||||
|
Schedule(() => notifications.Post(new SimpleNotification
|
||||||
|
{
|
||||||
|
Icon = FontAwesome.fa_ellipsis_h,
|
||||||
|
Text = "Subsequent messages have been logged. Click to view log files.",
|
||||||
Activated = () =>
|
Activated = () =>
|
||||||
{
|
{
|
||||||
Host.Storage.GetStorageForDirectory("logs").OpenInNativeExplorer();
|
Host.Storage.GetStorageForDirectory("logs").OpenInNativeExplorer();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
});
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
Interlocked.Increment(ref recentErrorCount);
|
Interlocked.Increment(ref recentLogCount);
|
||||||
|
Scheduler.AddDelayed(() => Interlocked.Decrement(ref recentLogCount), debounce);
|
||||||
Scheduler.AddDelayed(() => Interlocked.Decrement(ref recentErrorCount), debounce);
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,6 +21,7 @@ using osu.Game.Online.API;
|
|||||||
using osu.Framework.Graphics.Performance;
|
using osu.Framework.Graphics.Performance;
|
||||||
using osu.Framework.Graphics.Textures;
|
using osu.Framework.Graphics.Textures;
|
||||||
using osu.Framework.Logging;
|
using osu.Framework.Logging;
|
||||||
|
using osu.Game.Audio;
|
||||||
using osu.Game.Database;
|
using osu.Game.Database;
|
||||||
using osu.Game.Graphics.Textures;
|
using osu.Game.Graphics.Textures;
|
||||||
using osu.Game.Input;
|
using osu.Game.Input;
|
||||||
@ -56,6 +57,8 @@ namespace osu.Game
|
|||||||
|
|
||||||
protected SettingsStore SettingsStore;
|
protected SettingsStore SettingsStore;
|
||||||
|
|
||||||
|
protected RulesetConfigCache RulesetConfigCache;
|
||||||
|
|
||||||
protected MenuCursorContainer MenuCursorContainer;
|
protected MenuCursorContainer MenuCursorContainer;
|
||||||
|
|
||||||
private Container content;
|
private Container content;
|
||||||
@ -123,6 +126,7 @@ namespace osu.Game
|
|||||||
dependencies.Cache(ScoreStore = new ScoreStore(Host.Storage, contextFactory, Host, BeatmapManager, RulesetStore));
|
dependencies.Cache(ScoreStore = new ScoreStore(Host.Storage, contextFactory, Host, BeatmapManager, RulesetStore));
|
||||||
dependencies.Cache(KeyBindingStore = new KeyBindingStore(contextFactory, RulesetStore));
|
dependencies.Cache(KeyBindingStore = new KeyBindingStore(contextFactory, RulesetStore));
|
||||||
dependencies.Cache(SettingsStore = new SettingsStore(contextFactory));
|
dependencies.Cache(SettingsStore = new SettingsStore(contextFactory));
|
||||||
|
dependencies.Cache(RulesetConfigCache = new RulesetConfigCache(SettingsStore));
|
||||||
dependencies.Cache(new OsuColour());
|
dependencies.Cache(new OsuColour());
|
||||||
|
|
||||||
fileImporters.Add(BeatmapManager);
|
fileImporters.Add(BeatmapManager);
|
||||||
@ -184,6 +188,10 @@ namespace osu.Game
|
|||||||
|
|
||||||
KeyBindingStore.Register(globalBinding);
|
KeyBindingStore.Register(globalBinding);
|
||||||
dependencies.Cache(globalBinding);
|
dependencies.Cache(globalBinding);
|
||||||
|
|
||||||
|
PreviewTrackManager previewTrackManager;
|
||||||
|
dependencies.Cache(previewTrackManager = new PreviewTrackManager());
|
||||||
|
Add(previewTrackManager);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void LoadComplete()
|
protected override void LoadComplete()
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user