mirror of
https://github.com/ppy/osu.git
synced 2025-01-27 03:23:03 +08:00
Merge branch 'master' into catch-droplet-fix
This commit is contained in:
commit
f7f17bab78
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">
|
||||
<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="PROGRAM_PARAMETERS" value="" />
|
||||
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/osu.Game.Tests" />
|
||||
<option name="PASS_PARENT_ENVS" value="1" />
|
||||
<envs />
|
||||
<option name="USE_MONO" 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_EXE_PATH_TRACKING" value="1" />
|
||||
<option name="PROJECT_ARGUMENTS_TRACKING" value="1" />
|
@ -1,12 +1,11 @@
|
||||
<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="PROGRAM_PARAMETERS" value="" />
|
||||
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/osu.Desktop" />
|
||||
<option name="PASS_PARENT_ENVS" value="1" />
|
||||
<envs />
|
||||
<option name="USE_MONO" 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_EXE_PATH_TRACKING" value="1" />
|
||||
<option name="PROJECT_ARGUMENTS_TRACKING" value="1" />
|
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()
|
||||
: base(string.Empty)
|
||||
: base(string.Empty, null)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
@ -1,12 +1,13 @@
|
||||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using System.Diagnostics;
|
||||
using System;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Graphics.Textures;
|
||||
using osu.Framework.Platform;
|
||||
using osu.Game;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Graphics;
|
||||
@ -24,16 +25,18 @@ namespace osu.Desktop.Overlays
|
||||
private OsuConfigManager config;
|
||||
private OsuGameBase game;
|
||||
private NotificationOverlay notificationOverlay;
|
||||
private GameHost host;
|
||||
|
||||
public override bool HandleKeyboardInput => false;
|
||||
public override bool HandleMouseInput => false;
|
||||
|
||||
[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;
|
||||
this.config = config;
|
||||
this.game = game;
|
||||
this.host = host;
|
||||
|
||||
AutoSizeAxes = Axes.Both;
|
||||
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).
|
||||
if (!string.IsNullOrEmpty(lastVersion))
|
||||
notificationOverlay.Post(new UpdateCompleteNotification(version));
|
||||
notificationOverlay.Post(new UpdateCompleteNotification(version, host.OpenUrlExternally));
|
||||
}
|
||||
}
|
||||
|
||||
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!";
|
||||
Icon = FontAwesome.fa_check_square;
|
||||
Activated = delegate
|
||||
{
|
||||
Process.Start($"https://osu.ppy.sh/home/changelog/{version}");
|
||||
openUrl?.Invoke($"https://osu.ppy.sh/home/changelog/lazer/{version}");
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
@ -30,6 +30,7 @@
|
||||
</ItemGroup>
|
||||
<ItemGroup Label="Package References">
|
||||
<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'" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Label="Resources">
|
||||
|
@ -55,7 +55,7 @@ namespace osu.Game.Rulesets.Catch.Tests
|
||||
{
|
||||
}
|
||||
|
||||
public void ToggleHyperDash(bool status) => MovableCatcher.HyperDashModifier = status ? 2 : 1;
|
||||
public void ToggleHyperDash(bool status) => MovableCatcher.SetHyperdashState(status ? 2 : 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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 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;
|
||||
|
||||
|
@ -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>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Difficulty;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Catch.Objects;
|
||||
using osu.Game.Rulesets.Catch.UI;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Difficulty
|
||||
{
|
||||
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; }
|
||||
|
||||
/// <summary>
|
||||
/// The distance for a fruit to to next hyper if it's not a hyper.
|
||||
/// </summary>
|
||||
public float DistanceToHyperDash { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The next fruit starts a new combo. Used for explodey.
|
||||
/// </summary>
|
||||
|
@ -42,6 +42,8 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
|
||||
{
|
||||
public virtual bool CanBePlated => false;
|
||||
|
||||
public virtual bool StaysOnPlate => CanBePlated;
|
||||
|
||||
protected DrawableCatchHitObject(CatchHitObject hitObject)
|
||||
: base(hitObject)
|
||||
{
|
||||
|
@ -13,6 +13,8 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
|
||||
{
|
||||
private Pulp pulp;
|
||||
|
||||
public override bool StaysOnPlate => false;
|
||||
|
||||
public DrawableDroplet(Droplet h)
|
||||
: base(h)
|
||||
{
|
||||
|
@ -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>();
|
||||
|
||||
@ -42,7 +42,7 @@ namespace osu.Game.Rulesets.Catch.Replays
|
||||
else if (Position.Value < CurrentFrame.Position)
|
||||
actions.Add(CatchAction.MoveLeft);
|
||||
|
||||
return new List<InputState>
|
||||
return new List<IInput>
|
||||
{
|
||||
new CatchReplayState
|
||||
{
|
||||
|
@ -16,6 +16,7 @@ using osu.Game.Rulesets.Catch.Objects.Drawable;
|
||||
using osu.Game.Rulesets.Catch.Replays;
|
||||
using osu.Game.Rulesets.Judgements;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.UI;
|
||||
using OpenTK;
|
||||
using OpenTK.Graphics;
|
||||
|
||||
@ -48,6 +49,16 @@ namespace osu.Game.Rulesets.Catch.UI
|
||||
|
||||
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)
|
||||
{
|
||||
var caughtFruit = (DrawableCatchHitObject)GetVisualRepresentation?.Invoke(fruit.HitObject);
|
||||
@ -63,21 +74,17 @@ namespace osu.Game.Rulesets.Catch.UI
|
||||
caughtFruit.LifetimeEnd = double.MaxValue;
|
||||
|
||||
MovableCatcher.Add(caughtFruit);
|
||||
|
||||
lastPlateableFruit = caughtFruit;
|
||||
|
||||
if (!fruit.StaysOnPlate)
|
||||
runAfterLoaded(() => MovableCatcher.Explode(caughtFruit));
|
||||
|
||||
}
|
||||
|
||||
if (fruit.HitObject.LastInCombo)
|
||||
{
|
||||
if (judgement.IsHit)
|
||||
{
|
||||
// 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(); };
|
||||
}
|
||||
runAfterLoaded(() => MovableCatcher.Explode());
|
||||
else
|
||||
MovableCatcher.Drop();
|
||||
}
|
||||
@ -87,7 +94,7 @@ namespace osu.Game.Rulesets.Catch.UI
|
||||
{
|
||||
base.UpdateAfterChildren();
|
||||
|
||||
var state = GetContainingInputManager().CurrentState as CatchFramedReplayInputHandler.CatchReplayState;
|
||||
var state = (GetContainingInputManager().CurrentState as RulesetInputManagerInputState<CatchAction>)?.LastReplayState as CatchFramedReplayInputHandler.CatchReplayState;
|
||||
|
||||
if (state?.CatcherX != null)
|
||||
MovableCatcher.X = state.CatcherX.Value;
|
||||
@ -99,6 +106,11 @@ namespace osu.Game.Rulesets.Catch.UI
|
||||
|
||||
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;
|
||||
|
||||
public Container ExplodingFruitTarget;
|
||||
@ -226,63 +238,74 @@ namespace osu.Game.Rulesets.Catch.UI
|
||||
/// <returns>Whether the catch is possible.</returns>
|
||||
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.
|
||||
var catchObjectPosition = fruit.X * CatchPlayfield.BASE_WIDTH;
|
||||
var catcherPosition = Position.X * CatchPlayfield.BASE_WIDTH;
|
||||
|
||||
var validCatch =
|
||||
catchObjectPosition >= catcherPosition - halfCatcherWidth &&
|
||||
catchObjectPosition <= catcherPosition + halfCatcherWidth;
|
||||
catchObjectPosition >= catcherPosition - halfCatchWidth &&
|
||||
catchObjectPosition <= catcherPosition + halfCatchWidth;
|
||||
|
||||
if (validCatch && fruit.HyperDash)
|
||||
{
|
||||
HyperDashModifier = Math.Abs(fruit.HyperDashTarget.X - fruit.X) / Math.Abs(fruit.HyperDashTarget.StartTime - fruit.StartTime) / BASE_SPEED;
|
||||
HyperDashDirection = fruit.HyperDashTarget.X - fruit.X;
|
||||
var target = fruit.HyperDashTarget;
|
||||
double timeDifference = target.StartTime - fruit.StartTime;
|
||||
double positionDifference = target.X * CatchPlayfield.BASE_WIDTH - catcherPosition;
|
||||
double velocity = positionDifference / Math.Max(1.0, timeDifference - 1000.0 / 60.0);
|
||||
|
||||
SetHyperdashState(Math.Abs(velocity), target.X);
|
||||
}
|
||||
else
|
||||
HyperDashModifier = 1;
|
||||
{
|
||||
SetHyperdashState();
|
||||
}
|
||||
|
||||
return validCatch;
|
||||
}
|
||||
|
||||
private double hyperDashModifier = 1;
|
||||
private int hyperDashDirection;
|
||||
private float hyperDashTargetPosition;
|
||||
|
||||
/// <summary>
|
||||
/// Whether we are hypderdashing or not.
|
||||
/// </summary>
|
||||
public bool HyperDashing => hyperDashModifier != 1;
|
||||
|
||||
private double hyperDashModifier = 1;
|
||||
|
||||
/// <summary>
|
||||
/// The direction in which hyperdash is allowed. 0 allows both directions.
|
||||
/// Set hyperdash state.
|
||||
/// </summary>
|
||||
public double HyperDashDirection;
|
||||
|
||||
/// <summary>
|
||||
/// The speed modifier resultant from hyperdash. Will trigger hyperdash when not equal to 1.
|
||||
/// </summary>
|
||||
public double HyperDashModifier
|
||||
/// <param name="modifier">The speed multiplier. If this is less or equals to 1, this catcher will be non-hyperdashing state.</param>
|
||||
/// <param name="targetPosition">When this catcher crosses this position, this catcher ends hyperdashing.</param>
|
||||
public void SetHyperdashState(double modifier = 1, float targetPosition = -1)
|
||||
{
|
||||
get { return hyperDashModifier; }
|
||||
set
|
||||
const float hyperdash_transition_length = 180;
|
||||
|
||||
bool previouslyHyperDashing = HyperDashing;
|
||||
if (modifier <= 1 || X == targetPosition)
|
||||
{
|
||||
if (value == hyperDashModifier) return;
|
||||
hyperDashModifier = value;
|
||||
hyperDashModifier = 1;
|
||||
hyperDashDirection = 0;
|
||||
|
||||
const float transition_length = 180;
|
||||
|
||||
if (HyperDashing)
|
||||
if (previouslyHyperDashing)
|
||||
{
|
||||
this.FadeColour(Color4.OrangeRed, transition_length, Easing.OutQuint);
|
||||
this.FadeTo(0.2f, transition_length, Easing.OutQuint);
|
||||
Trail = true;
|
||||
this.FadeColour(Color4.White, hyperdash_transition_length, Easing.OutQuint);
|
||||
this.FadeTo(1, hyperdash_transition_length, Easing.OutQuint);
|
||||
}
|
||||
else
|
||||
}
|
||||
else
|
||||
{
|
||||
hyperDashModifier = modifier;
|
||||
hyperDashDirection = Math.Sign(targetPosition - X);
|
||||
hyperDashTargetPosition = targetPosition;
|
||||
|
||||
if (!previouslyHyperDashing)
|
||||
{
|
||||
HyperDashDirection = 0;
|
||||
this.FadeColour(Color4.White, transition_length, Easing.OutQuint);
|
||||
this.FadeTo(1, transition_length, Easing.OutQuint);
|
||||
this.FadeColour(Color4.OrangeRed, hyperdash_transition_length, Easing.OutQuint);
|
||||
this.FadeTo(0.2f, hyperdash_transition_length, Easing.OutQuint);
|
||||
Trail = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -337,12 +360,18 @@ namespace osu.Game.Rulesets.Catch.UI
|
||||
var direction = Math.Sign(currentDirection);
|
||||
|
||||
double dashModifier = Dashing ? 1 : 0.5;
|
||||
|
||||
if (hyperDashModifier != 1 && (HyperDashDirection == 0 || direction == Math.Sign(HyperDashDirection)))
|
||||
dashModifier = hyperDashModifier;
|
||||
double speed = BASE_SPEED * dashModifier * hyperDashModifier;
|
||||
|
||||
Scale = new Vector2(Math.Abs(Scale.X) * direction, Scale.Y);
|
||||
X = (float)MathHelper.Clamp(X + direction * Clock.ElapsedFrameTime * BASE_SPEED * dashModifier, 0, 1);
|
||||
X = (float)MathHelper.Clamp(X + direction * Clock.ElapsedFrameTime * speed, 0, 1);
|
||||
|
||||
// Correct overshooting.
|
||||
if (hyperDashDirection > 0 && hyperDashTargetPosition < X ||
|
||||
hyperDashDirection < 0 && hyperDashTargetPosition > X)
|
||||
{
|
||||
X = hyperDashTargetPosition;
|
||||
SetHyperdashState();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -378,28 +407,31 @@ namespace osu.Game.Rulesets.Catch.UI
|
||||
var fruit = caughtFruit.ToArray();
|
||||
|
||||
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)
|
||||
{
|
||||
f.Anchor = Anchor.TopLeft;
|
||||
f.Position = caughtFruit.ToSpaceOfOtherDrawable(f.DrawPosition, ExplodingFruitTarget);
|
||||
caughtFruit.Remove(fruit);
|
||||
|
||||
caughtFruit.Remove(f);
|
||||
|
||||
ExplodingFruitTarget.Add(f);
|
||||
}
|
||||
|
||||
f.MoveToY(f.Y - 50, 250, Easing.OutSine)
|
||||
.Then()
|
||||
.MoveToY(f.Y + 50, 500, Easing.InSine);
|
||||
|
||||
f.MoveToX(f.X + originalX * 6, 1000);
|
||||
f.FadeOut(750);
|
||||
|
||||
f.Expire();
|
||||
ExplodingFruitTarget.Add(fruit);
|
||||
}
|
||||
|
||||
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
|
||||
|
45
osu.Game.Rulesets.Mania.Tests/ManiaInputTestCase.cs
Normal file
45
osu.Game.Rulesets.Mania.Tests/ManiaInputTestCase.cs
Normal file
@ -0,0 +1,45 @@
|
||||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Input.Bindings;
|
||||
using osu.Game.Tests.Visual;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Tests
|
||||
{
|
||||
public abstract class ManiaInputTestCase : OsuTestCase
|
||||
{
|
||||
private readonly Container<Drawable> content;
|
||||
protected override Container<Drawable> Content => content ?? base.Content;
|
||||
|
||||
protected ManiaInputTestCase(int keys)
|
||||
{
|
||||
base.Content.Add(content = new LocalInputManager(keys));
|
||||
}
|
||||
|
||||
private class LocalInputManager : ManiaInputManager
|
||||
{
|
||||
public LocalInputManager(int variant)
|
||||
: base(new ManiaRuleset().RulesetInfo, variant)
|
||||
{
|
||||
}
|
||||
|
||||
protected override RulesetKeyBindingContainer CreateKeyBindingContainer(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique)
|
||||
=> new LocalKeyBindingContainer(ruleset, variant, unique);
|
||||
|
||||
private class LocalKeyBindingContainer : RulesetKeyBindingContainer
|
||||
{
|
||||
public LocalKeyBindingContainer(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique)
|
||||
: base(ruleset, variant, unique)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void ReloadMappings()
|
||||
{
|
||||
KeyBindings = DefaultKeyBindings;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
37
osu.Game.Rulesets.Mania.Tests/ScrollingTestContainer.cs
Normal file
37
osu.Game.Rulesets.Mania.Tests/ScrollingTestContainer.cs
Normal file
@ -0,0 +1,37 @@
|
||||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Configuration;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Rulesets.Mania.UI;
|
||||
using osu.Game.Rulesets.UI.Scrolling;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Tests
|
||||
{
|
||||
/// <summary>
|
||||
/// A container which provides a <see cref="IScrollingInfo"/> to children.
|
||||
/// </summary>
|
||||
public class ScrollingTestContainer : Container
|
||||
{
|
||||
private readonly ScrollingDirection direction;
|
||||
|
||||
public ScrollingTestContainer(ScrollingDirection direction)
|
||||
{
|
||||
this.direction = direction;
|
||||
}
|
||||
|
||||
protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent)
|
||||
{
|
||||
var dependencies = new DependencyContainer(base.CreateLocalDependencies(parent));
|
||||
dependencies.CacheAs<IScrollingInfo>(new ScrollingInfo { Direction = { Value = direction }});
|
||||
return dependencies;
|
||||
}
|
||||
|
||||
private class ScrollingInfo : IScrollingInfo
|
||||
{
|
||||
public readonly Bindable<ScrollingDirection> Direction = new Bindable<ScrollingDirection>();
|
||||
IBindable<ScrollingDirection> IScrollingInfo.Direction => Direction;
|
||||
}
|
||||
}
|
||||
}
|
111
osu.Game.Rulesets.Mania.Tests/TestCaseColumn.cs
Normal file
111
osu.Game.Rulesets.Mania.Tests/TestCaseColumn.cs
Normal file
@ -0,0 +1,111 @@
|
||||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Rulesets.Mania.Objects;
|
||||
using osu.Game.Rulesets.Mania.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Mania.UI;
|
||||
using osu.Game.Rulesets.Mania.UI.Components;
|
||||
using osu.Game.Rulesets.UI.Scrolling;
|
||||
using OpenTK;
|
||||
using OpenTK.Graphics;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Tests
|
||||
{
|
||||
[TestFixture]
|
||||
public class TestCaseColumn : ManiaInputTestCase
|
||||
{
|
||||
public override IReadOnlyList<Type> RequiredTypes => new[]
|
||||
{
|
||||
typeof(Column),
|
||||
typeof(ColumnBackground),
|
||||
typeof(ColumnKeyArea),
|
||||
typeof(ColumnHitObjectArea)
|
||||
};
|
||||
|
||||
private readonly List<Column> columns = new List<Column>();
|
||||
|
||||
public TestCaseColumn()
|
||||
: base(2)
|
||||
{
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
Child = new FillFlowContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Spacing = new Vector2(20, 0),
|
||||
Children = new[]
|
||||
{
|
||||
createColumn(ScrollingDirection.Up, ManiaAction.Key1),
|
||||
createColumn(ScrollingDirection.Down, ManiaAction.Key2)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
AddStep("note", createNote);
|
||||
AddStep("hold note", createHoldNote);
|
||||
}
|
||||
|
||||
private void createNote()
|
||||
{
|
||||
for (int i = 0; i < columns.Count; i++)
|
||||
{
|
||||
var obj = new Note { Column = i, StartTime = Time.Current + 2000 };
|
||||
obj.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
|
||||
|
||||
columns[i].Add(new DrawableNote(obj, columns[i].Action));
|
||||
}
|
||||
}
|
||||
|
||||
private void createHoldNote()
|
||||
{
|
||||
for (int i = 0; i < columns.Count; i++)
|
||||
{
|
||||
var obj = new HoldNote { Column = i, StartTime = Time.Current + 2000, Duration = 500 };
|
||||
obj.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
|
||||
|
||||
columns[i].Add(new DrawableHoldNote(obj, columns[i].Action));
|
||||
}
|
||||
}
|
||||
|
||||
private Drawable createColumn(ScrollingDirection direction, ManiaAction action)
|
||||
{
|
||||
var column = new Column(direction)
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Height = 0.85f,
|
||||
AccentColour = Color4.OrangeRed,
|
||||
Action = action,
|
||||
VisibleTimeRange = { Value = 2000 }
|
||||
};
|
||||
|
||||
columns.Add(column);
|
||||
|
||||
return new ScrollingTestContainer(direction)
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
AutoSizeAxes = Axes.X,
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
Child = column
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
@ -1,106 +0,0 @@
|
||||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Rulesets.Mania.Objects;
|
||||
using osu.Game.Rulesets.Mania.Objects.Drawables;
|
||||
using osu.Game.Tests.Visual;
|
||||
using OpenTK;
|
||||
using OpenTK.Graphics;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Tests
|
||||
{
|
||||
[TestFixture]
|
||||
public class TestCaseManiaHitObjects : OsuTestCase
|
||||
{
|
||||
public TestCaseManiaHitObjects()
|
||||
{
|
||||
Note note1 = new Note();
|
||||
Note note2 = new Note();
|
||||
HoldNote holdNote = new HoldNote { StartTime = 1000 };
|
||||
|
||||
note1.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
|
||||
note2.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
|
||||
holdNote.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
|
||||
|
||||
Add(new FillFlowContainer
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
Direction = FillDirection.Horizontal,
|
||||
Spacing = new Vector2(10, 0),
|
||||
// Imagine that the containers containing the drawable notes are the "columns"
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Container
|
||||
{
|
||||
Name = "Normal note column",
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
Width = 50,
|
||||
Children = new[]
|
||||
{
|
||||
new Container
|
||||
{
|
||||
Name = "Timing section",
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
RelativeChildSize = new Vector2(1, 10000),
|
||||
Children = new[]
|
||||
{
|
||||
new DrawableNote(note1, ManiaAction.Key1)
|
||||
{
|
||||
Y = 5000,
|
||||
LifetimeStart = double.MinValue,
|
||||
LifetimeEnd = double.MaxValue,
|
||||
AccentColour = Color4.Red
|
||||
},
|
||||
new DrawableNote(note2, ManiaAction.Key1)
|
||||
{
|
||||
Y = 6000,
|
||||
LifetimeStart = double.MinValue,
|
||||
LifetimeEnd = double.MaxValue,
|
||||
AccentColour = Color4.Red
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
new Container
|
||||
{
|
||||
Name = "Hold note column",
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
Width = 50,
|
||||
Children = new[]
|
||||
{
|
||||
new Container
|
||||
{
|
||||
Name = "Timing section",
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
RelativeChildSize = new Vector2(1, 10000),
|
||||
Children = new[]
|
||||
{
|
||||
new DrawableHoldNote(holdNote, ManiaAction.Key1)
|
||||
{
|
||||
Y = 5000,
|
||||
Height = 1000,
|
||||
LifetimeStart = double.MinValue,
|
||||
LifetimeEnd = double.MaxValue,
|
||||
AccentColour = Color4.Red,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@ -1,185 +0,0 @@
|
||||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Timing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Rulesets.Mania.Beatmaps;
|
||||
using osu.Game.Rulesets.Mania.Configuration;
|
||||
using osu.Game.Rulesets.Mania.Judgements;
|
||||
using osu.Game.Rulesets.Mania.Objects;
|
||||
using osu.Game.Rulesets.Mania.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Mania.UI;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Tests.Visual;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Tests
|
||||
{
|
||||
[TestFixture]
|
||||
public class TestCaseManiaPlayfield : OsuTestCase
|
||||
{
|
||||
private const double start_time = 500;
|
||||
private const double duration = 500;
|
||||
|
||||
protected override double TimePerAction => 200;
|
||||
|
||||
private RulesetInfo maniaRuleset;
|
||||
|
||||
public TestCaseManiaPlayfield()
|
||||
{
|
||||
var rng = new Random(1337);
|
||||
|
||||
AddStep("1 column", () => createPlayfield(1));
|
||||
AddStep("4 columns", () => createPlayfield(4));
|
||||
AddStep("5 columns", () => createPlayfield(5));
|
||||
AddStep("8 columns", () => createPlayfield(8));
|
||||
AddStep("4 + 4 columns", () =>
|
||||
{
|
||||
var stages = new List<StageDefinition>
|
||||
{
|
||||
new StageDefinition { Columns = 4 },
|
||||
new StageDefinition { Columns = 4 },
|
||||
};
|
||||
createPlayfield(stages);
|
||||
});
|
||||
|
||||
AddStep("2 + 4 + 2 columns", () =>
|
||||
{
|
||||
var stages = new List<StageDefinition>
|
||||
{
|
||||
new StageDefinition { Columns = 2 },
|
||||
new StageDefinition { Columns = 4 },
|
||||
new StageDefinition { Columns = 2 },
|
||||
};
|
||||
createPlayfield(stages);
|
||||
});
|
||||
|
||||
AddStep("1 + 8 + 1 columns", () =>
|
||||
{
|
||||
var stages = new List<StageDefinition>
|
||||
{
|
||||
new StageDefinition { Columns = 1 },
|
||||
new StageDefinition { Columns = 8 },
|
||||
new StageDefinition { Columns = 1 },
|
||||
};
|
||||
createPlayfield(stages);
|
||||
});
|
||||
|
||||
AddStep("Reversed", () => createPlayfield(4, true));
|
||||
|
||||
AddStep("Notes with input", () => createPlayfieldWithNotes());
|
||||
AddStep("Notes with input (reversed)", () => createPlayfieldWithNotes(true));
|
||||
AddStep("Notes with gravity", () => createPlayfieldWithNotes());
|
||||
AddStep("Notes with gravity (reversed)", () => createPlayfieldWithNotes(true));
|
||||
|
||||
AddStep("Hit explosion", () =>
|
||||
{
|
||||
var playfield = createPlayfield(4);
|
||||
|
||||
int col = rng.Next(0, 4);
|
||||
|
||||
var note = new Note { Column = col };
|
||||
note.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
|
||||
|
||||
var drawableNote = new DrawableNote(note, ManiaAction.Key1)
|
||||
{
|
||||
AccentColour = playfield.Columns.ElementAt(col).AccentColour
|
||||
};
|
||||
|
||||
playfield.OnJudgement(drawableNote, new ManiaJudgement { Result = HitResult.Perfect });
|
||||
playfield.Columns[col].OnJudgement(drawableNote, new ManiaJudgement { Result = HitResult.Perfect });
|
||||
});
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(RulesetStore rulesets, SettingsStore settings)
|
||||
{
|
||||
maniaRuleset = rulesets.GetRuleset(3);
|
||||
|
||||
Dependencies.Cache(new ManiaConfigManager(settings, maniaRuleset, 4));
|
||||
}
|
||||
|
||||
private ManiaPlayfield createPlayfield(int cols, bool inverted = false)
|
||||
{
|
||||
var stages = new List<StageDefinition>
|
||||
{
|
||||
new StageDefinition { Columns = cols },
|
||||
};
|
||||
|
||||
return createPlayfield(stages, inverted);
|
||||
}
|
||||
|
||||
private ManiaPlayfield createPlayfield(List<StageDefinition> stages, bool inverted = false)
|
||||
{
|
||||
Clear();
|
||||
|
||||
var inputManager = new ManiaInputManager(maniaRuleset, stages.Sum(g => g.Columns)) { RelativeSizeAxes = Axes.Both };
|
||||
Add(inputManager);
|
||||
|
||||
ManiaPlayfield playfield;
|
||||
|
||||
inputManager.Add(playfield = new ManiaPlayfield(stages)
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
});
|
||||
|
||||
playfield.Inverted.Value = inverted;
|
||||
|
||||
return playfield;
|
||||
}
|
||||
|
||||
private void createPlayfieldWithNotes(bool inverted = false)
|
||||
{
|
||||
Clear();
|
||||
|
||||
var rateAdjustClock = new StopwatchClock(true) { Rate = 1 };
|
||||
|
||||
var inputManager = new ManiaInputManager(maniaRuleset, 4) { RelativeSizeAxes = Axes.Both };
|
||||
Add(inputManager);
|
||||
|
||||
ManiaPlayfield playfield;
|
||||
var stages = new List<StageDefinition>
|
||||
{
|
||||
new StageDefinition { Columns = 4 },
|
||||
};
|
||||
|
||||
inputManager.Add(playfield = new ManiaPlayfield(stages)
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Clock = new FramedClock(rateAdjustClock)
|
||||
});
|
||||
|
||||
playfield.Inverted.Value = inverted;
|
||||
|
||||
for (double t = start_time; t <= start_time + duration; t += 100)
|
||||
{
|
||||
var note1 = new Note { StartTime = t, Column = 0 };
|
||||
var note2 = new Note { StartTime = t, Column = 3 };
|
||||
|
||||
note1.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
|
||||
note2.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
|
||||
|
||||
playfield.Add(new DrawableNote(note1, ManiaAction.Key1));
|
||||
playfield.Add(new DrawableNote(note2, ManiaAction.Key4));
|
||||
}
|
||||
|
||||
var holdNote1 = new HoldNote { StartTime = start_time, Duration = duration, Column = 1 };
|
||||
var holdNote2 = new HoldNote { StartTime = start_time, Duration = duration, Column = 2 };
|
||||
|
||||
holdNote1.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
|
||||
holdNote2.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
|
||||
|
||||
playfield.Add(new DrawableHoldNote(holdNote1, ManiaAction.Key2));
|
||||
playfield.Add(new DrawableHoldNote(holdNote2, ManiaAction.Key3));
|
||||
}
|
||||
}
|
||||
}
|
168
osu.Game.Rulesets.Mania.Tests/TestCaseNotes.cs
Normal file
168
osu.Game.Rulesets.Mania.Tests/TestCaseNotes.cs
Normal file
@ -0,0 +1,168 @@
|
||||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Rulesets.Mania.Objects;
|
||||
using osu.Game.Rulesets.Mania.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
using osu.Game.Rulesets.UI.Scrolling;
|
||||
using osu.Game.Tests.Visual;
|
||||
using OpenTK;
|
||||
using OpenTK.Graphics;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Tests
|
||||
{
|
||||
[TestFixture]
|
||||
public class TestCaseNotes : OsuTestCase
|
||||
{
|
||||
public override IReadOnlyList<Type> RequiredTypes => new[]
|
||||
{
|
||||
typeof(DrawableNote),
|
||||
typeof(DrawableHoldNote)
|
||||
};
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
Child = new FillFlowContainer
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Direction = FillDirection.Horizontal,
|
||||
Spacing = new Vector2(20),
|
||||
Children = new[]
|
||||
{
|
||||
createNoteDisplay(ScrollingDirection.Down),
|
||||
createNoteDisplay(ScrollingDirection.Up),
|
||||
createHoldNoteDisplay(ScrollingDirection.Down),
|
||||
createHoldNoteDisplay(ScrollingDirection.Up),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private Drawable createNoteDisplay(ScrollingDirection direction)
|
||||
{
|
||||
var note = new Note { StartTime = 999999999 };
|
||||
note.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
|
||||
|
||||
return new ScrollingTestContainer(direction)
|
||||
{
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Child = new NoteContainer(direction, $"note, scrolling {direction.ToString().ToLower()}")
|
||||
{
|
||||
Child = new DrawableNote(note, ManiaAction.Key1) { AccentColour = Color4.OrangeRed }
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private Drawable createHoldNoteDisplay(ScrollingDirection direction)
|
||||
{
|
||||
var note = new HoldNote { StartTime = 999999999, Duration = 1000 };
|
||||
note.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
|
||||
|
||||
return new ScrollingTestContainer(direction)
|
||||
{
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Child = new NoteContainer(direction, $"hold note, scrolling {direction.ToString().ToLower()}")
|
||||
{
|
||||
Child = new DrawableHoldNote(note, ManiaAction.Key1)
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
AccentColour = Color4.OrangeRed,
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private class NoteContainer : Container
|
||||
{
|
||||
private readonly Container content;
|
||||
protected override Container<Drawable> Content => content;
|
||||
|
||||
private readonly ScrollingDirection direction;
|
||||
|
||||
public NoteContainer(ScrollingDirection direction, string description)
|
||||
{
|
||||
this.direction = direction;
|
||||
AutoSizeAxes = Axes.Both;
|
||||
|
||||
InternalChild = new FillFlowContainer
|
||||
{
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Spacing = new Vector2(0, 10),
|
||||
Direction = FillDirection.Vertical,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Container
|
||||
{
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre,
|
||||
Width = 45,
|
||||
Height = 100,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Box
|
||||
{
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Width = 1.25f,
|
||||
Colour = Color4.Black.Opacity(0.5f)
|
||||
},
|
||||
content = new Container { RelativeSizeAxes = Axes.Both }
|
||||
}
|
||||
},
|
||||
new SpriteText
|
||||
{
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre,
|
||||
TextSize = 14,
|
||||
Text = description
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
|
||||
foreach (var obj in content.OfType<DrawableHitObject>())
|
||||
{
|
||||
if (!(obj.HitObject is IHasEndTime endTime))
|
||||
continue;
|
||||
|
||||
if (!obj.HasNestedHitObjects)
|
||||
continue;
|
||||
|
||||
foreach (var nested in obj.NestedHitObjects)
|
||||
{
|
||||
double finalPosition = (nested.HitObject.StartTime - obj.HitObject.StartTime) / endTime.Duration;
|
||||
switch (direction)
|
||||
{
|
||||
case ScrollingDirection.Up:
|
||||
nested.Y = (float)(finalPosition * content.DrawHeight);
|
||||
break;
|
||||
case ScrollingDirection.Down:
|
||||
nested.Y = (float)(-finalPosition * content.DrawHeight);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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())
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
121
osu.Game.Rulesets.Mania.Tests/TestCaseStage.cs
Normal file
121
osu.Game.Rulesets.Mania.Tests/TestCaseStage.cs
Normal file
@ -0,0 +1,121 @@
|
||||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using System.Collections.Generic;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Rulesets.Mania.Beatmaps;
|
||||
using osu.Game.Rulesets.Mania.Objects;
|
||||
using osu.Game.Rulesets.Mania.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Mania.UI;
|
||||
using osu.Game.Rulesets.UI.Scrolling;
|
||||
using OpenTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Tests
|
||||
{
|
||||
[TestFixture]
|
||||
public class TestCaseStage : ManiaInputTestCase
|
||||
{
|
||||
private const int columns = 4;
|
||||
|
||||
private readonly List<ManiaStage> stages = new List<ManiaStage>();
|
||||
|
||||
public TestCaseStage()
|
||||
: base(columns)
|
||||
{
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
Child = new FillFlowContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Spacing = new Vector2(20, 0),
|
||||
Children = new[]
|
||||
{
|
||||
createStage(ScrollingDirection.Up, ManiaAction.Key1),
|
||||
createStage(ScrollingDirection.Down, ManiaAction.Key3)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
AddStep("note", createNote);
|
||||
AddStep("hold note", createHoldNote);
|
||||
AddStep("minor bar line", () => createBarLine(false));
|
||||
AddStep("major bar line", () => createBarLine(true));
|
||||
}
|
||||
|
||||
private void createNote()
|
||||
{
|
||||
foreach (var stage in stages)
|
||||
{
|
||||
for (int i = 0; i < stage.Columns.Count; i++)
|
||||
{
|
||||
var obj = new Note { Column = i, StartTime = Time.Current + 2000 };
|
||||
obj.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
|
||||
|
||||
stage.Add(new DrawableNote(obj, stage.Columns[i].Action));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void createHoldNote()
|
||||
{
|
||||
foreach (var stage in stages)
|
||||
{
|
||||
for (int i = 0; i < stage.Columns.Count; i++)
|
||||
{
|
||||
var obj = new HoldNote { Column = i, StartTime = Time.Current + 2000, Duration = 500 };
|
||||
obj.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
|
||||
|
||||
stage.Add(new DrawableHoldNote(obj, stage.Columns[i].Action));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void createBarLine(bool major)
|
||||
{
|
||||
foreach (var stage in stages)
|
||||
{
|
||||
var obj = new BarLine
|
||||
{
|
||||
StartTime = Time.Current + 2000,
|
||||
ControlPoint = new TimingControlPoint(),
|
||||
BeatIndex = major ? 0 : 1
|
||||
};
|
||||
|
||||
obj.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
|
||||
|
||||
stage.Add(obj);
|
||||
}
|
||||
}
|
||||
|
||||
private Drawable createStage(ScrollingDirection direction, ManiaAction action)
|
||||
{
|
||||
var specialAction = ManiaAction.Special1;
|
||||
|
||||
var stage = new ManiaStage(direction, 0, new StageDefinition { Columns = 2 }, ref action, ref specialAction) { VisibleTimeRange = { Value = 2000 } };
|
||||
stages.Add(stage);
|
||||
|
||||
return new ScrollingTestContainer(direction)
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
AutoSizeAxes = Axes.X,
|
||||
Child = stage
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
@ -58,6 +58,13 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
||||
|
||||
public override Pattern Generate()
|
||||
{
|
||||
if (TotalColumns == 1)
|
||||
{
|
||||
var pattern = new Pattern();
|
||||
addToPattern(pattern, 0, HitObject.StartTime, endTime);
|
||||
return pattern;
|
||||
}
|
||||
|
||||
if (spanCount > 1)
|
||||
{
|
||||
if (segmentDuration <= 90)
|
||||
@ -322,7 +329,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
||||
break;
|
||||
}
|
||||
|
||||
bool isDoubleSample(SampleInfo sample) => sample.Name == SampleInfo.HIT_CLAP && sample.Name == SampleInfo.HIT_FINISH;
|
||||
bool isDoubleSample(SampleInfo sample) => sample.Name == SampleInfo.HIT_CLAP || sample.Name == SampleInfo.HIT_FINISH;
|
||||
|
||||
bool canGenerateTwoNotes = (convertType & PatternType.LowProbability) == 0;
|
||||
canGenerateTwoNotes &= HitObject.Samples.Any(isDoubleSample) || sampleInfoListAt(HitObject.StartTime).Any(isDoubleSample);
|
||||
|
@ -77,10 +77,25 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
||||
}
|
||||
else
|
||||
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()
|
||||
{
|
||||
if (TotalColumns == 1)
|
||||
{
|
||||
var pattern = new Pattern();
|
||||
addToPattern(pattern, 0);
|
||||
return pattern;
|
||||
}
|
||||
|
||||
int lastColumn = PreviousPattern.HitObjects.FirstOrDefault()?.Column ?? 0;
|
||||
|
||||
if ((convertType & PatternType.Reverse) > 0 && PreviousPattern.HitObjects.Any())
|
||||
@ -346,7 +361,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
||||
addToCentre = false;
|
||||
|
||||
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)
|
||||
{
|
||||
|
@ -4,6 +4,7 @@
|
||||
using osu.Framework.Configuration.Tracking;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Rulesets.Configuration;
|
||||
using osu.Game.Rulesets.Mania.UI;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Configuration
|
||||
{
|
||||
@ -19,6 +20,7 @@ namespace osu.Game.Rulesets.Mania.Configuration
|
||||
base.InitialiseDefaults();
|
||||
|
||||
Set(ManiaSetting.ScrollTime, 1500.0, 50.0, 10000.0, 50.0);
|
||||
Set(ManiaSetting.ScrollDirection, ManiaScrollingDirection.Down);
|
||||
}
|
||||
|
||||
public override TrackedSettings CreateTrackedSettings() => new TrackedSettings
|
||||
@ -29,6 +31,7 @@ namespace osu.Game.Rulesets.Mania.Configuration
|
||||
|
||||
public enum ManiaSetting
|
||||
{
|
||||
ScrollTime
|
||||
ScrollTime,
|
||||
ScrollDirection
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,18 @@
|
||||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using osu.Game.Rulesets.Difficulty;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Difficulty
|
||||
{
|
||||
public class ManiaDifficultyAttributes : DifficultyAttributes
|
||||
{
|
||||
public double GreatHitWindow;
|
||||
|
||||
public ManiaDifficultyAttributes(Mod[] mods, double starRating)
|
||||
: base(mods, starRating)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
@ -29,47 +29,44 @@ namespace osu.Game.Rulesets.Mania.Difficulty
|
||||
/// </summary>
|
||||
private const double decay_weight = 0.9;
|
||||
|
||||
/// <summary>
|
||||
/// HitObjects are stored as a member variable.
|
||||
/// </summary>
|
||||
private readonly List<ManiaHitObjectDifficulty> difficultyHitObjects = new List<ManiaHitObjectDifficulty>();
|
||||
private readonly bool isForCurrentRuleset;
|
||||
|
||||
public ManiaDifficultyCalculator(IBeatmap beatmap)
|
||||
: base(beatmap)
|
||||
public ManiaDifficultyCalculator(Ruleset ruleset, WorkingBeatmap beatmap)
|
||||
: base(ruleset, beatmap)
|
||||
{
|
||||
isForCurrentRuleset = beatmap.BeatmapInfo.Ruleset.Equals(ruleset.RulesetInfo);
|
||||
}
|
||||
|
||||
public ManiaDifficultyCalculator(IBeatmap beatmap, Mod[] mods)
|
||||
: base(beatmap, mods)
|
||||
protected override DifficultyAttributes Calculate(IBeatmap beatmap, Mod[] mods, double timeRate)
|
||||
{
|
||||
}
|
||||
if (!beatmap.HitObjects.Any())
|
||||
return new ManiaDifficultyAttributes(mods, 0);
|
||||
|
||||
public override double Calculate(Dictionary<string, double> categoryDifficulty = null)
|
||||
{
|
||||
// Fill our custom DifficultyHitObject class, that carries additional information
|
||||
difficultyHitObjects.Clear();
|
||||
var difficultyHitObjects = new List<ManiaHitObjectDifficulty>();
|
||||
|
||||
int columnCount = (Beatmap as ManiaBeatmap)?.TotalColumns ?? 7;
|
||||
int columnCount = ((ManiaBeatmap)beatmap).TotalColumns;
|
||||
|
||||
// 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
|
||||
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())
|
||||
return 0;
|
||||
if (!calculateStrainValues(difficultyHitObjects, timeRate))
|
||||
return new DifficultyAttributes(mods, 0);
|
||||
|
||||
double starRating = calculateDifficulty() * star_scaling_factor;
|
||||
|
||||
if (categoryDifficulty != null)
|
||||
categoryDifficulty["Strain"] = starRating;
|
||||
double starRating = calculateDifficulty(difficultyHitObjects, timeRate) * star_scaling_factor;
|
||||
|
||||
return starRating;
|
||||
return new ManiaDifficultyAttributes(mods, starRating)
|
||||
{
|
||||
// Todo: This int cast is temporary to achieve 1:1 results with osu!stable, and should be remoevd in the future
|
||||
GreatHitWindow = (int)(beatmap.HitObjects.First().HitWindows.Great / 2) / timeRate
|
||||
};
|
||||
}
|
||||
|
||||
private bool calculateStrainValues()
|
||||
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.
|
||||
using (List<ManiaHitObjectDifficulty>.Enumerator hitObjectsEnumerator = difficultyHitObjects.GetEnumerator())
|
||||
using (var hitObjectsEnumerator = objects.GetEnumerator())
|
||||
{
|
||||
if (!hitObjectsEnumerator.MoveNext())
|
||||
return false;
|
||||
@ -80,7 +77,7 @@ namespace osu.Game.Rulesets.Mania.Difficulty
|
||||
while (hitObjectsEnumerator.MoveNext())
|
||||
{
|
||||
var next = hitObjectsEnumerator.Current;
|
||||
next?.CalculateStrains(current, TimeRate);
|
||||
next?.CalculateStrains(current, timeRate);
|
||||
current = next;
|
||||
}
|
||||
|
||||
@ -88,9 +85,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
|
||||
List<double> highestStrains = new List<double>();
|
||||
@ -98,7 +95,7 @@ namespace osu.Game.Rulesets.Mania.Difficulty
|
||||
double maximumStrain = 0; // We need to keep track of the maximum strain in the current interval
|
||||
|
||||
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 (hitObject.BaseHitObject.StartTime > intervalEndTime)
|
||||
@ -143,21 +140,35 @@ namespace osu.Game.Rulesets.Mania.Difficulty
|
||||
return difficulty;
|
||||
}
|
||||
|
||||
protected override Mod[] DifficultyAdjustmentMods => new Mod[]
|
||||
protected override Mod[] DifficultyAdjustmentMods
|
||||
{
|
||||
new ManiaModDoubleTime(),
|
||||
new ManiaModHalfTime(),
|
||||
new ManiaModEasy(),
|
||||
new ManiaModHardRock(),
|
||||
new ManiaModKey1(),
|
||||
new ManiaModKey2(),
|
||||
new ManiaModKey3(),
|
||||
new ManiaModKey4(),
|
||||
new ManiaModKey5(),
|
||||
new ManiaModKey6(),
|
||||
new ManiaModKey7(),
|
||||
new ManiaModKey8(),
|
||||
new ManiaModKey9(),
|
||||
};
|
||||
get
|
||||
{
|
||||
var mods = new Mod[]
|
||||
{
|
||||
new ManiaModDoubleTime(),
|
||||
new ManiaModHalfTime(),
|
||||
new ManiaModEasy(),
|
||||
new ManiaModHardRock(),
|
||||
};
|
||||
|
||||
if (isForCurrentRuleset)
|
||||
return mods;
|
||||
|
||||
// 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -13,6 +13,8 @@ namespace osu.Game.Rulesets.Mania.Difficulty
|
||||
{
|
||||
public class ManiaPerformanceCalculator : PerformanceCalculator
|
||||
{
|
||||
protected new ManiaDifficultyAttributes Attributes => (ManiaDifficultyAttributes)base.Attributes;
|
||||
|
||||
private Mod[] mods;
|
||||
|
||||
// Score after being scaled by non-difficulty-increasing mods
|
||||
@ -25,7 +27,7 @@ namespace osu.Game.Rulesets.Mania.Difficulty
|
||||
private int countMeh;
|
||||
private int countMiss;
|
||||
|
||||
public ManiaPerformanceCalculator(Ruleset ruleset, IBeatmap beatmap, Score score)
|
||||
public ManiaPerformanceCalculator(Ruleset ruleset, WorkingBeatmap beatmap, Score score)
|
||||
: base(ruleset, beatmap, score)
|
||||
{
|
||||
}
|
||||
@ -82,7 +84,7 @@ namespace osu.Game.Rulesets.Mania.Difficulty
|
||||
private double computeStrainValue()
|
||||
{
|
||||
// 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
|
||||
strainValue *= 1.0 + 0.1 * Math.Min(1.0, totalHits / 1500.0);
|
||||
@ -105,14 +107,12 @@ namespace osu.Game.Rulesets.Mania.Difficulty
|
||||
|
||||
private double computeAccuracyValue(double strainValue)
|
||||
{
|
||||
// Todo: This int cast is temporary to achieve 1:1 results with osu!stable, and should be remoevd in the future
|
||||
double hitWindowGreat = (int)(Beatmap.HitObjects.First().HitWindows.Great / 2) / TimeRate;
|
||||
if (hitWindowGreat <= 0)
|
||||
if (Attributes.GreatHitWindow <= 0)
|
||||
return 0;
|
||||
|
||||
// Lots of arbitrary values from testing.
|
||||
// Considering to use derivation from perfect accuracy in a probabilistic manner - assume normal distribution
|
||||
double accuracyValue = Math.Max(0.0, 0.2 - (hitWindowGreat - 34) * 0.006667)
|
||||
double accuracyValue = Math.Max(0.0, 0.2 - (Attributes.GreatHitWindow - 34) * 0.006667)
|
||||
* strainValue
|
||||
* Math.Pow(Math.Max(0.0, scaledScore - 960000) / 40000, 1.1);
|
||||
|
||||
|
@ -16,6 +16,7 @@ using osu.Game.Rulesets.Mania.Replays;
|
||||
using osu.Game.Rulesets.Replays.Types;
|
||||
using osu.Game.Beatmaps.Legacy;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Overlays.Settings;
|
||||
using osu.Game.Rulesets.Configuration;
|
||||
using osu.Game.Rulesets.Difficulty;
|
||||
using osu.Game.Rulesets.Mania.Beatmaps;
|
||||
@ -29,7 +30,7 @@ namespace osu.Game.Rulesets.Mania
|
||||
{
|
||||
public override RulesetContainer CreateRulesetContainerWith(WorkingBeatmap beatmap) => new ManiaRulesetContainer(this, 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)
|
||||
{
|
||||
@ -147,7 +148,7 @@ namespace osu.Game.Rulesets.Mania
|
||||
|
||||
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;
|
||||
|
||||
@ -155,6 +156,8 @@ namespace osu.Game.Rulesets.Mania
|
||||
|
||||
public override IRulesetConfigManager CreateConfig(SettingsStore settings) => new ManiaConfigManager(settings, RulesetInfo);
|
||||
|
||||
public override RulesetSettingsSubsection CreateSettings() => new ManiaSettingsSubsection(this);
|
||||
|
||||
public ManiaRuleset(RulesetInfo rulesetInfo = null)
|
||||
: base(rulesetInfo)
|
||||
{
|
||||
|
34
osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs
Normal file
34
osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs
Normal file
@ -0,0 +1,34 @@
|
||||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Overlays.Settings;
|
||||
using osu.Game.Rulesets.Mania.Configuration;
|
||||
using osu.Game.Rulesets.Mania.UI;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania
|
||||
{
|
||||
public class ManiaSettingsSubsection : RulesetSettingsSubsection
|
||||
{
|
||||
protected override string Header => "osu!mania";
|
||||
|
||||
public ManiaSettingsSubsection(ManiaRuleset ruleset)
|
||||
: base(ruleset)
|
||||
{
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(ManiaConfigManager config)
|
||||
{
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new SettingsEnumDropdown<ManiaScrollingDirection>
|
||||
{
|
||||
LabelText = "Scrolling direction",
|
||||
Bindable = config.GetBindable<ManiaScrollingDirection>(ManiaSetting.ScrollDirection)
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
@ -10,6 +10,7 @@ using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Rulesets.Mania.Judgements;
|
||||
using osu.Framework.Input.Bindings;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Rulesets.UI.Scrolling;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
||||
{
|
||||
@ -75,6 +76,13 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
||||
AddNested(tail);
|
||||
}
|
||||
|
||||
protected override void OnDirectionChanged(ScrollingDirection direction)
|
||||
{
|
||||
base.OnDirectionChanged(direction);
|
||||
|
||||
bodyPiece.Anchor = bodyPiece.Origin = direction == ScrollingDirection.Up ? Anchor.TopLeft : Anchor.BottomLeft;
|
||||
}
|
||||
|
||||
public override Color4 AccentColour
|
||||
{
|
||||
get { return base.AccentColour; }
|
||||
@ -100,7 +108,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
||||
base.Update();
|
||||
|
||||
// Make the body piece not lie under the head note
|
||||
bodyPiece.Y = head.Height / 2;
|
||||
bodyPiece.Y = (Direction.Value == ScrollingDirection.Up ? 1 : -1) * head.Height / 2;
|
||||
bodyPiece.Height = DrawHeight - head.Height / 2 + tail.Height / 2;
|
||||
}
|
||||
|
||||
|
@ -1,8 +1,12 @@
|
||||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Configuration;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Rulesets.Mania.UI;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.UI.Scrolling;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
||||
{
|
||||
@ -16,18 +20,29 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
||||
|
||||
public new TObject HitObject;
|
||||
|
||||
protected readonly IBindable<ScrollingDirection> Direction = new Bindable<ScrollingDirection>();
|
||||
|
||||
protected DrawableManiaHitObject(TObject hitObject, ManiaAction? action = null)
|
||||
: base(hitObject)
|
||||
{
|
||||
Anchor = Anchor.TopCentre;
|
||||
Origin = Anchor.TopCentre;
|
||||
|
||||
HitObject = hitObject;
|
||||
|
||||
if (action != null)
|
||||
Action = action.Value;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(IScrollingInfo scrollingInfo)
|
||||
{
|
||||
Direction.BindTo(scrollingInfo.Direction);
|
||||
Direction.BindValueChanged(OnDirectionChanged, true);
|
||||
}
|
||||
|
||||
protected virtual void OnDirectionChanged(ScrollingDirection direction)
|
||||
{
|
||||
Anchor = Origin = direction == ScrollingDirection.Up ? Anchor.TopCentre : Anchor.BottomCentre;
|
||||
}
|
||||
|
||||
protected override void UpdateState(ArmedState state)
|
||||
{
|
||||
switch (state)
|
||||
|
@ -9,6 +9,7 @@ using osu.Framework.Input.Bindings;
|
||||
using osu.Game.Rulesets.Mania.Judgements;
|
||||
using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Rulesets.UI.Scrolling;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
||||
{
|
||||
@ -28,14 +29,14 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
||||
CornerRadius = 5;
|
||||
Masking = true;
|
||||
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
headPiece = new NotePiece
|
||||
{
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre
|
||||
}
|
||||
};
|
||||
InternalChild = headPiece = new NotePiece();
|
||||
}
|
||||
|
||||
protected override void OnDirectionChanged(ScrollingDirection direction)
|
||||
{
|
||||
base.OnDirectionChanged(direction);
|
||||
|
||||
headPiece.Anchor = headPiece.Origin = direction == ScrollingDirection.Up ? Anchor.TopCentre : Anchor.BottomCentre;
|
||||
}
|
||||
|
||||
public override Color4 AccentColour
|
||||
|
@ -1,12 +1,16 @@
|
||||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Configuration;
|
||||
using OpenTK.Graphics;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Rulesets.Mania.UI;
|
||||
using osu.Game.Rulesets.UI.Scrolling;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces
|
||||
{
|
||||
@ -18,6 +22,8 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces
|
||||
public const float NOTE_HEIGHT = 10;
|
||||
private const float head_colour_height = 6;
|
||||
|
||||
private readonly IBindable<ScrollingDirection> direction = new Bindable<ScrollingDirection>();
|
||||
|
||||
private readonly Box colouredBox;
|
||||
|
||||
public NotePiece()
|
||||
@ -33,8 +39,6 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces
|
||||
},
|
||||
colouredBox = new Box
|
||||
{
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Height = head_colour_height,
|
||||
Alpha = 0.2f
|
||||
@ -42,6 +46,16 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces
|
||||
};
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(IScrollingInfo scrollingInfo)
|
||||
{
|
||||
direction.BindTo(scrollingInfo.Direction);
|
||||
direction.BindValueChanged(direction =>
|
||||
{
|
||||
colouredBox.Anchor = colouredBox.Origin = direction == ScrollingDirection.Up ? Anchor.TopCentre : Anchor.BottomCentre;
|
||||
}, true);
|
||||
}
|
||||
|
||||
private Color4 accentColour;
|
||||
public Color4 AccentColour
|
||||
{
|
||||
|
@ -17,6 +17,6 @@ namespace osu.Game.Rulesets.Mania.Replays
|
||||
|
||||
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 } };
|
||||
}
|
||||
}
|
||||
|
@ -1,50 +1,51 @@
|
||||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using OpenTK;
|
||||
using OpenTK.Graphics;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Graphics.Colour;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using osu.Framework.Input.Bindings;
|
||||
using osu.Game.Rulesets.Judgements;
|
||||
using osu.Game.Rulesets.Mania.UI.Components;
|
||||
using osu.Game.Rulesets.UI.Scrolling;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.UI
|
||||
{
|
||||
public class Column : ScrollingPlayfield, IKeyBindingHandler<ManiaAction>, IHasAccentColour
|
||||
public class Column : ManiaScrollingPlayfield, IKeyBindingHandler<ManiaAction>, IHasAccentColour
|
||||
{
|
||||
private const float key_icon_size = 10;
|
||||
private const float key_icon_corner_radius = 3;
|
||||
private const float key_icon_border_radius = 2;
|
||||
|
||||
private const float hit_target_height = 10;
|
||||
private const float hit_target_bar_height = 2;
|
||||
|
||||
private const float column_width = 45;
|
||||
private const float special_column_width = 70;
|
||||
|
||||
public ManiaAction Action;
|
||||
private ManiaAction action;
|
||||
|
||||
private readonly Box background;
|
||||
private readonly Box backgroundOverlay;
|
||||
private readonly Container hitTargetBar;
|
||||
private readonly Container keyIcon;
|
||||
public ManiaAction Action
|
||||
{
|
||||
get => action;
|
||||
set
|
||||
{
|
||||
if (action == value)
|
||||
return;
|
||||
action = value;
|
||||
|
||||
background.Action = value;
|
||||
keyArea.Action = value;
|
||||
}
|
||||
}
|
||||
|
||||
private readonly ColumnBackground background;
|
||||
private readonly ColumnKeyArea keyArea;
|
||||
private readonly ColumnHitObjectArea hitObjectArea;
|
||||
|
||||
internal readonly Container TopLevelContainer;
|
||||
private readonly Container explosionContainer;
|
||||
|
||||
protected override Container<Drawable> Content => content;
|
||||
private readonly Container<Drawable> content;
|
||||
protected override Container<Drawable> Content => hitObjectArea;
|
||||
|
||||
public Column()
|
||||
: base(ScrollingDirection.Up)
|
||||
public Column(ScrollingDirection direction)
|
||||
: base(direction)
|
||||
{
|
||||
RelativeSizeAxes = Axes.Y;
|
||||
Width = column_width;
|
||||
@ -52,71 +53,21 @@ namespace osu.Game.Rulesets.Mania.UI
|
||||
Masking = true;
|
||||
CornerRadius = 5;
|
||||
|
||||
InternalChildren = new Drawable[]
|
||||
background = new ColumnBackground { RelativeSizeAxes = Axes.Both };
|
||||
|
||||
Container hitTargetContainer;
|
||||
|
||||
InternalChildren = new[]
|
||||
{
|
||||
background = new Box
|
||||
{
|
||||
Name = "Background",
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Alpha = 0.3f
|
||||
},
|
||||
backgroundOverlay = new Box
|
||||
{
|
||||
Name = "Background Gradient Overlay",
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Height = 0.5f,
|
||||
Anchor = Anchor.TopLeft,
|
||||
Origin = Anchor.TopLeft,
|
||||
Blending = BlendingMode.Additive,
|
||||
Alpha = 0
|
||||
},
|
||||
new Container
|
||||
// For input purposes, the background is added at the highest depth, but is then proxied back below all other elements
|
||||
background.CreateProxy(),
|
||||
hitTargetContainer = new Container
|
||||
{
|
||||
Name = "Hit target + hit objects",
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Padding = new MarginPadding { Top = ManiaStage.HIT_TARGET_POSITION },
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Container
|
||||
{
|
||||
Name = "Hit target",
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Height = hit_target_height,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Box
|
||||
{
|
||||
Name = "Background",
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = Color4.Black
|
||||
},
|
||||
hitTargetBar = new Container
|
||||
{
|
||||
Name = "Bar",
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Height = hit_target_bar_height,
|
||||
Masking = true,
|
||||
Children = new[]
|
||||
{
|
||||
new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
content = new Container
|
||||
{
|
||||
Name = "Hit objects",
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
},
|
||||
// For column lighting, we need to capture input events before the notes
|
||||
new InputTarget
|
||||
{
|
||||
Pressed = onPressed,
|
||||
Released = onReleased
|
||||
},
|
||||
hitObjectArea = new ColumnHitObjectArea { RelativeSizeAxes = Axes.Both },
|
||||
explosionContainer = new Container
|
||||
{
|
||||
Name = "Hit explosions",
|
||||
@ -124,46 +75,27 @@ namespace osu.Game.Rulesets.Mania.UI
|
||||
}
|
||||
}
|
||||
},
|
||||
new Container
|
||||
keyArea = new ColumnKeyArea
|
||||
{
|
||||
Name = "Key",
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Height = ManiaStage.HIT_TARGET_POSITION,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Box
|
||||
{
|
||||
Name = "Key gradient",
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = ColourInfo.GradientVertical(Color4.Black, Color4.Black.Opacity(0)),
|
||||
Alpha = 0.5f
|
||||
},
|
||||
keyIcon = new Container
|
||||
{
|
||||
Name = "Key icon",
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Size = new Vector2(key_icon_size),
|
||||
Masking = true,
|
||||
CornerRadius = key_icon_corner_radius,
|
||||
BorderThickness = 2,
|
||||
BorderColour = Color4.White, // Not true
|
||||
Children = new[]
|
||||
{
|
||||
new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Alpha = 0,
|
||||
AlwaysPresent = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
background,
|
||||
TopLevelContainer = new Container { RelativeSizeAxes = Axes.Both }
|
||||
};
|
||||
|
||||
TopLevelContainer.Add(explosionContainer.CreateProxy());
|
||||
|
||||
Direction.BindValueChanged(d =>
|
||||
{
|
||||
hitTargetContainer.Padding = new MarginPadding
|
||||
{
|
||||
Top = d == ScrollingDirection.Up ? ManiaStage.HIT_TARGET_POSITION : 0,
|
||||
Bottom = d == ScrollingDirection.Down ? ManiaStage.HIT_TARGET_POSITION : 0,
|
||||
};
|
||||
|
||||
keyArea.Anchor = keyArea.Origin= d == ScrollingDirection.Up ? Anchor.TopLeft : Anchor.BottomLeft;
|
||||
}, true);
|
||||
}
|
||||
|
||||
public override Axes RelativeSizeAxes => Axes.Y;
|
||||
@ -192,22 +124,9 @@ namespace osu.Game.Rulesets.Mania.UI
|
||||
return;
|
||||
accentColour = value;
|
||||
|
||||
background.Colour = accentColour;
|
||||
backgroundOverlay.Colour = ColourInfo.GradientVertical(accentColour.Opacity(0.6f), accentColour.Opacity(0));
|
||||
|
||||
hitTargetBar.EdgeEffect = new EdgeEffectParameters
|
||||
{
|
||||
Type = EdgeEffectType.Glow,
|
||||
Radius = 5,
|
||||
Colour = accentColour.Opacity(0.5f),
|
||||
};
|
||||
|
||||
keyIcon.EdgeEffect = new EdgeEffectParameters
|
||||
{
|
||||
Type = EdgeEffectType.Glow,
|
||||
Radius = 5,
|
||||
Colour = accentColour.Opacity(0.5f),
|
||||
};
|
||||
background.AccentColour = value;
|
||||
keyArea.AccentColour = value;
|
||||
hitObjectArea.AccentColour = value;
|
||||
}
|
||||
}
|
||||
|
||||
@ -228,48 +147,10 @@ namespace osu.Game.Rulesets.Mania.UI
|
||||
if (!judgement.IsHit || !judgedObject.DisplayJudgement)
|
||||
return;
|
||||
|
||||
explosionContainer.Add(new HitExplosion(judgedObject));
|
||||
}
|
||||
|
||||
private bool onPressed(ManiaAction action)
|
||||
{
|
||||
if (action == Action)
|
||||
explosionContainer.Add(new HitExplosion(judgedObject)
|
||||
{
|
||||
backgroundOverlay.FadeTo(1, 50, Easing.OutQuint).Then().FadeTo(0.5f, 250, Easing.OutQuint);
|
||||
keyIcon.ScaleTo(1.4f, 50, Easing.OutQuint).Then().ScaleTo(1.3f, 250, Easing.OutQuint);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool onReleased(ManiaAction action)
|
||||
{
|
||||
if (action == Action)
|
||||
{
|
||||
backgroundOverlay.FadeTo(0, 250, Easing.OutQuint);
|
||||
keyIcon.ScaleTo(1f, 125, Easing.OutQuint);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This is a simple container which delegates various input events that have to be captured before the notes.
|
||||
/// </summary>
|
||||
private class InputTarget : Container, IKeyBindingHandler<ManiaAction>
|
||||
{
|
||||
public Func<ManiaAction, bool> Pressed;
|
||||
public Func<ManiaAction, bool> Released;
|
||||
|
||||
public InputTarget()
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
AlwaysPresent = true;
|
||||
Alpha = 0;
|
||||
}
|
||||
|
||||
public bool OnPressed(ManiaAction action) => Pressed?.Invoke(action) ?? false;
|
||||
public bool OnReleased(ManiaAction action) => Released?.Invoke(action) ?? false;
|
||||
Anchor = Direction == ScrollingDirection.Up ? Anchor.TopCentre : Anchor.BottomCentre
|
||||
});
|
||||
}
|
||||
|
||||
public bool OnPressed(ManiaAction action)
|
||||
|
106
osu.Game.Rulesets.Mania/UI/Components/ColumnBackground.cs
Normal file
106
osu.Game.Rulesets.Mania/UI/Components/ColumnBackground.cs
Normal file
@ -0,0 +1,106 @@
|
||||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Configuration;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Colour;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Input.Bindings;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Rulesets.UI.Scrolling;
|
||||
using OpenTK.Graphics;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.UI.Components
|
||||
{
|
||||
public class ColumnBackground : CompositeDrawable, IKeyBindingHandler<ManiaAction>, IHasAccentColour
|
||||
{
|
||||
public ManiaAction Action;
|
||||
|
||||
private Box background;
|
||||
private Box backgroundOverlay;
|
||||
|
||||
private readonly IBindable<ScrollingDirection> direction = new Bindable<ScrollingDirection>();
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(IScrollingInfo scrollingInfo)
|
||||
{
|
||||
InternalChildren = new[]
|
||||
{
|
||||
background = new Box
|
||||
{
|
||||
Name = "Background",
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Alpha = 0.3f
|
||||
},
|
||||
backgroundOverlay = new Box
|
||||
{
|
||||
Name = "Background Gradient Overlay",
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Height = 0.5f,
|
||||
Blending = BlendingMode.Additive,
|
||||
Alpha = 0
|
||||
}
|
||||
};
|
||||
|
||||
direction.BindTo(scrollingInfo.Direction);
|
||||
direction.BindValueChanged(direction =>
|
||||
{
|
||||
backgroundOverlay.Anchor = backgroundOverlay.Origin = direction == ScrollingDirection.Up ? Anchor.TopLeft : Anchor.BottomLeft;
|
||||
updateColours();
|
||||
}, true);
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
updateColours();
|
||||
}
|
||||
|
||||
private Color4 accentColour;
|
||||
|
||||
public Color4 AccentColour
|
||||
{
|
||||
get => accentColour;
|
||||
set
|
||||
{
|
||||
if (accentColour == value)
|
||||
return;
|
||||
accentColour = value;
|
||||
|
||||
updateColours();
|
||||
}
|
||||
}
|
||||
|
||||
private void updateColours()
|
||||
{
|
||||
if (!IsLoaded)
|
||||
return;
|
||||
|
||||
background.Colour = AccentColour;
|
||||
|
||||
var brightPoint = AccentColour.Opacity(0.6f);
|
||||
var dimPoint = AccentColour.Opacity(0);
|
||||
|
||||
backgroundOverlay.Colour = ColourInfo.GradientVertical(
|
||||
direction.Value == ScrollingDirection.Up ? brightPoint : dimPoint,
|
||||
direction.Value == ScrollingDirection.Up ? dimPoint : brightPoint);
|
||||
}
|
||||
|
||||
public bool OnPressed(ManiaAction action)
|
||||
{
|
||||
if (action == Action)
|
||||
backgroundOverlay.FadeTo(1, 50, Easing.OutQuint).Then().FadeTo(0.5f, 250, Easing.OutQuint);
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool OnReleased(ManiaAction action)
|
||||
{
|
||||
if (action == Action)
|
||||
backgroundOverlay.FadeTo(0, 250, Easing.OutQuint);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
99
osu.Game.Rulesets.Mania/UI/Components/ColumnHitObjectArea.cs
Normal file
99
osu.Game.Rulesets.Mania/UI/Components/ColumnHitObjectArea.cs
Normal file
@ -0,0 +1,99 @@
|
||||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Configuration;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Rulesets.UI.Scrolling;
|
||||
using OpenTK.Graphics;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.UI.Components
|
||||
{
|
||||
public class ColumnHitObjectArea : Container, IHasAccentColour
|
||||
{
|
||||
private const float hit_target_height = 10;
|
||||
private const float hit_target_bar_height = 2;
|
||||
|
||||
private Container<Drawable> content;
|
||||
protected override Container<Drawable> Content => content;
|
||||
|
||||
private readonly IBindable<ScrollingDirection> direction = new Bindable<ScrollingDirection>();
|
||||
|
||||
private Container hitTargetLine;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(IScrollingInfo scrollingInfo)
|
||||
{
|
||||
Drawable hitTargetBar;
|
||||
|
||||
InternalChildren = new[]
|
||||
{
|
||||
hitTargetBar = new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Height = hit_target_height,
|
||||
Colour = Color4.Black
|
||||
},
|
||||
hitTargetLine = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Height = hit_target_bar_height,
|
||||
Masking = true,
|
||||
Child = new Box { RelativeSizeAxes = Axes.Both }
|
||||
},
|
||||
content = new Container
|
||||
{
|
||||
Name = "Hit objects",
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
},
|
||||
};
|
||||
|
||||
direction.BindTo(scrollingInfo.Direction);
|
||||
direction.BindValueChanged(direction =>
|
||||
{
|
||||
Anchor anchor = direction == ScrollingDirection.Up ? Anchor.TopLeft : Anchor.BottomLeft;
|
||||
|
||||
hitTargetBar.Anchor = hitTargetBar.Origin = anchor;
|
||||
hitTargetLine.Anchor = hitTargetLine.Origin = anchor;
|
||||
}, true);
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
updateColours();
|
||||
}
|
||||
|
||||
private Color4 accentColour;
|
||||
|
||||
public Color4 AccentColour
|
||||
{
|
||||
get => accentColour;
|
||||
set
|
||||
{
|
||||
if (accentColour == value)
|
||||
return;
|
||||
accentColour = value;
|
||||
|
||||
updateColours();
|
||||
}
|
||||
}
|
||||
|
||||
private void updateColours()
|
||||
{
|
||||
if (!IsLoaded)
|
||||
return;
|
||||
|
||||
hitTargetLine.EdgeEffect = new EdgeEffectParameters
|
||||
{
|
||||
Type = EdgeEffectType.Glow,
|
||||
Radius = 5,
|
||||
Colour = accentColour.Opacity(0.5f),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
122
osu.Game.Rulesets.Mania/UI/Components/ColumnKeyArea.cs
Normal file
122
osu.Game.Rulesets.Mania/UI/Components/ColumnKeyArea.cs
Normal file
@ -0,0 +1,122 @@
|
||||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Configuration;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Colour;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Input.Bindings;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Rulesets.UI.Scrolling;
|
||||
using OpenTK;
|
||||
using OpenTK.Graphics;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.UI.Components
|
||||
{
|
||||
public class ColumnKeyArea : CompositeDrawable, IKeyBindingHandler<ManiaAction>, IHasAccentColour
|
||||
{
|
||||
private const float key_icon_size = 10;
|
||||
private const float key_icon_corner_radius = 3;
|
||||
|
||||
public ManiaAction Action;
|
||||
|
||||
private readonly IBindable<ScrollingDirection> direction = new Bindable<ScrollingDirection>();
|
||||
|
||||
private Container keyIcon;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(IScrollingInfo scrollingInfo)
|
||||
{
|
||||
Drawable gradient;
|
||||
|
||||
InternalChildren = new[]
|
||||
{
|
||||
gradient = new Box
|
||||
{
|
||||
Name = "Key gradient",
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Alpha = 0.5f
|
||||
},
|
||||
keyIcon = new Container
|
||||
{
|
||||
Name = "Key icon",
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Size = new Vector2(key_icon_size),
|
||||
Masking = true,
|
||||
CornerRadius = key_icon_corner_radius,
|
||||
BorderThickness = 2,
|
||||
BorderColour = Color4.White, // Not true
|
||||
Children = new[]
|
||||
{
|
||||
new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Alpha = 0,
|
||||
AlwaysPresent = true
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
direction.BindTo(scrollingInfo.Direction);
|
||||
direction.BindValueChanged(direction =>
|
||||
{
|
||||
gradient.Colour = ColourInfo.GradientVertical(
|
||||
direction == ScrollingDirection.Up ? Color4.Black : Color4.Black.Opacity(0),
|
||||
direction == ScrollingDirection.Up ? Color4.Black.Opacity(0) : Color4.Black);
|
||||
}, true);
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
updateColours();
|
||||
}
|
||||
|
||||
private Color4 accentColour;
|
||||
|
||||
public Color4 AccentColour
|
||||
{
|
||||
get => accentColour;
|
||||
set
|
||||
{
|
||||
if (accentColour == value)
|
||||
return;
|
||||
accentColour = value;
|
||||
|
||||
updateColours();
|
||||
}
|
||||
}
|
||||
|
||||
private void updateColours()
|
||||
{
|
||||
if (!IsLoaded)
|
||||
return;
|
||||
|
||||
keyIcon.EdgeEffect = new EdgeEffectParameters
|
||||
{
|
||||
Type = EdgeEffectType.Glow,
|
||||
Radius = 5,
|
||||
Colour = accentColour.Opacity(0.5f),
|
||||
};
|
||||
}
|
||||
|
||||
public bool OnPressed(ManiaAction action)
|
||||
{
|
||||
if (action == Action)
|
||||
keyIcon.ScaleTo(1.4f, 50, Easing.OutQuint).Then().ScaleTo(1.3f, 250, Easing.OutQuint);
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool OnReleased(ManiaAction action)
|
||||
{
|
||||
if (action == Action)
|
||||
keyIcon.ScaleTo(1f, 125, Easing.OutQuint);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
@ -15,13 +15,14 @@ namespace osu.Game.Rulesets.Mania.UI
|
||||
{
|
||||
internal class HitExplosion : CompositeDrawable
|
||||
{
|
||||
public override bool RemoveWhenNotAlive => true;
|
||||
|
||||
private readonly CircularContainer circle;
|
||||
|
||||
public HitExplosion(DrawableHitObject judgedObject)
|
||||
{
|
||||
bool isTick = judgedObject is DrawableHoldNoteTick;
|
||||
|
||||
Anchor = Anchor.TopCentre;
|
||||
Origin = Anchor.Centre;
|
||||
|
||||
RelativeSizeAxes = Axes.X;
|
||||
|
17
osu.Game.Rulesets.Mania/UI/IScrollingInfo.cs
Normal file
17
osu.Game.Rulesets.Mania/UI/IScrollingInfo.cs
Normal file
@ -0,0 +1,17 @@
|
||||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using osu.Framework.Configuration;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.UI.Scrolling;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.UI
|
||||
{
|
||||
public interface IScrollingInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// The direction <see cref="HitObject"/>s should scroll in.
|
||||
/// </summary>
|
||||
IBindable<ScrollingDirection> Direction { get; }
|
||||
}
|
||||
}
|
@ -8,7 +8,6 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Configuration;
|
||||
using osu.Game.Rulesets.Judgements;
|
||||
using osu.Game.Rulesets.Mania.Beatmaps;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
@ -17,18 +16,13 @@ using osu.Game.Rulesets.UI.Scrolling;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.UI
|
||||
{
|
||||
public class ManiaPlayfield : ScrollingPlayfield
|
||||
public class ManiaPlayfield : ManiaScrollingPlayfield
|
||||
{
|
||||
/// <summary>
|
||||
/// Whether this playfield should be inverted. This flips everything inside the playfield.
|
||||
/// </summary>
|
||||
public readonly Bindable<bool> Inverted = new Bindable<bool>(true);
|
||||
|
||||
public List<Column> Columns => stages.SelectMany(x => x.Columns).ToList();
|
||||
private readonly List<ManiaStage> stages = new List<ManiaStage>();
|
||||
|
||||
public ManiaPlayfield(List<StageDefinition> stageDefinitions)
|
||||
: base(ScrollingDirection.Up)
|
||||
public ManiaPlayfield(ScrollingDirection direction, List<StageDefinition> stageDefinitions)
|
||||
: base(direction)
|
||||
{
|
||||
if (stageDefinitions == null)
|
||||
throw new ArgumentNullException(nameof(stageDefinitions));
|
||||
@ -36,8 +30,6 @@ namespace osu.Game.Rulesets.Mania.UI
|
||||
if (stageDefinitions.Count <= 0)
|
||||
throw new ArgumentException("Can't have zero or fewer stages.");
|
||||
|
||||
Inverted.Value = true;
|
||||
|
||||
GridContainer playfieldGrid;
|
||||
InternalChild = playfieldGrid = new GridContainer
|
||||
{
|
||||
@ -50,9 +42,8 @@ namespace osu.Game.Rulesets.Mania.UI
|
||||
int firstColumnIndex = 0;
|
||||
for (int i = 0; i < stageDefinitions.Count; i++)
|
||||
{
|
||||
var newStage = new ManiaStage(firstColumnIndex, stageDefinitions[i], ref normalColumnAction, ref specialColumnAction);
|
||||
var newStage = new ManiaStage(direction, firstColumnIndex, stageDefinitions[i], ref normalColumnAction, ref specialColumnAction);
|
||||
newStage.VisibleTimeRange.BindTo(VisibleTimeRange);
|
||||
newStage.Inverted.BindTo(Inverted);
|
||||
|
||||
playfieldGrid.Content[0][i] = newStage;
|
||||
|
||||
|
@ -4,6 +4,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Configuration;
|
||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Input;
|
||||
@ -12,6 +13,7 @@ using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Input.Handlers;
|
||||
using osu.Game.Rulesets.Mania.Beatmaps;
|
||||
using osu.Game.Rulesets.Mania.Configuration;
|
||||
using osu.Game.Rulesets.Mania.Mods;
|
||||
using osu.Game.Rulesets.Mania.Objects;
|
||||
using osu.Game.Rulesets.Mania.Objects.Drawables;
|
||||
@ -33,6 +35,9 @@ namespace osu.Game.Rulesets.Mania.UI
|
||||
|
||||
public IEnumerable<BarLine> BarLines;
|
||||
|
||||
private readonly Bindable<ManiaScrollingDirection> configDirection = new Bindable<ManiaScrollingDirection>();
|
||||
private ScrollingInfo scrollingInfo;
|
||||
|
||||
public ManiaRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap)
|
||||
: base(ruleset, beatmap)
|
||||
{
|
||||
@ -65,12 +70,24 @@ namespace osu.Game.Rulesets.Mania.UI
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
private void load(ManiaConfigManager config)
|
||||
{
|
||||
BarLines.ForEach(Playfield.Add);
|
||||
|
||||
config.BindWith(ManiaSetting.ScrollDirection, configDirection);
|
||||
configDirection.BindValueChanged(d => scrollingInfo.Direction.Value = (ScrollingDirection)d, true);
|
||||
}
|
||||
|
||||
protected sealed override Playfield CreatePlayfield() => new ManiaPlayfield(Beatmap.Stages)
|
||||
private DependencyContainer dependencies;
|
||||
|
||||
protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent)
|
||||
{
|
||||
dependencies = new DependencyContainer(base.CreateLocalDependencies(parent));
|
||||
dependencies.CacheAs<IScrollingInfo>(scrollingInfo = new ScrollingInfo());
|
||||
return dependencies;
|
||||
}
|
||||
|
||||
protected sealed override Playfield CreatePlayfield() => new ManiaPlayfield(scrollingInfo.Direction, Beatmap.Stages)
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
@ -100,5 +117,11 @@ namespace osu.Game.Rulesets.Mania.UI
|
||||
protected override Vector2 PlayfieldArea => new Vector2(1, 0.8f);
|
||||
|
||||
protected override ReplayInputHandler CreateReplayInputHandler(Replay replay) => new ManiaFramedReplayInputHandler(replay);
|
||||
|
||||
private class ScrollingInfo : IScrollingInfo
|
||||
{
|
||||
public readonly Bindable<ScrollingDirection> Direction = new Bindable<ScrollingDirection>();
|
||||
IBindable<ScrollingDirection> IScrollingInfo.Direction => Direction;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
13
osu.Game.Rulesets.Mania/UI/ManiaScrollingDirection.cs
Normal file
13
osu.Game.Rulesets.Mania/UI/ManiaScrollingDirection.cs
Normal file
@ -0,0 +1,13 @@
|
||||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using osu.Game.Rulesets.UI.Scrolling;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.UI
|
||||
{
|
||||
public enum ManiaScrollingDirection
|
||||
{
|
||||
Up = ScrollingDirection.Up,
|
||||
Down = ScrollingDirection.Down
|
||||
}
|
||||
}
|
26
osu.Game.Rulesets.Mania/UI/ManiaScrollingPlayfield.cs
Normal file
26
osu.Game.Rulesets.Mania/UI/ManiaScrollingPlayfield.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.Framework.Allocation;
|
||||
using osu.Framework.Configuration;
|
||||
using osu.Game.Rulesets.UI.Scrolling;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.UI
|
||||
{
|
||||
public class ManiaScrollingPlayfield : ScrollingPlayfield
|
||||
{
|
||||
private readonly IBindable<ScrollingDirection> direction = new Bindable<ScrollingDirection>();
|
||||
|
||||
public ManiaScrollingPlayfield(ScrollingDirection direction)
|
||||
: base(direction)
|
||||
{
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(IScrollingInfo scrollingInfo)
|
||||
{
|
||||
direction.BindTo(scrollingInfo.Direction);
|
||||
direction.BindValueChanged(direction => Direction.Value = direction, true);
|
||||
}
|
||||
}
|
||||
}
|
@ -5,7 +5,6 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Configuration;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
@ -24,20 +23,15 @@ namespace osu.Game.Rulesets.Mania.UI
|
||||
/// <summary>
|
||||
/// A collection of <see cref="Column"/>s.
|
||||
/// </summary>
|
||||
internal class ManiaStage : ScrollingPlayfield
|
||||
internal class ManiaStage : ManiaScrollingPlayfield
|
||||
{
|
||||
public const float HIT_TARGET_POSITION = 50;
|
||||
|
||||
/// <summary>
|
||||
/// Whether this playfield should be inverted. This flips everything inside the playfield.
|
||||
/// </summary>
|
||||
public readonly Bindable<bool> Inverted = new Bindable<bool>(true);
|
||||
|
||||
public IReadOnlyList<Column> Columns => columnFlow.Children;
|
||||
private readonly FillFlowContainer<Column> columnFlow;
|
||||
|
||||
protected override Container<Drawable> Content => content;
|
||||
private readonly Container<Drawable> content;
|
||||
protected override Container<Drawable> Content => barLineContainer;
|
||||
private readonly Container<Drawable> barLineContainer;
|
||||
|
||||
public Container<DrawableManiaJudgement> Judgements => judgements;
|
||||
private readonly JudgementContainer<DrawableManiaJudgement> judgements;
|
||||
@ -49,8 +43,8 @@ namespace osu.Game.Rulesets.Mania.UI
|
||||
|
||||
private readonly int firstColumnIndex;
|
||||
|
||||
public ManiaStage(int firstColumnIndex, StageDefinition definition, ref ManiaAction normalColumnStartAction, ref ManiaAction specialColumnStartAction)
|
||||
: base(ScrollingDirection.Up)
|
||||
public ManiaStage(ScrollingDirection direction, int firstColumnIndex, StageDefinition definition, ref ManiaAction normalColumnStartAction, ref ManiaAction specialColumnStartAction)
|
||||
: base(direction)
|
||||
{
|
||||
this.firstColumnIndex = firstColumnIndex;
|
||||
|
||||
@ -106,13 +100,12 @@ namespace osu.Game.Rulesets.Mania.UI
|
||||
Width = 1366, // Bar lines should only be masked on the vertical axis
|
||||
BypassAutoSizeAxes = Axes.Both,
|
||||
Masking = true,
|
||||
Child = content = new Container
|
||||
Child = barLineContainer = new Container
|
||||
{
|
||||
Name = "Bar lines",
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre,
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
Padding = new MarginPadding { Top = HIT_TARGET_POSITION }
|
||||
}
|
||||
},
|
||||
judgements = new JudgementContainer<DrawableManiaJudgement>
|
||||
@ -131,7 +124,7 @@ namespace osu.Game.Rulesets.Mania.UI
|
||||
for (int i = 0; i < definition.Columns; i++)
|
||||
{
|
||||
var isSpecial = definition.IsSpecialColumn(i);
|
||||
var column = new Column
|
||||
var column = new Column(direction)
|
||||
{
|
||||
IsSpecial = isSpecial,
|
||||
Action = isSpecial ? specialColumnStartAction++ : normalColumnStartAction++
|
||||
@ -140,14 +133,14 @@ namespace osu.Game.Rulesets.Mania.UI
|
||||
AddColumn(column);
|
||||
}
|
||||
|
||||
Inverted.ValueChanged += invertedChanged;
|
||||
Inverted.TriggerChange();
|
||||
}
|
||||
|
||||
private void invertedChanged(bool newValue)
|
||||
{
|
||||
Scale = new Vector2(1, newValue ? -1 : 1);
|
||||
Judgements.Scale = Scale;
|
||||
Direction.BindValueChanged(d =>
|
||||
{
|
||||
barLineContainer.Padding = new MarginPadding
|
||||
{
|
||||
Top = d == ScrollingDirection.Up ? HIT_TARGET_POSITION : 0,
|
||||
Bottom = d == ScrollingDirection.Down ? HIT_TARGET_POSITION : 0,
|
||||
};
|
||||
}, true);
|
||||
}
|
||||
|
||||
public void AddColumn(Column c)
|
||||
@ -218,7 +211,7 @@ namespace osu.Game.Rulesets.Mania.UI
|
||||
{
|
||||
// Due to masking differences, it is not possible to get the width of the columns container automatically
|
||||
// While masking on effectively only the Y-axis, so we need to set the width of the bar line container manually
|
||||
content.Width = columnFlow.Width;
|
||||
barLineContainer.Width = columnFlow.Width;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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())
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
22
osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs
Normal file
22
osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.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.Rulesets.Difficulty;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
{
|
||||
public class OsuDifficultyAttributes : DifficultyAttributes
|
||||
{
|
||||
public double AimStrain;
|
||||
public double SpeedStrain;
|
||||
public double ApproachRate;
|
||||
public double OverallDifficulty;
|
||||
public int MaxCombo;
|
||||
|
||||
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
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Difficulty;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
@ -18,31 +18,29 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
private const int section_length = 400;
|
||||
private const double difficulty_multiplier = 0.0675;
|
||||
|
||||
public OsuDifficultyCalculator(IBeatmap beatmap)
|
||||
: base(beatmap)
|
||||
public OsuDifficultyCalculator(Ruleset ruleset, WorkingBeatmap beatmap)
|
||||
: base(ruleset, beatmap)
|
||||
{
|
||||
}
|
||||
|
||||
public OsuDifficultyCalculator(IBeatmap beatmap, Mod[] mods)
|
||||
: base(beatmap, mods)
|
||||
protected override DifficultyAttributes Calculate(IBeatmap beatmap, Mod[] mods, double timeRate)
|
||||
{
|
||||
}
|
||||
if (!beatmap.HitObjects.Any())
|
||||
return new OsuDifficultyAttributes(mods, 0);
|
||||
|
||||
public override double Calculate(Dictionary<string, double> categoryDifficulty = null)
|
||||
{
|
||||
OsuDifficultyBeatmap beatmap = new OsuDifficultyBeatmap((List<OsuHitObject>)Beatmap.HitObjects, TimeRate);
|
||||
OsuDifficultyBeatmap difficultyBeatmap = new OsuDifficultyBeatmap(beatmap.HitObjects.Cast<OsuHitObject>().ToList(), timeRate);
|
||||
Skill[] skills =
|
||||
{
|
||||
new Aim(),
|
||||
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
|
||||
double currentSectionEnd = 2 * sectionLength;
|
||||
|
||||
foreach (OsuDifficultyHitObject h in beatmap)
|
||||
foreach (OsuDifficultyHitObject h in difficultyBeatmap)
|
||||
{
|
||||
while (h.BaseObject.StartTime > currentSectionEnd)
|
||||
{
|
||||
@ -61,16 +59,24 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
|
||||
double aimRating = Math.Sqrt(skills[0].DifficultyValue()) * difficulty_multiplier;
|
||||
double speedRating = Math.Sqrt(skills[1].DifficultyValue()) * difficulty_multiplier;
|
||||
|
||||
double starRating = aimRating + speedRating + Math.Abs(aimRating - speedRating) / 2;
|
||||
|
||||
if (categoryDifficulty != null)
|
||||
{
|
||||
categoryDifficulty.Add("Aim", aimRating);
|
||||
categoryDifficulty.Add("Speed", speedRating);
|
||||
}
|
||||
// Todo: These int casts are temporary to achieve 1:1 results with osu!stable, and should be remoevd in the future
|
||||
double hitWindowGreat = (int)(beatmap.HitObjects.First().HitWindows.Great / 2) / timeRate;
|
||||
double preEmpt = (int)BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.ApproachRate, 1800, 1200, 450) / timeRate;
|
||||
|
||||
return starRating;
|
||||
int maxCombo = beatmap.HitObjects.Count();
|
||||
// Add the ticks + tail of the slider. 1 is subtracted because the "headcircle" would be counted twice (once for the slider itself in the line above)
|
||||
maxCombo += beatmap.HitObjects.OfType<Slider>().Sum(s => s.NestedHitObjects.Count - 1);
|
||||
|
||||
return new OsuDifficultyAttributes(mods, starRating)
|
||||
{
|
||||
AimStrain = aimRating,
|
||||
SpeedStrain = speedRating,
|
||||
ApproachRate = preEmpt > 1200 ? (1800 - preEmpt) / 120 : (1200 - preEmpt) / 150 + 5,
|
||||
OverallDifficulty = (80 - hitWindowGreat) / 6,
|
||||
MaxCombo = maxCombo
|
||||
};
|
||||
}
|
||||
|
||||
protected override Mod[] DifficultyAdjustmentMods => new Mod[]
|
||||
|
@ -15,21 +15,13 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
{
|
||||
public class OsuPerformanceCalculator : PerformanceCalculator
|
||||
{
|
||||
public new OsuDifficultyAttributes Attributes => (OsuDifficultyAttributes)base.Attributes;
|
||||
|
||||
private readonly int countHitCircles;
|
||||
private readonly int beatmapMaxCombo;
|
||||
|
||||
private Mod[] mods;
|
||||
|
||||
/// <summary>
|
||||
/// Approach rate adjusted by mods.
|
||||
/// </summary>
|
||||
private double realApproachRate;
|
||||
|
||||
/// <summary>
|
||||
/// Overall difficulty adjusted by mods.
|
||||
/// </summary>
|
||||
private double realOverallDifficulty;
|
||||
|
||||
private double accuracy;
|
||||
private int scoreMaxCombo;
|
||||
private int countGreat;
|
||||
@ -37,7 +29,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
private int countMeh;
|
||||
private int countMiss;
|
||||
|
||||
public OsuPerformanceCalculator(Ruleset ruleset, IBeatmap beatmap, Score score)
|
||||
public OsuPerformanceCalculator(Ruleset ruleset, WorkingBeatmap beatmap, Score score)
|
||||
: base(ruleset, beatmap, score)
|
||||
{
|
||||
countHitCircles = Beatmap.HitObjects.Count(h => h is HitCircle);
|
||||
@ -61,13 +53,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
if (mods.Any(m => !m.Ranked))
|
||||
return 0;
|
||||
|
||||
// Todo: These int casts are temporary to achieve 1:1 results with osu!stable, and should be remoevd in the future
|
||||
double hitWindowGreat = (int)(Beatmap.HitObjects.First().HitWindows.Great / 2) / TimeRate;
|
||||
double preEmpt = (int)BeatmapDifficulty.DifficultyRange(Beatmap.BeatmapInfo.BaseDifficulty.ApproachRate, 1800, 1200, 450) / TimeRate;
|
||||
|
||||
realApproachRate = preEmpt > 1200 ? (1800 - preEmpt) / 120 : (1200 - preEmpt) / 150 + 5;
|
||||
realOverallDifficulty = (80 - hitWindowGreat) / 6;
|
||||
|
||||
// Custom multipliers for NoFail and SpunOut.
|
||||
double multiplier = 1.12f; // This is being adjusted to keep the final pp value scaled around what it used to be when changing things
|
||||
|
||||
@ -92,8 +77,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
categoryRatings.Add("Aim", aimValue);
|
||||
categoryRatings.Add("Speed", speedValue);
|
||||
categoryRatings.Add("Accuracy", accuracyValue);
|
||||
categoryRatings.Add("OD", realOverallDifficulty);
|
||||
categoryRatings.Add("AR", realApproachRate);
|
||||
categoryRatings.Add("OD", Attributes.OverallDifficulty);
|
||||
categoryRatings.Add("AR", Attributes.ApproachRate);
|
||||
categoryRatings.Add("Max Combo", beatmapMaxCombo);
|
||||
}
|
||||
|
||||
@ -102,7 +87,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
|
||||
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
|
||||
double lengthBonus = 0.95f + 0.4f * Math.Min(1.0f, totalHits / 2000.0f) +
|
||||
@ -118,22 +103,22 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
aimValue *= Math.Min(Math.Pow(scoreMaxCombo, 0.8f) / Math.Pow(beatmapMaxCombo, 0.8f), 1.0f);
|
||||
|
||||
double approachRateFactor = 1.0f;
|
||||
if (realApproachRate > 10.33f)
|
||||
approachRateFactor += 0.45f * (realApproachRate - 10.33f);
|
||||
else if (realApproachRate < 8.0f)
|
||||
if (Attributes.ApproachRate > 10.33f)
|
||||
approachRateFactor += 0.45f * (Attributes.ApproachRate - 10.33f);
|
||||
else if (Attributes.ApproachRate < 8.0f)
|
||||
{
|
||||
// HD is worth more with lower ar!
|
||||
if (mods.Any(h => h is OsuModHidden))
|
||||
approachRateFactor += 0.02f * (8.0f - realApproachRate);
|
||||
approachRateFactor += 0.02f * (8.0f - Attributes.ApproachRate);
|
||||
else
|
||||
approachRateFactor += 0.01f * (8.0f - realApproachRate);
|
||||
approachRateFactor += 0.01f * (8.0f - Attributes.ApproachRate);
|
||||
}
|
||||
|
||||
aimValue *= approachRateFactor;
|
||||
|
||||
// We want to give more reward for lower AR when it comes to aim and HD. This nerfs high AR and buffs lower AR.
|
||||
if (mods.Any(h => h is OsuModHidden))
|
||||
aimValue *= 1.02 + (11.0f - realApproachRate) / 50.0; // Gives a 1.04 bonus for AR10, a 1.06 bonus for AR9, a 1.02 bonus for AR11.
|
||||
aimValue *= 1.02 + (11.0f - Attributes.ApproachRate) / 50.0; // Gives a 1.04 bonus for AR10, a 1.06 bonus for AR9, a 1.02 bonus for AR11.
|
||||
|
||||
if (mods.Any(h => h is OsuModFlashlight))
|
||||
{
|
||||
@ -144,14 +129,14 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
// Scale the aim value with accuracy _slightly_
|
||||
aimValue *= 0.5f + accuracy / 2.0f;
|
||||
// It is important to also consider accuracy difficulty when doing that
|
||||
aimValue *= 0.98f + Math.Pow(realOverallDifficulty, 2) / 2500;
|
||||
aimValue *= 0.98f + Math.Pow(Attributes.OverallDifficulty, 2) / 2500;
|
||||
|
||||
return aimValue;
|
||||
}
|
||||
|
||||
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
|
||||
speedValue *= 0.95f + 0.4f * Math.Min(1.0f, totalHits / 2000.0f) +
|
||||
@ -170,7 +155,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
// Scale the speed value with accuracy _slightly_
|
||||
speedValue *= 0.5f + accuracy / 2.0f;
|
||||
// It is important to also consider accuracy difficulty when doing that
|
||||
speedValue *= 0.98f + Math.Pow(realOverallDifficulty, 2) / 2500;
|
||||
speedValue *= 0.98f + Math.Pow(Attributes.OverallDifficulty, 2) / 2500;
|
||||
|
||||
return speedValue;
|
||||
}
|
||||
@ -192,7 +177,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
|
||||
// Lots of arbitrary values from testing.
|
||||
// Considering to use derivation from perfect accuracy in a probabilistic manner - assume normal distribution
|
||||
double accuracyValue = Math.Pow(1.52163f, realOverallDifficulty) * Math.Pow(betterAccuracyPercentage, 24) * 2.83f;
|
||||
double accuracyValue = Math.Pow(1.52163f, Attributes.OverallDifficulty) * Math.Pow(betterAccuracyPercentage, 24) * 2.83f;
|
||||
|
||||
// Bonus for many hitcircles - it's harder to keep good accuracy up for longer
|
||||
accuracyValue *= Math.Min(1.15f, Math.Pow(amountHitObjectsWithAccuracy / 1000.0f, 0.3f));
|
||||
|
@ -4,6 +4,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.MathUtils;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using OpenTK;
|
||||
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.
|
||||
for (int i = searchStart; i >= 0 && i < curve.Count; i += direction)
|
||||
{
|
||||
if (curve[i] == Position)
|
||||
if (Precision.AlmostEquals(curve[i], Position))
|
||||
continue;
|
||||
|
||||
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);
|
||||
|
||||
//initialise background
|
||||
var upload = new TextureUpload(textureWidth * 4);
|
||||
var bytes = upload.Data;
|
||||
var raw = new RawTexture(textureWidth, 1);
|
||||
var bytes = raw.Data;
|
||||
|
||||
const float aa_portion = 0.02f;
|
||||
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;
|
||||
|
||||
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 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);
|
||||
|
||||
|
@ -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>
|
||||
{
|
||||
Mouse = new ReplayMouseState(GamefieldToScreenSpace(Position ?? Vector2.Zero)),
|
||||
PressedActions = CurrentFrame.Actions
|
||||
}
|
||||
};
|
||||
|
@ -18,7 +18,7 @@ using osu.Game.Rulesets.Replays;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.UI
|
||||
{
|
||||
public class OsuRulesetContainer : RulesetContainer<OsuHitObject>
|
||||
public class OsuRulesetContainer : RulesetContainer<OsuPlayfield, OsuHitObject>
|
||||
{
|
||||
public OsuRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap)
|
||||
: base(ruleset, beatmap)
|
||||
|
@ -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())
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
@ -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.Taiko.Difficulty
|
||||
{
|
||||
public class TaikoDifficultyAttributes : DifficultyAttributes
|
||||
{
|
||||
public double GreatHitWindow;
|
||||
public int MaxCombo;
|
||||
|
||||
public TaikoDifficultyAttributes(Mod[] mods, double starRating)
|
||||
: base(mods, starRating)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
@ -3,6 +3,7 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Difficulty;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
@ -27,54 +28,41 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
|
||||
/// </summary>
|
||||
private const double decay_weight = 0.9;
|
||||
|
||||
/// <summary>
|
||||
/// HitObjects are stored as a member variable.
|
||||
/// </summary>
|
||||
private readonly List<TaikoHitObjectDifficulty> difficultyHitObjects = new List<TaikoHitObjectDifficulty>();
|
||||
|
||||
public TaikoDifficultyCalculator(IBeatmap beatmap)
|
||||
: base(beatmap)
|
||||
public TaikoDifficultyCalculator(Ruleset ruleset, WorkingBeatmap beatmap)
|
||||
: base(ruleset, beatmap)
|
||||
{
|
||||
}
|
||||
|
||||
public TaikoDifficultyCalculator(IBeatmap beatmap, Mod[] mods)
|
||||
: base(beatmap, mods)
|
||||
protected override DifficultyAttributes Calculate(IBeatmap beatmap, Mod[] mods, double timeRate)
|
||||
{
|
||||
}
|
||||
if (!beatmap.HitObjects.Any())
|
||||
return new TaikoDifficultyAttributes(mods, 0);
|
||||
|
||||
public override double Calculate(Dictionary<string, double> categoryDifficulty = null)
|
||||
{
|
||||
// Fill our custom DifficultyHitObject class, that carries additional information
|
||||
difficultyHitObjects.Clear();
|
||||
var difficultyHitObjects = new List<TaikoHitObjectDifficulty>();
|
||||
|
||||
foreach (var hitObject in Beatmap.HitObjects)
|
||||
foreach (var hitObject in beatmap.HitObjects)
|
||||
difficultyHitObjects.Add(new TaikoHitObjectDifficulty((TaikoHitObject)hitObject));
|
||||
|
||||
// Sort DifficultyHitObjects by StartTime of the HitObjects - just to make sure.
|
||||
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)
|
||||
categoryDifficulty["Strain"] = starRating;
|
||||
|
||||
return starRating;
|
||||
return new TaikoDifficultyAttributes(mods, starRating)
|
||||
{
|
||||
// Todo: This int cast is temporary to achieve 1:1 results with osu!stable, and should be remoevd in the future
|
||||
GreatHitWindow = (int)(beatmap.HitObjects.First().HitWindows.Great / 2) / timeRate,
|
||||
MaxCombo = beatmap.HitObjects.Count(h => h is Hit)
|
||||
};
|
||||
}
|
||||
|
||||
protected override Mod[] DifficultyAdjustmentMods => new Mod[]
|
||||
{
|
||||
new TaikoModDoubleTime(),
|
||||
new TaikoModHalfTime(),
|
||||
new TaikoModEasy(),
|
||||
new TaikoModHardRock(),
|
||||
};
|
||||
|
||||
private bool calculateStrainValues()
|
||||
private bool calculateStrainValues(List<TaikoHitObjectDifficulty> objects, double timeRate)
|
||||
{
|
||||
// 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;
|
||||
|
||||
@ -84,7 +72,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
|
||||
while (hitObjectsEnumerator.MoveNext())
|
||||
{
|
||||
var next = hitObjectsEnumerator.Current;
|
||||
next?.CalculateStrains(current, TimeRate);
|
||||
next?.CalculateStrains(current, timeRate);
|
||||
current = next;
|
||||
}
|
||||
|
||||
@ -92,9 +80,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
|
||||
List<double> highestStrains = new List<double>();
|
||||
@ -102,7 +90,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
|
||||
double maximumStrain = 0; // We need to keep track of the maximum strain in the current interval
|
||||
|
||||
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 (hitObject.BaseHitObject.StartTime > intervalEndTime)
|
||||
@ -144,5 +132,13 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
|
||||
|
||||
return difficulty;
|
||||
}
|
||||
|
||||
protected override Mod[] DifficultyAdjustmentMods => new Mod[]
|
||||
{
|
||||
new TaikoModDoubleTime(),
|
||||
new TaikoModHalfTime(),
|
||||
new TaikoModEasy(),
|
||||
new TaikoModHardRock(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -8,13 +8,12 @@ using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Difficulty;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Rulesets.Taiko.Objects;
|
||||
|
||||
namespace osu.Game.Rulesets.Taiko.Difficulty
|
||||
{
|
||||
public class TaikoPerformanceCalculator : PerformanceCalculator
|
||||
{
|
||||
private readonly int beatmapMaxCombo;
|
||||
protected new TaikoDifficultyAttributes Attributes => (TaikoDifficultyAttributes)base.Attributes;
|
||||
|
||||
private Mod[] mods;
|
||||
private int countGreat;
|
||||
@ -22,10 +21,9 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
|
||||
private int countMeh;
|
||||
private int countMiss;
|
||||
|
||||
public TaikoPerformanceCalculator(Ruleset ruleset, IBeatmap beatmap, Score score)
|
||||
public TaikoPerformanceCalculator(Ruleset ruleset, WorkingBeatmap beatmap, Score score)
|
||||
: base(ruleset, beatmap, score)
|
||||
{
|
||||
beatmapMaxCombo = beatmap.HitObjects.Count(h => h is Hit);
|
||||
}
|
||||
|
||||
public override double Calculate(Dictionary<string, double> categoryDifficulty = null)
|
||||
@ -68,7 +66,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
|
||||
|
||||
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
|
||||
double lengthBonus = 1 + 0.1f * Math.Min(1.0, totalHits / 1500.0);
|
||||
@ -78,8 +76,8 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
|
||||
strainValue *= Math.Pow(0.985, countMiss);
|
||||
|
||||
// Combo scaling
|
||||
if (beatmapMaxCombo > 0)
|
||||
strainValue *= Math.Min(Math.Pow(Score.MaxCombo, 0.5) / Math.Pow(beatmapMaxCombo, 0.5), 1.0);
|
||||
if (Attributes.MaxCombo > 0)
|
||||
strainValue *= Math.Min(Math.Pow(Score.MaxCombo, 0.5) / Math.Pow(Attributes.MaxCombo, 0.5), 1.0);
|
||||
|
||||
if (mods.Any(m => m is ModHidden))
|
||||
strainValue *= 1.025;
|
||||
@ -94,14 +92,12 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
|
||||
|
||||
private double computeAccuracyValue()
|
||||
{
|
||||
// Todo: This int cast is temporary to achieve 1:1 results with osu!stable, and should be remoevd in the future
|
||||
double hitWindowGreat = (int)(Beatmap.HitObjects.First().HitWindows.Great / 2) / TimeRate;
|
||||
if (hitWindowGreat <= 0)
|
||||
if (Attributes.GreatHitWindow <= 0)
|
||||
return 0;
|
||||
|
||||
// Lots of arbitrary values from testing.
|
||||
// Considering to use derivation from perfect accuracy in a probabilistic manner - assume normal distribution
|
||||
double accValue = Math.Pow(150.0 / hitWindowGreat, 1.1) * Math.Pow(Score.Accuracy, 15) * 22.0;
|
||||
double accValue = Math.Pow(150.0 / Attributes.GreatHitWindow, 1.1) * Math.Pow(Score.Accuracy, 15) * 22.0;
|
||||
|
||||
// Bonus for many hitcircles - it's harder to keep good accuracy up for longer
|
||||
return accValue * Math.Min(1.15, Math.Pow(totalHits / 1500.0, 0.3));
|
||||
|
@ -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.Rulesets.Scoring;
|
||||
|
||||
namespace osu.Game.Rulesets.Taiko.Judgements
|
||||
{
|
||||
public class TaikoIntermediateSwellJudgement : TaikoJudgement
|
||||
{
|
||||
public override HitResult MaxResult => HitResult.Perfect;
|
||||
|
||||
public override bool AffectsCombo => false;
|
||||
|
||||
public TaikoIntermediateSwellJudgement()
|
||||
{
|
||||
Final = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Computes the numeric result value for the combo portion of the score.
|
||||
/// </summary>
|
||||
/// <param name="result">The result to compute the value for.</param>
|
||||
/// <returns>The numeric result value.</returns>
|
||||
protected override int NumericResultFor(HitResult result) => 0;
|
||||
}
|
||||
}
|
@ -86,6 +86,10 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
||||
switch (State.Value)
|
||||
{
|
||||
case ArmedState.Idle:
|
||||
SecondHitAllowed = false;
|
||||
validKeyPressed = false;
|
||||
|
||||
UnproxyContent();
|
||||
this.Delay(HitObject.HitWindows.HalfWindowFor(HitResult.Miss)).Expire();
|
||||
break;
|
||||
case ArmedState.Miss:
|
||||
@ -93,6 +97,9 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
||||
.Expire();
|
||||
break;
|
||||
case ArmedState.Hit:
|
||||
// If we're far enough away from the left stage, we should bring outselves in front of it
|
||||
ProxyContent();
|
||||
|
||||
var flash = circlePiece?.FlashBox;
|
||||
if (flash != null)
|
||||
{
|
||||
|
@ -3,6 +3,7 @@
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Rulesets.Taiko.Judgements;
|
||||
|
||||
@ -46,6 +47,19 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
||||
AddJudgement(new TaikoStrongHitJudgement { Result = HitResult.Great });
|
||||
}
|
||||
|
||||
protected override void UpdateState(ArmedState state)
|
||||
{
|
||||
base.UpdateState(state);
|
||||
|
||||
switch (state)
|
||||
{
|
||||
case ArmedState.Idle:
|
||||
firstHitTime = 0;
|
||||
firstKeyHeld = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public override bool OnReleased(TaikoAction action)
|
||||
{
|
||||
if (action == firstHitAction)
|
||||
|
@ -2,7 +2,6 @@
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
@ -21,10 +20,9 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
||||
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.
|
||||
/// A judgement is only displayed when the user has complete the swell (either a hit or miss).
|
||||
/// </summary>
|
||||
public event Action OnStart;
|
||||
public override bool DisplayJudgement => AllJudged;
|
||||
|
||||
private const float target_ring_thick_border = 1.4f;
|
||||
private const float target_ring_thin_border = 1f;
|
||||
@ -35,12 +33,6 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
||||
private readonly CircularContainer targetRing;
|
||||
private readonly CircularContainer expandingRing;
|
||||
|
||||
/// <summary>
|
||||
/// The amount of times the user has hit this swell.
|
||||
/// </summary>
|
||||
private int userHits;
|
||||
|
||||
private bool hasStarted;
|
||||
private readonly SwellSymbolPiece symbol;
|
||||
|
||||
public DrawableSwell(Swell swell)
|
||||
@ -48,7 +40,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
||||
{
|
||||
FillMode = FillMode.Fit;
|
||||
|
||||
AddInternal(bodyContainer = new Container
|
||||
Content.Add(bodyContainer = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Depth = 1,
|
||||
@ -136,9 +128,9 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
||||
{
|
||||
if (userTriggered)
|
||||
{
|
||||
userHits++;
|
||||
AddJudgement(new TaikoIntermediateSwellJudgement());
|
||||
|
||||
var completion = (float)userHits / HitObject.RequiredHits;
|
||||
var completion = (float)Judgements.Count / HitObject.RequiredHits;
|
||||
|
||||
expandingRing
|
||||
.FadeTo(expandingRing.Alpha + MathHelper.Clamp(completion / 16, 0.1f, 0.6f), 50)
|
||||
@ -149,7 +141,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
||||
|
||||
expandingRing.ScaleTo(1f + Math.Min(target_ring_scale - 1f, (target_ring_scale - 1f) * completion * 1.3f), 260, Easing.OutQuint);
|
||||
|
||||
if (userHits == HitObject.RequiredHits)
|
||||
if (Judgements.Count == HitObject.RequiredHits)
|
||||
AddJudgement(new TaikoJudgement { Result = HitResult.Great });
|
||||
}
|
||||
else
|
||||
@ -158,7 +150,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
||||
return;
|
||||
|
||||
//TODO: THIS IS SHIT AND CAN'T EXIST POST-TAIKO WORLD CUP
|
||||
AddJudgement(userHits > HitObject.RequiredHits / 2
|
||||
AddJudgement(Judgements.Count > HitObject.RequiredHits / 2
|
||||
? new TaikoJudgement { Result = HitResult.Good }
|
||||
: new TaikoJudgement { Result = HitResult.Miss });
|
||||
}
|
||||
@ -169,20 +161,22 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
||||
const float preempt = 100;
|
||||
const float out_transition_time = 300;
|
||||
|
||||
double untilStartTime = HitObject.StartTime - Time.Current;
|
||||
double untilJudgement = untilStartTime + (Judgements.FirstOrDefault()?.TimeOffset ?? 0) + HitObject.Duration;
|
||||
|
||||
targetRing.Delay(untilStartTime - preempt).ScaleTo(target_ring_scale, preempt * 4, Easing.OutQuint);
|
||||
this.Delay(untilJudgement).FadeOut(out_transition_time, Easing.Out);
|
||||
|
||||
switch (state)
|
||||
{
|
||||
case ArmedState.Idle:
|
||||
UnproxyContent();
|
||||
expandingRing.FadeTo(0);
|
||||
using (BeginAbsoluteSequence(HitObject.StartTime - preempt, true))
|
||||
targetRing.ScaleTo(target_ring_scale, preempt * 4, Easing.OutQuint);
|
||||
break;
|
||||
case ArmedState.Miss:
|
||||
case ArmedState.Hit:
|
||||
bodyContainer.Delay(untilJudgement).ScaleTo(1.4f, out_transition_time);
|
||||
this.FadeOut(out_transition_time, Easing.Out);
|
||||
bodyContainer.ScaleTo(1.4f, out_transition_time);
|
||||
|
||||
Expire();
|
||||
break;
|
||||
}
|
||||
|
||||
Expire();
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
@ -195,11 +189,8 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
||||
X = Math.Max(0, X);
|
||||
|
||||
double t = Math.Min(HitObject.StartTime, Time.Current);
|
||||
if (t == HitObject.StartTime && !hasStarted)
|
||||
{
|
||||
OnStart?.Invoke();
|
||||
hasStarted = true;
|
||||
}
|
||||
if (t == HitObject.StartTime)
|
||||
ProxyContent();
|
||||
}
|
||||
|
||||
private bool? lastWasCentre;
|
||||
|
@ -9,10 +9,75 @@ using OpenTK;
|
||||
using System.Linq;
|
||||
using osu.Game.Audio;
|
||||
using System.Collections.Generic;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Primitives;
|
||||
|
||||
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
|
||||
{
|
||||
public override Vector2 OriginPosition => new Vector2(DrawHeight / 2);
|
||||
@ -34,7 +99,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
Size = BaseSize = new Vector2(HitObject.IsStrong ? TaikoHitObject.DEFAULT_STRONG_SIZE : TaikoHitObject.DEFAULT_SIZE);
|
||||
|
||||
InternalChild = MainPiece = CreateMainPiece();
|
||||
Content.Add(MainPiece = CreateMainPiece());
|
||||
MainPiece.KiaiMode = HitObject.Kiai;
|
||||
}
|
||||
|
||||
@ -44,9 +109,5 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
||||
protected override string SampleNamespace => "Taiko";
|
||||
|
||||
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();
|
||||
|
||||
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[]
|
||||
{
|
||||
new KeyBinding(InputKey.MouseLeft, TaikoAction.LeftCentre),
|
||||
new KeyBinding(InputKey.MouseRight, TaikoAction.LeftRim),
|
||||
new KeyBinding(InputKey.D, TaikoAction.LeftRim),
|
||||
new KeyBinding(InputKey.F, TaikoAction.LeftCentre),
|
||||
new KeyBinding(InputKey.J, TaikoAction.RightCentre),
|
||||
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)
|
||||
@ -114,9 +112,9 @@ namespace osu.Game.Rulesets.Taiko
|
||||
|
||||
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;
|
||||
|
||||
|
@ -18,6 +18,8 @@ namespace osu.Game.Rulesets.Taiko.UI
|
||||
/// </summary>
|
||||
internal class HitExplosion : CircularContainer
|
||||
{
|
||||
public override bool RemoveWhenNotAlive => true;
|
||||
|
||||
public readonly DrawableHitObject JudgedObject;
|
||||
|
||||
private readonly Box innerFill;
|
||||
@ -66,7 +68,7 @@ namespace osu.Game.Rulesets.Taiko.UI
|
||||
this.ScaleTo(3f, 1000, Easing.OutQuint);
|
||||
this.FadeOut(500);
|
||||
|
||||
Expire();
|
||||
Expire(true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -14,6 +14,8 @@ namespace osu.Game.Rulesets.Taiko.UI
|
||||
{
|
||||
public class KiaiHitExplosion : CircularContainer
|
||||
{
|
||||
public override bool RemoveWhenNotAlive => true;
|
||||
|
||||
public readonly DrawableHitObject JudgedObject;
|
||||
|
||||
private readonly bool isRim;
|
||||
@ -62,7 +64,7 @@ namespace osu.Game.Rulesets.Taiko.UI
|
||||
this.ScaleTo(new Vector2(1, 3f), 500, Easing.OutQuint);
|
||||
this.FadeOut(250);
|
||||
|
||||
Expire();
|
||||
Expire(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -216,10 +216,9 @@ namespace osu.Game.Rulesets.Taiko.UI
|
||||
if (barline != null)
|
||||
barlineContainer.Add(barline.CreateProxy());
|
||||
|
||||
// Swells should be moved at the very top of the playfield when they reach the hit target
|
||||
var swell = h as DrawableSwell;
|
||||
if (swell != null)
|
||||
swell.OnStart += () => topLevelHitContainer.Add(swell.CreateProxy());
|
||||
var taikoObject = h as DrawableTaikoHitObject;
|
||||
if (taikoObject != null)
|
||||
topLevelHitContainer.Add(taikoObject.CreateProxiedContent());
|
||||
}
|
||||
|
||||
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();
|
||||
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));
|
||||
|
||||
if (judgedObject.HitObject.Kiai)
|
||||
|
@ -69,11 +69,7 @@ namespace osu.Game.Rulesets.Taiko.UI
|
||||
bool isMajor = currentBeat % (int)currentPoint.TimeSignature == 0;
|
||||
Playfield.Add(isMajor ? new DrawableBarLineMajor(barLine) : new DrawableBarLine(barLine));
|
||||
|
||||
double bl = currentPoint.BeatLength;
|
||||
if (bl < 800)
|
||||
bl *= (int)currentPoint.TimeSignature;
|
||||
|
||||
time += bl;
|
||||
time += currentPoint.BeatLength * (int)currentPoint.TimeSignature;
|
||||
currentBeat++;
|
||||
}
|
||||
}
|
||||
|
@ -19,7 +19,7 @@ namespace osu.Game.Tests.Beatmaps.IO
|
||||
[TestFixture]
|
||||
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]
|
||||
public void TestImportWhenClosed()
|
||||
@ -265,7 +265,7 @@ namespace osu.Game.Tests.Beatmaps.IO
|
||||
private string createTemporaryBeatmap()
|
||||
{
|
||||
var temp = Path.GetTempFileName() + ".osz";
|
||||
File.Copy(osz_path, temp, true);
|
||||
File.Copy(TEST_OSZ_PATH, temp, true);
|
||||
Assert.IsTrue(File.Exists(temp));
|
||||
return temp;
|
||||
}
|
||||
|
@ -2,8 +2,8 @@
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using NUnit.Framework;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Difficulty;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
|
||||
@ -139,14 +139,14 @@ namespace osu.Game.Tests.NonVisual
|
||||
private class TestDifficultyCalculator : DifficultyCalculator
|
||||
{
|
||||
public TestDifficultyCalculator(params Mod[] mods)
|
||||
: base(null)
|
||||
: base(null, null)
|
||||
{
|
||||
DifficultyAdjustmentMods = mods;
|
||||
}
|
||||
|
||||
public override double Calculate(Dictionary<string, double> categoryDifficulty = null) => throw new NotImplementedException();
|
||||
|
||||
protected override Mod[] DifficultyAdjustmentMods { get; }
|
||||
|
||||
protected override DifficultyAttributes Calculate(IBeatmap beatmap, Mod[] mods, double timeRate) => throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4,28 +4,48 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Configuration;
|
||||
using OpenTK;
|
||||
using osu.Framework.Graphics;
|
||||
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 OpenTK.Graphics;
|
||||
|
||||
namespace osu.Game.Tests.Visual
|
||||
{
|
||||
[TestFixture]
|
||||
public class TestCaseEditorComposeTimeline : OsuTestCase
|
||||
public class TestCaseEditorComposeTimeline : EditorClockTestCase
|
||||
{
|
||||
public override IReadOnlyList<Type> RequiredTypes => new[] { typeof(TimelineArea), typeof(Timeline), typeof(TimelineButton) };
|
||||
|
||||
public TestCaseEditorComposeTimeline()
|
||||
public override IReadOnlyList<Type> RequiredTypes => new[]
|
||||
{
|
||||
typeof(TimelineArea),
|
||||
typeof(Timeline),
|
||||
typeof(TimelineButton),
|
||||
typeof(CentreMarker)
|
||||
};
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
Beatmap.Value = new WaveformTestBeatmap();
|
||||
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new MusicController
|
||||
new FillFlowContainer
|
||||
{
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre,
|
||||
State = Visibility.Visible
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Direction = FillDirection.Vertical,
|
||||
Spacing = new Vector2(0, 5),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new StartStopButton(),
|
||||
new AudioVisualiser(),
|
||||
}
|
||||
},
|
||||
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.Online.Multiplayer;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Screens;
|
||||
using osu.Game.Screens.Backgrounds;
|
||||
using osu.Game.Screens.Multi.Components;
|
||||
using osu.Game.Screens.Multi.Screens.Lounge;
|
||||
using osu.Game.Users;
|
||||
@ -165,6 +167,7 @@ namespace osu.Game.Tests.Visual
|
||||
AddStep(@"set rooms", () => lounge.Rooms = rooms);
|
||||
selectAssert(1);
|
||||
AddStep(@"open room 1", () => clickRoom(1));
|
||||
AddUntilStep(() => lounge.ChildScreen?.IsCurrentScreen == true, "wait until room current");
|
||||
AddStep(@"make lounge current", lounge.MakeCurrent);
|
||||
filterAssert(@"THE FINAL", LoungeTab.Public, 1);
|
||||
filterAssert(string.Empty, LoungeTab.Public, 2);
|
||||
@ -198,6 +201,8 @@ namespace osu.Game.Tests.Visual
|
||||
|
||||
private class TestLounge : Lounge
|
||||
{
|
||||
protected override BackgroundScreen CreateBackground() => new BackgroundScreenDefault();
|
||||
|
||||
public IEnumerable<DrawableRoom> ChildRooms => RoomsContainer.Children.Where(r => r.MatchingFilter);
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
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 };
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -114,7 +114,7 @@ namespace osu.Game.Tests.Visual
|
||||
|
||||
private class TestPlayfield : ScrollingPlayfield
|
||||
{
|
||||
public readonly ScrollingDirection Direction;
|
||||
public new readonly ScrollingDirection Direction;
|
||||
|
||||
public TestPlayfield(ScrollingDirection direction)
|
||||
: base(direction)
|
||||
|
@ -5,6 +5,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Overlays.Volume;
|
||||
using OpenTK;
|
||||
using OpenTK.Graphics;
|
||||
|
||||
namespace osu.Game.Tests.Visual
|
||||
@ -17,13 +18,21 @@ namespace osu.Game.Tests.Visual
|
||||
{
|
||||
VolumeMeter meter;
|
||||
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
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -10,7 +10,6 @@ using osu.Framework.Graphics.Audio;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Overlays;
|
||||
|
||||
namespace osu.Game.Tests.Visual
|
||||
{
|
||||
@ -20,22 +19,14 @@ namespace osu.Game.Tests.Visual
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
Beatmap.Value = new WaveformTestBeatmap();
|
||||
|
||||
FillFlowContainer flow;
|
||||
Child = flow = new FillFlowContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Direction = FillDirection.Vertical,
|
||||
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)
|
||||
@ -44,10 +35,9 @@ namespace osu.Game.Tests.Visual
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Resolution = 1f / i,
|
||||
Waveform = Beatmap.Value.Waveform,
|
||||
};
|
||||
|
||||
Beatmap.ValueChanged += b => newDisplay.Waveform = b.Waveform;
|
||||
|
||||
flow.Add(new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
|
@ -13,7 +13,6 @@ using osu.Game.Graphics.Cursor;
|
||||
using osu.Game.Screens.Edit.Screens.Compose.Timeline;
|
||||
using OpenTK;
|
||||
using OpenTK.Graphics;
|
||||
using OpenTK.Input;
|
||||
|
||||
namespace osu.Game.Tests.Visual
|
||||
{
|
||||
@ -66,7 +65,7 @@ namespace osu.Game.Tests.Visual
|
||||
{
|
||||
reset();
|
||||
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));
|
||||
}
|
||||
|
||||
@ -77,16 +76,12 @@ namespace osu.Game.Tests.Visual
|
||||
|
||||
// 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("Press ctrl", () => InputManager.PressKey(Key.LControl));
|
||||
AddStep("Scroll by 3", () => InputManager.ScrollBy(new Vector2(3, 0)));
|
||||
AddStep("Release ctrl", () => InputManager.ReleaseKey(Key.LControl));
|
||||
AddStep("Scroll by 3", () => InputManager.ScrollBy(new Vector2(0, 3)));
|
||||
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));
|
||||
|
||||
// Scroll out at 0.25
|
||||
AddStep("Press ctrl", () => InputManager.PressKey(Key.LControl));
|
||||
AddStep("Scroll by -3", () => InputManager.ScrollBy(new Vector2(-3, 0)));
|
||||
AddStep("Release ctrl", () => InputManager.ReleaseKey(Key.LControl));
|
||||
AddStep("Scroll by -3", () => InputManager.ScrollBy(new Vector2(0, -3)));
|
||||
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));
|
||||
}
|
||||
@ -98,15 +93,11 @@ namespace osu.Game.Tests.Visual
|
||||
|
||||
// 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("Press ctrl", () => InputManager.PressKey(Key.LControl));
|
||||
AddStep("Scroll by 1", () => InputManager.ScrollBy(new Vector2(1, 0)));
|
||||
AddStep("Release ctrl", () => InputManager.ReleaseKey(Key.LControl));
|
||||
AddStep("Scroll by 1", () => InputManager.ScrollBy(new Vector2(0, 1)));
|
||||
|
||||
// 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("Press ctrl", () => InputManager.PressKey(Key.LControl));
|
||||
AddStep("Scroll by 1", () => InputManager.ScrollBy(new Vector2(1, 0)));
|
||||
AddStep("Release ctrl", () => InputManager.ReleaseKey(Key.LControl));
|
||||
AddStep("Scroll by 1", () => InputManager.ScrollBy(new Vector2(0, 1)));
|
||||
AddAssert("Box not at 0", () => !Precision.AlmostEquals(boxQuad.TopLeft, scrollQuad.TopLeft));
|
||||
|
||||
// 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));
|
||||
|
||||
// Scroll out at 0.6
|
||||
AddStep("Press ctrl", () => InputManager.PressKey(Key.LControl));
|
||||
AddStep("Scroll by -1", () => InputManager.ScrollBy(new Vector2(-1, 0)));
|
||||
AddStep("Release ctrl", () => InputManager.ReleaseKey(Key.LControl));
|
||||
AddStep("Scroll by -1", () => InputManager.ScrollBy(new Vector2(0, -1)));
|
||||
|
||||
// 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("Press ctrl", () => InputManager.PressKey(Key.LControl));
|
||||
AddStep("Scroll by -1", () => InputManager.ScrollBy(new Vector2(-1, 0)));
|
||||
AddStep("Release ctrl", () => InputManager.ReleaseKey(Key.LControl));
|
||||
AddStep("Scroll by -1", () => InputManager.ScrollBy(new Vector2(0, -1)));
|
||||
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");
|
||||
}
|
||||
}
|
||||
}
|
@ -366,8 +366,7 @@ namespace osu.Game.Beatmaps
|
||||
if (ruleset != null)
|
||||
{
|
||||
// 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(converted).Calculate();
|
||||
beatmap.BeatmapInfo.StarDifficulty = ruleset.CreateInstance().CreateDifficultyCalculator(new DummyConversionBeatmap(beatmap)).Calculate().StarRating;
|
||||
}
|
||||
else
|
||||
beatmap.BeatmapInfo.StarDifficulty = 0;
|
||||
@ -390,6 +389,9 @@ namespace osu.Game.Beatmaps
|
||||
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
|
||||
|
@ -62,7 +62,7 @@ namespace osu.Game.Beatmaps
|
||||
|
||||
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";
|
||||
|
||||
|
@ -13,7 +13,6 @@ using osu.Game.Storyboards;
|
||||
using osu.Framework.IO.File;
|
||||
using System.IO;
|
||||
using osu.Game.IO.Serialization;
|
||||
using System.Diagnostics;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.UI;
|
||||
using osu.Game.Skinning;
|
||||
@ -49,12 +48,13 @@ namespace osu.Game.Beatmaps
|
||||
/// <summary>
|
||||
/// Saves the <see cref="Beatmaps.Beatmap"/>.
|
||||
/// </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");
|
||||
using (var sw = new StreamWriter(path))
|
||||
sw.WriteLine(Beatmap.Serialize());
|
||||
Process.Start(path);
|
||||
return path;
|
||||
}
|
||||
|
||||
protected abstract IBeatmap GetBeatmap();
|
||||
|
@ -181,24 +181,6 @@ namespace osu.Game.Database
|
||||
}
|
||||
}
|
||||
|
||||
public void 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)
|
||||
{
|
||||
}
|
||||
public void Migrate() => Database.Migrate();
|
||||
}
|
||||
}
|
||||
|
@ -3,11 +3,11 @@
|
||||
|
||||
using osu.Game.Online.Chat;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using System.Collections.Generic;
|
||||
using osu.Framework.Platform;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Overlays.Notifications;
|
||||
|
||||
@ -25,12 +25,14 @@ namespace osu.Game.Graphics.Containers
|
||||
private OsuGame game;
|
||||
|
||||
private Action showNotImplementedError;
|
||||
private GameHost host;
|
||||
|
||||
[BackgroundDependencyLoader(true)]
|
||||
private void load(OsuGame game, NotificationOverlay notifications)
|
||||
private void load(OsuGame game, NotificationOverlay notifications, GameHost host)
|
||||
{
|
||||
// will be null in tests
|
||||
this.game = game;
|
||||
this.host = host;
|
||||
|
||||
showNotImplementedError = () => notifications?.Post(new SimpleNotification
|
||||
{
|
||||
@ -88,7 +90,7 @@ namespace osu.Game.Graphics.Containers
|
||||
showNotImplementedError?.Invoke();
|
||||
break;
|
||||
case LinkAction.External:
|
||||
Process.Start(url);
|
||||
host.OpenUrlExternally(url);
|
||||
break;
|
||||
case LinkAction.OpenUserProfile:
|
||||
if (long.TryParse(linkArgument, out long userId))
|
||||
|
@ -8,20 +8,32 @@ using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Input;
|
||||
using OpenTK;
|
||||
using osu.Framework.Configuration;
|
||||
using osu.Game.Audio;
|
||||
using osu.Game.Overlays;
|
||||
|
||||
namespace osu.Game.Graphics.Containers
|
||||
{
|
||||
public class OsuFocusedOverlayContainer : FocusedOverlayContainer
|
||||
public class OsuFocusedOverlayContainer : FocusedOverlayContainer, IPreviewTrackOwner
|
||||
{
|
||||
private SampleChannel samplePopIn;
|
||||
private SampleChannel samplePopOut;
|
||||
|
||||
private PreviewTrackManager previewTrackManager;
|
||||
|
||||
protected readonly Bindable<OverlayActivation> OverlayActivationMode = new Bindable<OverlayActivation>(OverlayActivation.All);
|
||||
|
||||
[BackgroundDependencyLoader(true)]
|
||||
private void load(OsuGame osuGame, AudioManager audio)
|
||||
protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent)
|
||||
{
|
||||
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)
|
||||
OverlayActivationMode.BindTo(osuGame.OverlayActivationMode);
|
||||
|
||||
@ -33,7 +45,7 @@ namespace osu.Game.Graphics.Containers
|
||||
|
||||
/// <summary>
|
||||
/// 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>
|
||||
public virtual bool BlockScreenWideMouse => BlockPassThroughMouse;
|
||||
|
||||
@ -66,5 +78,11 @@ namespace osu.Game.Graphics.Containers
|
||||
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;
|
||||
|
||||
/// <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;
|
||||
|
||||
private Bindable<bool> parallaxEnabled;
|
||||
@ -45,7 +48,7 @@ namespace osu.Game.Graphics.Containers
|
||||
if (!parallaxEnabled)
|
||||
{
|
||||
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);
|
||||
|
||||
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;
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user