Merge branch 'master' into api-offline-early-return

This commit is contained in:
Dean Herbert 2018-06-22 13:16:30 +09:00 committed by GitHub
commit 5d3b51b8d4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
101 changed files with 2798 additions and 1207 deletions

View File

@ -1,12 +1,11 @@
<component name="ProjectRunConfigurationManager"> <component name="ProjectRunConfigurationManager">
<configuration default="false" name="VisualTests (netcoreapp2.1)" type="DotNetProject" factoryName=".NET Project"> <configuration default="false" name="VisualTests" type="DotNetProject" factoryName=".NET Project">
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Game.Tests/bin/Debug/netcoreapp2.1/osu.Game.Tests.dll" /> <option name="EXE_PATH" value="$PROJECT_DIR$/osu.Game.Tests/bin/Debug/netcoreapp2.1/osu.Game.Tests.dll" />
<option name="PROGRAM_PARAMETERS" value="" /> <option name="PROGRAM_PARAMETERS" value="" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/osu.Game.Tests" /> <option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/osu.Game.Tests" />
<option name="PASS_PARENT_ENVS" value="1" /> <option name="PASS_PARENT_ENVS" value="1" />
<envs />
<option name="USE_MONO" value="0" />
<option name="USE_EXTERNAL_CONSOLE" value="0" /> <option name="USE_EXTERNAL_CONSOLE" value="0" />
<option name="USE_MONO" value="0" />
<option name="PROJECT_PATH" value="$PROJECT_DIR$/osu.Game.Tests/osu.Game.Tests.csproj" /> <option name="PROJECT_PATH" value="$PROJECT_DIR$/osu.Game.Tests/osu.Game.Tests.csproj" />
<option name="PROJECT_EXE_PATH_TRACKING" value="1" /> <option name="PROJECT_EXE_PATH_TRACKING" value="1" />
<option name="PROJECT_ARGUMENTS_TRACKING" value="1" /> <option name="PROJECT_ARGUMENTS_TRACKING" value="1" />

View File

@ -1,12 +1,11 @@
<component name="ProjectRunConfigurationManager"> <component name="ProjectRunConfigurationManager">
<configuration default="false" name="osu! (netcoreapp2.1)" type="DotNetProject" factoryName=".NET Project"> <configuration default="false" name="osu!" type="DotNetProject" factoryName=".NET Project">
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Desktop/bin/Debug/netcoreapp2.1/osu!.dll" /> <option name="EXE_PATH" value="$PROJECT_DIR$/osu.Desktop/bin/Debug/netcoreapp2.1/osu!.dll" />
<option name="PROGRAM_PARAMETERS" value="" /> <option name="PROGRAM_PARAMETERS" value="" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/osu.Desktop" /> <option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/osu.Desktop" />
<option name="PASS_PARENT_ENVS" value="1" /> <option name="PASS_PARENT_ENVS" value="1" />
<envs />
<option name="USE_MONO" value="0" />
<option name="USE_EXTERNAL_CONSOLE" value="0" /> <option name="USE_EXTERNAL_CONSOLE" value="0" />
<option name="USE_MONO" value="0" />
<option name="PROJECT_PATH" value="$PROJECT_DIR$/osu.Desktop/osu.Desktop.csproj" /> <option name="PROJECT_PATH" value="$PROJECT_DIR$/osu.Desktop/osu.Desktop.csproj" />
<option name="PROJECT_EXE_PATH_TRACKING" value="1" /> <option name="PROJECT_EXE_PATH_TRACKING" value="1" />
<option name="PROJECT_ARGUMENTS_TRACKING" value="1" /> <option name="PROJECT_ARGUMENTS_TRACKING" value="1" />

View File

@ -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"
}
]
}

View File

@ -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"
]
}
]
}

View File

@ -1,12 +1,13 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Diagnostics; using System;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Textures; using osu.Framework.Graphics.Textures;
using osu.Framework.Platform;
using osu.Game; using osu.Game;
using osu.Game.Configuration; using osu.Game.Configuration;
using osu.Game.Graphics; using osu.Game.Graphics;
@ -24,16 +25,18 @@ namespace osu.Desktop.Overlays
private OsuConfigManager config; private OsuConfigManager config;
private OsuGameBase game; private OsuGameBase game;
private NotificationOverlay notificationOverlay; private NotificationOverlay notificationOverlay;
private GameHost host;
public override bool HandleKeyboardInput => false; public override bool HandleKeyboardInput => false;
public override bool HandleMouseInput => false; public override bool HandleMouseInput => false;
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(NotificationOverlay notification, OsuColour colours, TextureStore textures, OsuGameBase game, OsuConfigManager config) private void load(NotificationOverlay notification, OsuColour colours, TextureStore textures, OsuGameBase game, OsuConfigManager config, GameHost host)
{ {
notificationOverlay = notification; notificationOverlay = notification;
this.config = config; this.config = config;
this.game = game; this.game = game;
this.host = host;
AutoSizeAxes = Axes.Both; AutoSizeAxes = Axes.Both;
Anchor = Anchor.BottomCentre; Anchor = Anchor.BottomCentre;
@ -106,19 +109,19 @@ namespace osu.Desktop.Overlays
// only show a notification if we've previously saved a version to the config file (ie. not the first run). // only show a notification if we've previously saved a version to the config file (ie. not the first run).
if (!string.IsNullOrEmpty(lastVersion)) if (!string.IsNullOrEmpty(lastVersion))
notificationOverlay.Post(new UpdateCompleteNotification(version)); notificationOverlay.Post(new UpdateCompleteNotification(version, host.OpenUrlExternally));
} }
} }
private class UpdateCompleteNotification : SimpleNotification private class UpdateCompleteNotification : SimpleNotification
{ {
public UpdateCompleteNotification(string version) public UpdateCompleteNotification(string version, Action<string> openUrl = null)
{ {
Text = $"You are now running osu!lazer {version}.\nClick to see what's new!"; Text = $"You are now running osu!lazer {version}.\nClick to see what's new!";
Icon = FontAwesome.fa_check_square; Icon = FontAwesome.fa_check_square;
Activated = delegate Activated = delegate
{ {
Process.Start($"https://osu.ppy.sh/home/changelog/{version}"); openUrl?.Invoke($"https://osu.ppy.sh/home/changelog/lazer/{version}");
return true; return true;
}; };
} }

View File

@ -30,6 +30,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup Label="Package References"> <ItemGroup Label="Package References">
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.0" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="2.1.1" />
<PackageReference Include="squirrel.windows" Version="1.8.0" Condition="'$(TargetFramework)' == 'net471'" /> <PackageReference Include="squirrel.windows" Version="1.8.0" Condition="'$(TargetFramework)' == 'net471'" />
</ItemGroup> </ItemGroup>
<ItemGroup Label="Resources"> <ItemGroup Label="Resources">

View File

@ -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())
{
}
}
}

View File

@ -112,7 +112,7 @@ namespace osu.Game.Rulesets.Catch
public override Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.fa_osu_fruits_o }; public override Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.fa_osu_fruits_o };
public override DifficultyCalculator CreateDifficultyCalculator(IBeatmap beatmap, Mod[] mods = null) => new CatchDifficultyCalculator(beatmap); public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new CatchDifficultyCalculator(this, beatmap);
public override int? LegacyID => 2; public override int? LegacyID => 2;

View File

@ -0,0 +1,19 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Mods;
namespace osu.Game.Rulesets.Catch.Difficulty
{
public class CatchDifficultyAttributes : DifficultyAttributes
{
public double ApproachRate;
public int MaxCombo;
public CatchDifficultyAttributes(Mod[] mods, double starRating)
: base(mods, starRating)
{
}
}
}

View File

@ -1,18 +1,146 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Catch.UI;
namespace osu.Game.Rulesets.Catch.Difficulty namespace osu.Game.Rulesets.Catch.Difficulty
{ {
public class CatchDifficultyCalculator : DifficultyCalculator public class CatchDifficultyCalculator : DifficultyCalculator
{ {
public CatchDifficultyCalculator(IBeatmap beatmap) : base(beatmap)
/// <summary>
/// In milliseconds. For difficulty calculation we will only look at the highest strain value in each time interval of size STRAIN_STEP.
/// This is to eliminate higher influence of stream over aim by simply having more HitObjects with high strain.
/// The higher this value, the less strains there will be, indirectly giving long beatmaps an advantage.
/// </summary>
private const double strain_step = 750;
/// <summary>
/// The weighting of each strain value decays to this number * it's previous value
/// </summary>
private const double decay_weight = 0.94;
private const double star_scaling_factor = 0.145;
public CatchDifficultyCalculator(Ruleset ruleset, WorkingBeatmap beatmap)
: base(ruleset, beatmap)
{ {
} }
public override double Calculate(Dictionary<string, double> categoryDifficulty = null) => 0; protected override DifficultyAttributes Calculate(IBeatmap beatmap, Mod[] mods, double timeRate)
{
if (!beatmap.HitObjects.Any())
return new CatchDifficultyAttributes(mods, 0);
var catcher = new CatcherArea.Catcher(beatmap.BeatmapInfo.BaseDifficulty);
float halfCatchWidth = catcher.CatchWidth * 0.5f;
var difficultyHitObjects = new List<CatchDifficultyHitObject>();
foreach (var hitObject in beatmap.HitObjects)
{
// We want to only consider fruits that contribute to the combo. Droplets are addressed as accuracy and spinners are not relevant for "skill" calculations.
if (hitObject is Fruit)
{
difficultyHitObjects.Add(new CatchDifficultyHitObject((CatchHitObject)hitObject, halfCatchWidth));
}
if (hitObject is JuiceStream)
difficultyHitObjects.AddRange(hitObject.NestedHitObjects.OfType<CatchHitObject>().Where(o => !(o is TinyDroplet)).Select(o => new CatchDifficultyHitObject(o, halfCatchWidth)));
}
difficultyHitObjects.Sort((a, b) => a.BaseHitObject.StartTime.CompareTo(b.BaseHitObject.StartTime));
if (!calculateStrainValues(difficultyHitObjects, timeRate))
return new CatchDifficultyAttributes(mods, 0);
// this is the same as osu!, so there's potential to share the implementation... maybe
double preEmpt = BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.ApproachRate, 1800, 1200, 450) / timeRate;
double starRating = Math.Sqrt(calculateDifficulty(difficultyHitObjects, timeRate)) * star_scaling_factor;
return new CatchDifficultyAttributes(mods, starRating)
{
ApproachRate = preEmpt > 1200.0 ? -(preEmpt - 1800.0) / 120.0 : -(preEmpt - 1200.0) / 150.0 + 5.0,
MaxCombo = difficultyHitObjects.Count
};
}
private bool calculateStrainValues(List<CatchDifficultyHitObject> objects, double timeRate)
{
CatchDifficultyHitObject lastObject = null;
if (!objects.Any()) return false;
// Traverse hitObjects in pairs to calculate the strain value of NextHitObject from the strain value of CurrentHitObject and environment.
foreach (var currentObject in objects)
{
if (lastObject != null)
currentObject.CalculateStrains(lastObject, timeRate);
lastObject = currentObject;
}
return true;
}
private double calculateDifficulty(List<CatchDifficultyHitObject> objects, double timeRate)
{
// The strain step needs to be adjusted for the algorithm to be considered equal with speed changing mods
double actualStrainStep = strain_step * timeRate;
// Find the highest strain value within each strain step
var highestStrains = new List<double>();
double intervalEndTime = actualStrainStep;
double maximumStrain = 0; // We need to keep track of the maximum strain in the current interval
CatchDifficultyHitObject previousHitObject = null;
foreach (CatchDifficultyHitObject hitObject in objects)
{
// While we are beyond the current interval push the currently available maximum to our strain list
while (hitObject.BaseHitObject.StartTime > intervalEndTime)
{
highestStrains.Add(maximumStrain);
// The maximum strain of the next interval is not zero by default! We need to take the last hitObject we encountered, take its strain and apply the decay
// until the beginning of the next interval.
if (previousHitObject == null)
{
maximumStrain = 0;
}
else
{
double decay = Math.Pow(CatchDifficultyHitObject.DECAY_BASE, (intervalEndTime - previousHitObject.BaseHitObject.StartTime) / 1000);
maximumStrain = previousHitObject.Strain * decay;
}
// Go to the next time interval
intervalEndTime += actualStrainStep;
}
// Obtain maximum strain
maximumStrain = Math.Max(hitObject.Strain, maximumStrain);
previousHitObject = hitObject;
}
// Build the weighted sum over the highest strains for each interval
double difficulty = 0;
double weight = 1;
highestStrains.Sort((a, b) => b.CompareTo(a)); // Sort from highest to lowest strain.
foreach (double strain in highestStrains)
{
difficulty += weight * strain;
weight *= decay_weight;
}
return difficulty;
}
} }
} }

View 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);
}
}
}

View File

@ -24,6 +24,11 @@ namespace osu.Game.Rulesets.Catch.Objects
public int ComboIndex { get; set; } public int ComboIndex { get; set; }
/// <summary>
/// The distance for a fruit to to next hyper if it's not a hyper.
/// </summary>
public float DistanceToHyperDash { get; set; }
/// <summary> /// <summary>
/// The next fruit starts a new combo. Used for explodey. /// The next fruit starts a new combo. Used for explodey.
/// </summary> /// </summary>

View File

@ -42,6 +42,8 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
{ {
public virtual bool CanBePlated => false; public virtual bool CanBePlated => false;
public virtual bool StaysOnPlate => CanBePlated;
protected DrawableCatchHitObject(CatchHitObject hitObject) protected DrawableCatchHitObject(CatchHitObject hitObject)
: base(hitObject) : base(hitObject)
{ {

View File

@ -13,6 +13,8 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
{ {
private Pulp pulp; private Pulp pulp;
public override bool StaysOnPlate => false;
public DrawableDroplet(Droplet h) public DrawableDroplet(Droplet h)
: base(h) : base(h)
{ {

View File

@ -48,6 +48,16 @@ namespace osu.Game.Rulesets.Catch.UI
public void OnJudgement(DrawableCatchHitObject fruit, Judgement judgement) public void OnJudgement(DrawableCatchHitObject fruit, Judgement judgement)
{ {
void runAfterLoaded(Action action)
{
// this is required to make this run after the last caught fruit runs UpdateState at least once.
// TODO: find a better alternative
if (lastPlateableFruit.IsLoaded)
action();
else
lastPlateableFruit.OnLoadComplete = _ => action();
}
if (judgement.IsHit && fruit.CanBePlated) if (judgement.IsHit && fruit.CanBePlated)
{ {
var caughtFruit = (DrawableCatchHitObject)GetVisualRepresentation?.Invoke(fruit.HitObject); var caughtFruit = (DrawableCatchHitObject)GetVisualRepresentation?.Invoke(fruit.HitObject);
@ -63,21 +73,17 @@ namespace osu.Game.Rulesets.Catch.UI
caughtFruit.LifetimeEnd = double.MaxValue; caughtFruit.LifetimeEnd = double.MaxValue;
MovableCatcher.Add(caughtFruit); MovableCatcher.Add(caughtFruit);
lastPlateableFruit = caughtFruit; lastPlateableFruit = caughtFruit;
if (!fruit.StaysOnPlate)
runAfterLoaded(() => MovableCatcher.Explode(caughtFruit));
} }
if (fruit.HitObject.LastInCombo) if (fruit.HitObject.LastInCombo)
{ {
if (judgement.IsHit) if (judgement.IsHit)
{ runAfterLoaded(() => MovableCatcher.Explode());
// this is required to make this run after the last caught fruit runs UpdateState at least once.
// TODO: find a better alternative
if (lastPlateableFruit.IsLoaded)
MovableCatcher.Explode();
else
lastPlateableFruit.OnLoadComplete = _ => { MovableCatcher.Explode(); };
}
else else
MovableCatcher.Drop(); MovableCatcher.Drop();
} }
@ -99,6 +105,11 @@ namespace osu.Game.Rulesets.Catch.UI
public class Catcher : Container, IKeyBindingHandler<CatchAction> public class Catcher : Container, IKeyBindingHandler<CatchAction>
{ {
/// <summary>
/// Width of the area that can be used to attempt catches during gameplay.
/// </summary>
internal float CatchWidth => CATCHER_SIZE * Math.Abs(Scale.X);
private Container<DrawableHitObject> caughtFruit; private Container<DrawableHitObject> caughtFruit;
public Container ExplodingFruitTarget; public Container ExplodingFruitTarget;
@ -226,15 +237,15 @@ namespace osu.Game.Rulesets.Catch.UI
/// <returns>Whether the catch is possible.</returns> /// <returns>Whether the catch is possible.</returns>
public bool AttemptCatch(CatchHitObject fruit) public bool AttemptCatch(CatchHitObject fruit)
{ {
double halfCatcherWidth = CATCHER_SIZE * Math.Abs(Scale.X) * 0.5f; float halfCatchWidth = CatchWidth * 0.5f;
// this stuff wil disappear once we move fruit to non-relative coordinate space in the future. // this stuff wil disappear once we move fruit to non-relative coordinate space in the future.
var catchObjectPosition = fruit.X * CatchPlayfield.BASE_WIDTH; var catchObjectPosition = fruit.X * CatchPlayfield.BASE_WIDTH;
var catcherPosition = Position.X * CatchPlayfield.BASE_WIDTH; var catcherPosition = Position.X * CatchPlayfield.BASE_WIDTH;
var validCatch = var validCatch =
catchObjectPosition >= catcherPosition - halfCatcherWidth && catchObjectPosition >= catcherPosition - halfCatchWidth &&
catchObjectPosition <= catcherPosition + halfCatcherWidth; catchObjectPosition <= catcherPosition + halfCatchWidth;
if (validCatch && fruit.HyperDash) if (validCatch && fruit.HyperDash)
{ {
@ -378,28 +389,31 @@ namespace osu.Game.Rulesets.Catch.UI
var fruit = caughtFruit.ToArray(); var fruit = caughtFruit.ToArray();
foreach (var f in fruit) foreach (var f in fruit)
Explode(f);
}
public void Explode(DrawableHitObject fruit)
{
var originalX = fruit.X * Scale.X;
if (ExplodingFruitTarget != null)
{ {
var originalX = f.X * Scale.X; fruit.Anchor = Anchor.TopLeft;
fruit.Position = caughtFruit.ToSpaceOfOtherDrawable(fruit.DrawPosition, ExplodingFruitTarget);
if (ExplodingFruitTarget != null) caughtFruit.Remove(fruit);
{
f.Anchor = Anchor.TopLeft;
f.Position = caughtFruit.ToSpaceOfOtherDrawable(f.DrawPosition, ExplodingFruitTarget);
caughtFruit.Remove(f); ExplodingFruitTarget.Add(fruit);
ExplodingFruitTarget.Add(f);
}
f.MoveToY(f.Y - 50, 250, Easing.OutSine)
.Then()
.MoveToY(f.Y + 50, 500, Easing.InSine);
f.MoveToX(f.X + originalX * 6, 1000);
f.FadeOut(750);
f.Expire();
} }
fruit.MoveToY(fruit.Y - 50, 250, Easing.OutSine)
.Then()
.MoveToY(fruit.Y + 50, 500, Easing.InSine);
fruit.MoveToX(fruit.X + originalX * 6, 1000);
fruit.FadeOut(750);
fruit.Expire();
} }
private class CatcherSprite : Sprite private class CatcherSprite : Sprite

View File

@ -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())
{
}
}
}

View File

@ -29,47 +29,36 @@ namespace osu.Game.Rulesets.Mania.Difficulty
/// </summary> /// </summary>
private const double decay_weight = 0.9; private const double decay_weight = 0.9;
/// <summary> private readonly bool isForCurrentRuleset;
/// HitObjects are stored as a member variable.
/// </summary>
private readonly List<ManiaHitObjectDifficulty> difficultyHitObjects = new List<ManiaHitObjectDifficulty>();
public ManiaDifficultyCalculator(IBeatmap beatmap) public ManiaDifficultyCalculator(Ruleset ruleset, WorkingBeatmap beatmap)
: base(beatmap) : base(ruleset, beatmap)
{ {
isForCurrentRuleset = beatmap.BeatmapInfo.Ruleset.Equals(ruleset.RulesetInfo);
} }
public ManiaDifficultyCalculator(IBeatmap beatmap, Mod[] mods) protected override DifficultyAttributes Calculate(IBeatmap beatmap, Mod[] mods, double timeRate)
: base(beatmap, mods)
{ {
} var difficultyHitObjects = new List<ManiaHitObjectDifficulty>();
public override double Calculate(Dictionary<string, double> categoryDifficulty = null) int columnCount = ((ManiaBeatmap)beatmap).TotalColumns;
{
// Fill our custom DifficultyHitObject class, that carries additional information
difficultyHitObjects.Clear();
int columnCount = (Beatmap as ManiaBeatmap)?.TotalColumns ?? 7;
// Sort DifficultyHitObjects by StartTime of the HitObjects - just to make sure. // Sort DifficultyHitObjects by StartTime of the HitObjects - just to make sure.
// Note: Stable sort is done so that the ordering of hitobjects with equal start times doesn't change // Note: Stable sort is done so that the ordering of hitobjects with equal start times doesn't change
difficultyHitObjects.AddRange(Beatmap.HitObjects.Select(h => new ManiaHitObjectDifficulty((ManiaHitObject)h, columnCount)).OrderBy(h => h.BaseHitObject.StartTime)); difficultyHitObjects.AddRange(beatmap.HitObjects.Select(h => new ManiaHitObjectDifficulty((ManiaHitObject)h, columnCount)).OrderBy(h => h.BaseHitObject.StartTime));
if (!calculateStrainValues()) if (!calculateStrainValues(difficultyHitObjects, timeRate))
return 0; return new DifficultyAttributes(mods, 0);
double starRating = calculateDifficulty() * star_scaling_factor; double starRating = calculateDifficulty(difficultyHitObjects, timeRate) * star_scaling_factor;
if (categoryDifficulty != null) return new DifficultyAttributes(mods, starRating);
categoryDifficulty["Strain"] = starRating;
return starRating;
} }
private bool calculateStrainValues() private bool calculateStrainValues(List<ManiaHitObjectDifficulty> objects, double timeRate)
{ {
// Traverse hitObjects in pairs to calculate the strain value of NextHitObject from the strain value of CurrentHitObject and environment. // Traverse hitObjects in pairs to calculate the strain value of NextHitObject from the strain value of CurrentHitObject and environment.
using (List<ManiaHitObjectDifficulty>.Enumerator hitObjectsEnumerator = difficultyHitObjects.GetEnumerator()) using (var hitObjectsEnumerator = objects.GetEnumerator())
{ {
if (!hitObjectsEnumerator.MoveNext()) if (!hitObjectsEnumerator.MoveNext())
return false; return false;
@ -80,7 +69,7 @@ namespace osu.Game.Rulesets.Mania.Difficulty
while (hitObjectsEnumerator.MoveNext()) while (hitObjectsEnumerator.MoveNext())
{ {
var next = hitObjectsEnumerator.Current; var next = hitObjectsEnumerator.Current;
next?.CalculateStrains(current, TimeRate); next?.CalculateStrains(current, timeRate);
current = next; current = next;
} }
@ -88,9 +77,9 @@ namespace osu.Game.Rulesets.Mania.Difficulty
} }
} }
private double calculateDifficulty() private double calculateDifficulty(List<ManiaHitObjectDifficulty> objects, double timeRate)
{ {
double actualStrainStep = strain_step * TimeRate; double actualStrainStep = strain_step * timeRate;
// Find the highest strain value within each strain step // Find the highest strain value within each strain step
List<double> highestStrains = new List<double>(); List<double> highestStrains = new List<double>();
@ -98,7 +87,7 @@ namespace osu.Game.Rulesets.Mania.Difficulty
double maximumStrain = 0; // We need to keep track of the maximum strain in the current interval double maximumStrain = 0; // We need to keep track of the maximum strain in the current interval
ManiaHitObjectDifficulty previousHitObject = null; ManiaHitObjectDifficulty previousHitObject = null;
foreach (var hitObject in difficultyHitObjects) foreach (var hitObject in objects)
{ {
// While we are beyond the current interval push the currently available maximum to our strain list // While we are beyond the current interval push the currently available maximum to our strain list
while (hitObject.BaseHitObject.StartTime > intervalEndTime) while (hitObject.BaseHitObject.StartTime > intervalEndTime)
@ -143,21 +132,35 @@ namespace osu.Game.Rulesets.Mania.Difficulty
return difficulty; return difficulty;
} }
protected override Mod[] DifficultyAdjustmentMods => new Mod[] protected override Mod[] DifficultyAdjustmentMods
{ {
new ManiaModDoubleTime(), get
new ManiaModHalfTime(), {
new ManiaModEasy(), var mods = new Mod[]
new ManiaModHardRock(), {
new ManiaModKey1(), new ManiaModDoubleTime(),
new ManiaModKey2(), new ManiaModHalfTime(),
new ManiaModKey3(), new ManiaModEasy(),
new ManiaModKey4(), new ManiaModHardRock(),
new ManiaModKey5(), };
new ManiaModKey6(),
new ManiaModKey7(), if (isForCurrentRuleset)
new ManiaModKey8(), return mods;
new ManiaModKey9(),
}; // if we are a convert, we can be played in any key mod.
return mods.Concat(new Mod[]
{
new ManiaModKey1(),
new ManiaModKey2(),
new ManiaModKey3(),
new ManiaModKey4(),
new ManiaModKey5(),
new ManiaModKey6(),
new ManiaModKey7(),
new ManiaModKey8(),
new ManiaModKey9(),
}).ToArray();
}
}
} }
} }

View File

@ -25,7 +25,7 @@ namespace osu.Game.Rulesets.Mania.Difficulty
private int countMeh; private int countMeh;
private int countMiss; private int countMiss;
public ManiaPerformanceCalculator(Ruleset ruleset, IBeatmap beatmap, Score score) public ManiaPerformanceCalculator(Ruleset ruleset, WorkingBeatmap beatmap, Score score)
: base(ruleset, beatmap, score) : base(ruleset, beatmap, score)
{ {
} }
@ -82,7 +82,7 @@ namespace osu.Game.Rulesets.Mania.Difficulty
private double computeStrainValue() private double computeStrainValue()
{ {
// Obtain strain difficulty // Obtain strain difficulty
double strainValue = Math.Pow(5 * Math.Max(1, Attributes["Strain"] / 0.2) - 4.0, 2.2) / 135.0; double strainValue = Math.Pow(5 * Math.Max(1, Attributes.StarRating / 0.2) - 4.0, 2.2) / 135.0;
// Longer maps are worth more // Longer maps are worth more
strainValue *= 1.0 + 0.1 * Math.Min(1.0, totalHits / 1500.0); strainValue *= 1.0 + 0.1 * Math.Min(1.0, totalHits / 1500.0);

View File

@ -29,7 +29,7 @@ namespace osu.Game.Rulesets.Mania
{ {
public override RulesetContainer CreateRulesetContainerWith(WorkingBeatmap beatmap) => new ManiaRulesetContainer(this, beatmap); public override RulesetContainer CreateRulesetContainerWith(WorkingBeatmap beatmap) => new ManiaRulesetContainer(this, beatmap);
public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new ManiaBeatmapConverter(beatmap); public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new ManiaBeatmapConverter(beatmap);
public override PerformanceCalculator CreatePerformanceCalculator(IBeatmap beatmap, Score score) => new ManiaPerformanceCalculator(this, beatmap, score); public override PerformanceCalculator CreatePerformanceCalculator(WorkingBeatmap beatmap, Score score) => new ManiaPerformanceCalculator(this, beatmap, score);
public override IEnumerable<Mod> ConvertLegacyMods(LegacyMods mods) public override IEnumerable<Mod> ConvertLegacyMods(LegacyMods mods)
{ {
@ -147,7 +147,7 @@ namespace osu.Game.Rulesets.Mania
public override Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.fa_osu_mania_o }; public override Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.fa_osu_mania_o };
public override DifficultyCalculator CreateDifficultyCalculator(IBeatmap beatmap, Mod[] mods = null) => new ManiaDifficultyCalculator(beatmap, mods); public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new ManiaDifficultyCalculator(this, beatmap);
public override int? LegacyID => 3; public override int? LegacyID => 3;

View File

@ -15,6 +15,8 @@ namespace osu.Game.Rulesets.Mania.UI
{ {
internal class HitExplosion : CompositeDrawable internal class HitExplosion : CompositeDrawable
{ {
public override bool RemoveWhenNotAlive => true;
private readonly CircularContainer circle; private readonly CircularContainer circle;
public HitExplosion(DrawableHitObject judgedObject) public HitExplosion(DrawableHitObject judgedObject)

View File

@ -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())
{
}
}
}

View File

@ -0,0 +1,19 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Mods;
namespace osu.Game.Rulesets.Osu.Difficulty
{
public class OsuDifficultyAttributes : DifficultyAttributes
{
public double AimStrain;
public double SpeedStrain;
public OsuDifficultyAttributes(Mod[] mods, double starRating)
: base(mods, starRating)
{
}
}
}

View File

@ -2,7 +2,7 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System; using System;
using System.Collections.Generic; using System.Linq;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
@ -18,31 +18,26 @@ namespace osu.Game.Rulesets.Osu.Difficulty
private const int section_length = 400; private const int section_length = 400;
private const double difficulty_multiplier = 0.0675; private const double difficulty_multiplier = 0.0675;
public OsuDifficultyCalculator(IBeatmap beatmap) public OsuDifficultyCalculator(Ruleset ruleset, WorkingBeatmap beatmap)
: base(beatmap) : base(ruleset, beatmap)
{ {
} }
public OsuDifficultyCalculator(IBeatmap beatmap, Mod[] mods) protected override DifficultyAttributes Calculate(IBeatmap beatmap, Mod[] mods, double timeRate)
: base(beatmap, mods)
{ {
} OsuDifficultyBeatmap difficultyBeatmap = new OsuDifficultyBeatmap(beatmap.HitObjects.Cast<OsuHitObject>().ToList(), timeRate);
public override double Calculate(Dictionary<string, double> categoryDifficulty = null)
{
OsuDifficultyBeatmap beatmap = new OsuDifficultyBeatmap((List<OsuHitObject>)Beatmap.HitObjects, TimeRate);
Skill[] skills = Skill[] skills =
{ {
new Aim(), new Aim(),
new Speed() new Speed()
}; };
double sectionLength = section_length * TimeRate; double sectionLength = section_length * timeRate;
// The first object doesn't generate a strain, so we begin with an incremented section end // The first object doesn't generate a strain, so we begin with an incremented section end
double currentSectionEnd = 2 * sectionLength; double currentSectionEnd = 2 * sectionLength;
foreach (OsuDifficultyHitObject h in beatmap) foreach (OsuDifficultyHitObject h in difficultyBeatmap)
{ {
while (h.BaseObject.StartTime > currentSectionEnd) while (h.BaseObject.StartTime > currentSectionEnd)
{ {
@ -61,16 +56,13 @@ namespace osu.Game.Rulesets.Osu.Difficulty
double aimRating = Math.Sqrt(skills[0].DifficultyValue()) * difficulty_multiplier; double aimRating = Math.Sqrt(skills[0].DifficultyValue()) * difficulty_multiplier;
double speedRating = Math.Sqrt(skills[1].DifficultyValue()) * difficulty_multiplier; double speedRating = Math.Sqrt(skills[1].DifficultyValue()) * difficulty_multiplier;
double starRating = aimRating + speedRating + Math.Abs(aimRating - speedRating) / 2; double starRating = aimRating + speedRating + Math.Abs(aimRating - speedRating) / 2;
if (categoryDifficulty != null) return new OsuDifficultyAttributes(mods, starRating)
{ {
categoryDifficulty.Add("Aim", aimRating); AimStrain = aimRating,
categoryDifficulty.Add("Speed", speedRating); SpeedStrain = speedRating
} };
return starRating;
} }
protected override Mod[] DifficultyAdjustmentMods => new Mod[] protected override Mod[] DifficultyAdjustmentMods => new Mod[]

View File

@ -15,6 +15,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty
{ {
public class OsuPerformanceCalculator : PerformanceCalculator public class OsuPerformanceCalculator : PerformanceCalculator
{ {
public new OsuDifficultyAttributes Attributes => (OsuDifficultyAttributes)base.Attributes;
private readonly int countHitCircles; private readonly int countHitCircles;
private readonly int beatmapMaxCombo; private readonly int beatmapMaxCombo;
@ -37,7 +39,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
private int countMeh; private int countMeh;
private int countMiss; private int countMiss;
public OsuPerformanceCalculator(Ruleset ruleset, IBeatmap beatmap, Score score) public OsuPerformanceCalculator(Ruleset ruleset, WorkingBeatmap beatmap, Score score)
: base(ruleset, beatmap, score) : base(ruleset, beatmap, score)
{ {
countHitCircles = Beatmap.HitObjects.Count(h => h is HitCircle); countHitCircles = Beatmap.HitObjects.Count(h => h is HitCircle);
@ -102,7 +104,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
private double computeAimValue() private double computeAimValue()
{ {
double aimValue = Math.Pow(5.0f * Math.Max(1.0f, Attributes["Aim"] / 0.0675f) - 4.0f, 3.0f) / 100000.0f; double aimValue = Math.Pow(5.0f * Math.Max(1.0f, Attributes.AimStrain / 0.0675f) - 4.0f, 3.0f) / 100000.0f;
// Longer maps are worth more // Longer maps are worth more
double lengthBonus = 0.95f + 0.4f * Math.Min(1.0f, totalHits / 2000.0f) + double lengthBonus = 0.95f + 0.4f * Math.Min(1.0f, totalHits / 2000.0f) +
@ -151,7 +153,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
private double computeSpeedValue() private double computeSpeedValue()
{ {
double speedValue = Math.Pow(5.0f * Math.Max(1.0f, Attributes["Speed"] / 0.0675f) - 4.0f, 3.0f) / 100000.0f; double speedValue = Math.Pow(5.0f * Math.Max(1.0f, Attributes.SpeedStrain / 0.0675f) - 4.0f, 3.0f) / 100000.0f;
// Longer maps are worth more // Longer maps are worth more
speedValue *= 0.95f + 0.4f * Math.Min(1.0f, totalHits / 2000.0f) + speedValue *= 0.95f + 0.4f * Math.Min(1.0f, totalHits / 2000.0f) +

View File

@ -4,6 +4,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.MathUtils;
using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Drawables;
using OpenTK; using OpenTK;
using osu.Game.Graphics; using osu.Game.Graphics;
@ -89,7 +90,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
// find the next vector2 in the curve which is not equal to our current position to infer a rotation. // find the next vector2 in the curve which is not equal to our current position to infer a rotation.
for (int i = searchStart; i >= 0 && i < curve.Count; i += direction) for (int i = searchStart; i >= 0 && i < curve.Count; i += direction)
{ {
if (curve[i] == Position) if (Precision.AlmostEquals(curve[i], Position))
continue; continue;
Rotation = MathHelper.RadiansToDegrees((float)Math.Atan2(curve[i].Y - Position.Y, curve[i].X - Position.X)); Rotation = MathHelper.RadiansToDegrees((float)Math.Atan2(curve[i].Y - Position.Y, curve[i].X - Position.X));

View File

@ -139,8 +139,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
var texture = new Texture(textureWidth, 1); var texture = new Texture(textureWidth, 1);
//initialise background //initialise background
var upload = new TextureUpload(textureWidth * 4); var raw = new RawTexture(textureWidth, 1);
var bytes = upload.Data; var bytes = raw.Data;
const float aa_portion = 0.02f; const float aa_portion = 0.02f;
const float border_portion = 0.128f; const float border_portion = 0.128f;
@ -171,7 +171,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
} }
} }
texture.SetData(upload); texture.SetData(new TextureUpload(raw));
path.Texture = texture; path.Texture = texture;
container.ForceRedraw(); container.ForceRedraw();

View File

@ -120,9 +120,9 @@ namespace osu.Game.Rulesets.Osu
public override Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.fa_osu_osu_o }; public override Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.fa_osu_osu_o };
public override DifficultyCalculator CreateDifficultyCalculator(IBeatmap beatmap, Mod[] mods = null) => new OsuDifficultyCalculator(beatmap, mods); public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new OsuDifficultyCalculator(this, beatmap);
public override PerformanceCalculator CreatePerformanceCalculator(IBeatmap beatmap, Score score) => new OsuPerformanceCalculator(this, beatmap, score); public override PerformanceCalculator CreatePerformanceCalculator(WorkingBeatmap beatmap, Score score) => new OsuPerformanceCalculator(this, beatmap, score);
public override HitObjectComposer CreateHitObjectComposer() => new OsuHitObjectComposer(this); public override HitObjectComposer CreateHitObjectComposer() => new OsuHitObjectComposer(this);

View File

@ -18,7 +18,7 @@ using osu.Game.Rulesets.Replays;
namespace osu.Game.Rulesets.Osu.UI namespace osu.Game.Rulesets.Osu.UI
{ {
public class OsuRulesetContainer : RulesetContainer<OsuHitObject> public class OsuRulesetContainer : RulesetContainer<OsuPlayfield, OsuHitObject>
{ {
public OsuRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap) public OsuRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap)
: base(ruleset, beatmap) : base(ruleset, beatmap)

View File

@ -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())
{
}
}
}

View File

@ -27,54 +27,33 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
/// </summary> /// </summary>
private const double decay_weight = 0.9; private const double decay_weight = 0.9;
/// <summary> public TaikoDifficultyCalculator(Ruleset ruleset, WorkingBeatmap beatmap)
/// HitObjects are stored as a member variable. : base(ruleset, beatmap)
/// </summary>
private readonly List<TaikoHitObjectDifficulty> difficultyHitObjects = new List<TaikoHitObjectDifficulty>();
public TaikoDifficultyCalculator(IBeatmap beatmap)
: base(beatmap)
{ {
} }
public TaikoDifficultyCalculator(IBeatmap beatmap, Mod[] mods) protected override DifficultyAttributes Calculate(IBeatmap beatmap, Mod[] mods, double timeRate)
: base(beatmap, mods)
{ {
} var difficultyHitObjects = new List<TaikoHitObjectDifficulty>();
public override double Calculate(Dictionary<string, double> categoryDifficulty = null) foreach (var hitObject in beatmap.HitObjects)
{
// Fill our custom DifficultyHitObject class, that carries additional information
difficultyHitObjects.Clear();
foreach (var hitObject in Beatmap.HitObjects)
difficultyHitObjects.Add(new TaikoHitObjectDifficulty((TaikoHitObject)hitObject)); difficultyHitObjects.Add(new TaikoHitObjectDifficulty((TaikoHitObject)hitObject));
// Sort DifficultyHitObjects by StartTime of the HitObjects - just to make sure. // Sort DifficultyHitObjects by StartTime of the HitObjects - just to make sure.
difficultyHitObjects.Sort((a, b) => a.BaseHitObject.StartTime.CompareTo(b.BaseHitObject.StartTime)); difficultyHitObjects.Sort((a, b) => a.BaseHitObject.StartTime.CompareTo(b.BaseHitObject.StartTime));
if (!calculateStrainValues()) return 0; if (!calculateStrainValues(difficultyHitObjects, timeRate))
return new DifficultyAttributes(mods, 0);
double starRating = calculateDifficulty() * star_scaling_factor; double starRating = calculateDifficulty(difficultyHitObjects, timeRate) * star_scaling_factor;
if (categoryDifficulty != null) return new DifficultyAttributes(mods, starRating);
categoryDifficulty["Strain"] = starRating;
return starRating;
} }
protected override Mod[] DifficultyAdjustmentMods => new Mod[] private bool calculateStrainValues(List<TaikoHitObjectDifficulty> objects, double timeRate)
{
new TaikoModDoubleTime(),
new TaikoModHalfTime(),
new TaikoModEasy(),
new TaikoModHardRock(),
};
private bool calculateStrainValues()
{ {
// Traverse hitObjects in pairs to calculate the strain value of NextHitObject from the strain value of CurrentHitObject and environment. // Traverse hitObjects in pairs to calculate the strain value of NextHitObject from the strain value of CurrentHitObject and environment.
using (List<TaikoHitObjectDifficulty>.Enumerator hitObjectsEnumerator = difficultyHitObjects.GetEnumerator()) using (var hitObjectsEnumerator = objects.GetEnumerator())
{ {
if (!hitObjectsEnumerator.MoveNext()) return false; if (!hitObjectsEnumerator.MoveNext()) return false;
@ -84,7 +63,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
while (hitObjectsEnumerator.MoveNext()) while (hitObjectsEnumerator.MoveNext())
{ {
var next = hitObjectsEnumerator.Current; var next = hitObjectsEnumerator.Current;
next?.CalculateStrains(current, TimeRate); next?.CalculateStrains(current, timeRate);
current = next; current = next;
} }
@ -92,9 +71,9 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
} }
} }
private double calculateDifficulty() private double calculateDifficulty(List<TaikoHitObjectDifficulty> objects, double timeRate)
{ {
double actualStrainStep = strain_step * TimeRate; double actualStrainStep = strain_step * timeRate;
// Find the highest strain value within each strain step // Find the highest strain value within each strain step
List<double> highestStrains = new List<double>(); List<double> highestStrains = new List<double>();
@ -102,7 +81,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
double maximumStrain = 0; // We need to keep track of the maximum strain in the current interval double maximumStrain = 0; // We need to keep track of the maximum strain in the current interval
TaikoHitObjectDifficulty previousHitObject = null; TaikoHitObjectDifficulty previousHitObject = null;
foreach (var hitObject in difficultyHitObjects) foreach (var hitObject in objects)
{ {
// While we are beyond the current interval push the currently available maximum to our strain list // While we are beyond the current interval push the currently available maximum to our strain list
while (hitObject.BaseHitObject.StartTime > intervalEndTime) while (hitObject.BaseHitObject.StartTime > intervalEndTime)
@ -144,5 +123,13 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
return difficulty; return difficulty;
} }
protected override Mod[] DifficultyAdjustmentMods => new Mod[]
{
new TaikoModDoubleTime(),
new TaikoModHalfTime(),
new TaikoModEasy(),
new TaikoModHardRock(),
};
} }
} }

View File

@ -22,10 +22,10 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
private int countMeh; private int countMeh;
private int countMiss; private int countMiss;
public TaikoPerformanceCalculator(Ruleset ruleset, IBeatmap beatmap, Score score) public TaikoPerformanceCalculator(Ruleset ruleset, WorkingBeatmap beatmap, Score score)
: base(ruleset, beatmap, score) : base(ruleset, beatmap, score)
{ {
beatmapMaxCombo = beatmap.HitObjects.Count(h => h is Hit); beatmapMaxCombo = Beatmap.HitObjects.Count(h => h is Hit);
} }
public override double Calculate(Dictionary<string, double> categoryDifficulty = null) public override double Calculate(Dictionary<string, double> categoryDifficulty = null)
@ -68,7 +68,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
private double computeStrainValue() private double computeStrainValue()
{ {
double strainValue = Math.Pow(5.0 * Math.Max(1.0, Attributes["Strain"] / 0.0075) - 4.0, 2.0) / 100000.0; double strainValue = Math.Pow(5.0 * Math.Max(1.0, Attributes.StarRating / 0.0075) - 4.0, 2.0) / 100000.0;
// Longer maps are worth more // Longer maps are worth more
double lengthBonus = 1 + 0.1f * Math.Min(1.0, totalHits / 1500.0); double lengthBonus = 1 + 0.1f * Math.Min(1.0, totalHits / 1500.0);

View File

@ -86,6 +86,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
switch (State.Value) switch (State.Value)
{ {
case ArmedState.Idle: case ArmedState.Idle:
UnproxyContent();
this.Delay(HitObject.HitWindows.HalfWindowFor(HitResult.Miss)).Expire(); this.Delay(HitObject.HitWindows.HalfWindowFor(HitResult.Miss)).Expire();
break; break;
case ArmedState.Miss: case ArmedState.Miss:
@ -93,6 +94,10 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
.Expire(); .Expire();
break; break;
case ArmedState.Hit: case ArmedState.Hit:
// If we're far enough away from the left stage, we should bring outselves in front of it
if (X >= -0.05f)
ProxyContent();
var flash = circlePiece?.FlashBox; var flash = circlePiece?.FlashBox;
if (flash != null) if (flash != null)
{ {

View File

@ -20,12 +20,6 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
{ {
public class DrawableSwell : DrawableTaikoHitObject<Swell> public class DrawableSwell : DrawableTaikoHitObject<Swell>
{ {
/// <summary>
/// Invoked when the swell has reached the hit target, i.e. when CurrentTime >= StartTime.
/// This is only ever invoked once.
/// </summary>
public event Action OnStart;
private const float target_ring_thick_border = 1.4f; private const float target_ring_thick_border = 1.4f;
private const float target_ring_thin_border = 1f; private const float target_ring_thin_border = 1f;
private const float target_ring_scale = 5f; private const float target_ring_scale = 5f;
@ -40,7 +34,6 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
/// </summary> /// </summary>
private int userHits; private int userHits;
private bool hasStarted;
private readonly SwellSymbolPiece symbol; private readonly SwellSymbolPiece symbol;
public DrawableSwell(Swell swell) public DrawableSwell(Swell swell)
@ -48,7 +41,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
{ {
FillMode = FillMode.Fit; FillMode = FillMode.Fit;
AddInternal(bodyContainer = new Container Content.Add(bodyContainer = new Container
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Depth = 1, Depth = 1,
@ -177,6 +170,9 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
switch (state) switch (state)
{ {
case ArmedState.Idle:
UnproxyContent();
break;
case ArmedState.Hit: case ArmedState.Hit:
bodyContainer.Delay(untilJudgement).ScaleTo(1.4f, out_transition_time); bodyContainer.Delay(untilJudgement).ScaleTo(1.4f, out_transition_time);
break; break;
@ -195,11 +191,8 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
X = Math.Max(0, X); X = Math.Max(0, X);
double t = Math.Min(HitObject.StartTime, Time.Current); double t = Math.Min(HitObject.StartTime, Time.Current);
if (t == HitObject.StartTime && !hasStarted) if (t == HitObject.StartTime)
{ ProxyContent();
OnStart?.Invoke();
hasStarted = true;
}
} }
private bool? lastWasCentre; private bool? lastWasCentre;

View File

@ -9,10 +9,75 @@ using OpenTK;
using System.Linq; using System.Linq;
using osu.Game.Audio; using osu.Game.Audio;
using System.Collections.Generic; using System.Collections.Generic;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Primitives;
namespace osu.Game.Rulesets.Taiko.Objects.Drawables namespace osu.Game.Rulesets.Taiko.Objects.Drawables
{ {
public abstract class DrawableTaikoHitObject<TaikoHitType> : DrawableHitObject<TaikoHitObject>, IKeyBindingHandler<TaikoAction> public abstract class DrawableTaikoHitObject : DrawableHitObject<TaikoHitObject>, IKeyBindingHandler<TaikoAction>
{
protected readonly Container Content;
private readonly Container proxiedContent;
private readonly Container nonProxiedContent;
protected DrawableTaikoHitObject(TaikoHitObject hitObject)
: base(hitObject)
{
InternalChildren = new[]
{
nonProxiedContent = new Container
{
RelativeSizeAxes = Axes.Both,
Child = Content = new Container { RelativeSizeAxes = Axes.Both }
},
proxiedContent = new Container { RelativeSizeAxes = Axes.Both }
};
}
/// <summary>
/// <see cref="proxiedContent"/> is proxied into an upper layer. We don't want to get masked away otherwise <see cref="proxiedContent"/> would too.
/// </summary>
protected override bool ComputeIsMaskedAway(RectangleF maskingBounds) => false;
private bool isProxied;
/// <summary>
/// Moves <see cref="Content"/> to a layer proxied above the playfield.
/// Does nothing is content is already proxied.
/// </summary>
protected void ProxyContent()
{
if (isProxied) return;
isProxied = true;
nonProxiedContent.Remove(Content);
proxiedContent.Add(Content);
}
/// <summary>
/// Moves <see cref="Content"/> to the normal hitobject layer.
/// Does nothing is content is not currently proxied.
/// </summary>
protected void UnproxyContent()
{
if (!isProxied) return;
isProxied = false;
proxiedContent.Remove(Content);
nonProxiedContent.Add(Content);
}
/// <summary>
/// Creates a proxy for the content of this <see cref="DrawableTaikoHitObject"/>.
/// </summary>
public Drawable CreateProxiedContent() => proxiedContent.CreateProxy();
public abstract bool OnPressed(TaikoAction action);
public virtual bool OnReleased(TaikoAction action) => false;
}
public abstract class DrawableTaikoHitObject<TaikoHitType> : DrawableTaikoHitObject
where TaikoHitType : TaikoHitObject where TaikoHitType : TaikoHitObject
{ {
public override Vector2 OriginPosition => new Vector2(DrawHeight / 2); public override Vector2 OriginPosition => new Vector2(DrawHeight / 2);
@ -34,7 +99,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
RelativeSizeAxes = Axes.Both; RelativeSizeAxes = Axes.Both;
Size = BaseSize = new Vector2(HitObject.IsStrong ? TaikoHitObject.DEFAULT_STRONG_SIZE : TaikoHitObject.DEFAULT_SIZE); Size = BaseSize = new Vector2(HitObject.IsStrong ? TaikoHitObject.DEFAULT_STRONG_SIZE : TaikoHitObject.DEFAULT_SIZE);
InternalChild = MainPiece = CreateMainPiece(); Content.Add(MainPiece = CreateMainPiece());
MainPiece.KiaiMode = HitObject.Kiai; MainPiece.KiaiMode = HitObject.Kiai;
} }
@ -44,9 +109,5 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
protected override string SampleNamespace => "Taiko"; protected override string SampleNamespace => "Taiko";
protected virtual TaikoPiece CreateMainPiece() => new CirclePiece(); protected virtual TaikoPiece CreateMainPiece() => new CirclePiece();
public abstract bool OnPressed(TaikoAction action);
public virtual bool OnReleased(TaikoAction action) => false;
} }
} }

View File

@ -27,14 +27,12 @@ namespace osu.Game.Rulesets.Taiko
public override IEnumerable<KeyBinding> GetDefaultKeyBindings(int variant = 0) => new[] public override IEnumerable<KeyBinding> GetDefaultKeyBindings(int variant = 0) => new[]
{ {
new KeyBinding(InputKey.MouseLeft, TaikoAction.LeftCentre),
new KeyBinding(InputKey.MouseRight, TaikoAction.LeftRim),
new KeyBinding(InputKey.D, TaikoAction.LeftRim), new KeyBinding(InputKey.D, TaikoAction.LeftRim),
new KeyBinding(InputKey.F, TaikoAction.LeftCentre), new KeyBinding(InputKey.F, TaikoAction.LeftCentre),
new KeyBinding(InputKey.J, TaikoAction.RightCentre), new KeyBinding(InputKey.J, TaikoAction.RightCentre),
new KeyBinding(InputKey.K, TaikoAction.RightRim), new KeyBinding(InputKey.K, TaikoAction.RightRim),
new KeyBinding(InputKey.MouseLeft, TaikoAction.LeftCentre),
new KeyBinding(InputKey.MouseLeft, TaikoAction.RightCentre),
new KeyBinding(InputKey.MouseRight, TaikoAction.LeftRim),
new KeyBinding(InputKey.MouseRight, TaikoAction.RightRim),
}; };
public override IEnumerable<Mod> ConvertLegacyMods(LegacyMods mods) public override IEnumerable<Mod> ConvertLegacyMods(LegacyMods mods)
@ -114,9 +112,9 @@ namespace osu.Game.Rulesets.Taiko
public override Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.fa_osu_taiko_o }; public override Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.fa_osu_taiko_o };
public override DifficultyCalculator CreateDifficultyCalculator(IBeatmap beatmap, Mod[] mods = null) => new TaikoDifficultyCalculator(beatmap, mods); public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new TaikoDifficultyCalculator(this, beatmap);
public override PerformanceCalculator CreatePerformanceCalculator(IBeatmap beatmap, Score score) => new TaikoPerformanceCalculator(this, beatmap, score); public override PerformanceCalculator CreatePerformanceCalculator(WorkingBeatmap beatmap, Score score) => new TaikoPerformanceCalculator(this, beatmap, score);
public override int? LegacyID => 1; public override int? LegacyID => 1;

View File

@ -18,6 +18,8 @@ namespace osu.Game.Rulesets.Taiko.UI
/// </summary> /// </summary>
internal class HitExplosion : CircularContainer internal class HitExplosion : CircularContainer
{ {
public override bool RemoveWhenNotAlive => true;
public readonly DrawableHitObject JudgedObject; public readonly DrawableHitObject JudgedObject;
private readonly Box innerFill; private readonly Box innerFill;
@ -66,7 +68,7 @@ namespace osu.Game.Rulesets.Taiko.UI
this.ScaleTo(3f, 1000, Easing.OutQuint); this.ScaleTo(3f, 1000, Easing.OutQuint);
this.FadeOut(500); this.FadeOut(500);
Expire(); Expire(true);
} }
/// <summary> /// <summary>

View File

@ -14,6 +14,8 @@ namespace osu.Game.Rulesets.Taiko.UI
{ {
public class KiaiHitExplosion : CircularContainer public class KiaiHitExplosion : CircularContainer
{ {
public override bool RemoveWhenNotAlive => true;
public readonly DrawableHitObject JudgedObject; public readonly DrawableHitObject JudgedObject;
private readonly bool isRim; private readonly bool isRim;
@ -62,7 +64,7 @@ namespace osu.Game.Rulesets.Taiko.UI
this.ScaleTo(new Vector2(1, 3f), 500, Easing.OutQuint); this.ScaleTo(new Vector2(1, 3f), 500, Easing.OutQuint);
this.FadeOut(250); this.FadeOut(250);
Expire(); Expire(true);
} }
} }
} }

View File

@ -216,10 +216,9 @@ namespace osu.Game.Rulesets.Taiko.UI
if (barline != null) if (barline != null)
barlineContainer.Add(barline.CreateProxy()); barlineContainer.Add(barline.CreateProxy());
// Swells should be moved at the very top of the playfield when they reach the hit target var taikoObject = h as DrawableTaikoHitObject;
var swell = h as DrawableSwell; if (taikoObject != null)
if (swell != null) topLevelHitContainer.Add(taikoObject.CreateProxiedContent());
swell.OnStart += () => topLevelHitContainer.Add(swell.CreateProxy());
} }
internal void OnJudgement(DrawableHitObject judgedObject, Judgement judgement) internal void OnJudgement(DrawableHitObject judgedObject, Judgement judgement)
@ -244,19 +243,6 @@ namespace osu.Game.Rulesets.Taiko.UI
hitExplosionContainer.Children.FirstOrDefault(e => e.JudgedObject == judgedObject)?.VisualiseSecondHit(); hitExplosionContainer.Children.FirstOrDefault(e => e.JudgedObject == judgedObject)?.VisualiseSecondHit();
else else
{ {
if (judgedObject.X >= -0.05f && judgedObject is DrawableHit)
{
// If we're far enough away from the left stage, we should bring outselves in front of it
// Todo: The following try-catch is temporary for replay rewinding support
try
{
topLevelHitContainer.Add(judgedObject.CreateProxy());
}
catch
{
}
}
hitExplosionContainer.Add(new HitExplosion(judgedObject, isRim)); hitExplosionContainer.Add(new HitExplosion(judgedObject, isRim));
if (judgedObject.HitObject.Kiai) if (judgedObject.HitObject.Kiai)

View File

@ -19,7 +19,7 @@ namespace osu.Game.Tests.Beatmaps.IO
[TestFixture] [TestFixture]
public class ImportBeatmapTest public class ImportBeatmapTest
{ {
private const string osz_path = @"../../../../osu-resources/osu.Game.Resources/Beatmaps/241526 Soleily - Renatus.osz"; public const string TEST_OSZ_PATH = @"../../../../osu-resources/osu.Game.Resources/Beatmaps/241526 Soleily - Renatus.osz";
[Test] [Test]
public void TestImportWhenClosed() public void TestImportWhenClosed()
@ -265,7 +265,7 @@ namespace osu.Game.Tests.Beatmaps.IO
private string createTemporaryBeatmap() private string createTemporaryBeatmap()
{ {
var temp = Path.GetTempFileName() + ".osz"; var temp = Path.GetTempFileName() + ".osz";
File.Copy(osz_path, temp, true); File.Copy(TEST_OSZ_PATH, temp, true);
Assert.IsTrue(File.Exists(temp)); Assert.IsTrue(File.Exists(temp));
return temp; return temp;
} }

View File

@ -2,8 +2,8 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System; using System;
using System.Collections.Generic;
using NUnit.Framework; using NUnit.Framework;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
@ -139,14 +139,14 @@ namespace osu.Game.Tests.NonVisual
private class TestDifficultyCalculator : DifficultyCalculator private class TestDifficultyCalculator : DifficultyCalculator
{ {
public TestDifficultyCalculator(params Mod[] mods) public TestDifficultyCalculator(params Mod[] mods)
: base(null) : base(null, null)
{ {
DifficultyAdjustmentMods = mods; DifficultyAdjustmentMods = mods;
} }
public override double Calculate(Dictionary<string, double> categoryDifficulty = null) => throw new NotImplementedException();
protected override Mod[] DifficultyAdjustmentMods { get; } protected override Mod[] DifficultyAdjustmentMods { get; }
protected override DifficultyAttributes Calculate(IBeatmap beatmap, Mod[] mods, double timeRate) => throw new NotImplementedException();
} }
} }
} }

View File

@ -4,28 +4,48 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Configuration;
using OpenTK; using OpenTK;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Game.Overlays; using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.Timing;
using osu.Game.Beatmaps;
using osu.Game.Screens.Edit.Screens.Compose.Timeline; using osu.Game.Screens.Edit.Screens.Compose.Timeline;
using OpenTK.Graphics;
namespace osu.Game.Tests.Visual namespace osu.Game.Tests.Visual
{ {
[TestFixture] [TestFixture]
public class TestCaseEditorComposeTimeline : OsuTestCase public class TestCaseEditorComposeTimeline : EditorClockTestCase
{ {
public override IReadOnlyList<Type> RequiredTypes => new[] { typeof(TimelineArea), typeof(Timeline), typeof(TimelineButton) }; public override IReadOnlyList<Type> RequiredTypes => new[]
public TestCaseEditorComposeTimeline()
{ {
typeof(TimelineArea),
typeof(Timeline),
typeof(TimelineButton),
typeof(CentreMarker)
};
[BackgroundDependencyLoader]
private void load()
{
Beatmap.Value = new WaveformTestBeatmap();
Children = new Drawable[] Children = new Drawable[]
{ {
new MusicController new FillFlowContainer
{ {
Anchor = Anchor.TopCentre, AutoSizeAxes = Axes.Both,
Origin = Anchor.TopCentre, Direction = FillDirection.Vertical,
State = Visibility.Visible Spacing = new Vector2(0, 5),
Children = new Drawable[]
{
new StartStopButton(),
new AudioVisualiser(),
}
}, },
new TimelineArea new TimelineArea
{ {
@ -36,5 +56,85 @@ namespace osu.Game.Tests.Visual
} }
}; };
} }
private class AudioVisualiser : CompositeDrawable
{
private readonly Drawable marker;
private readonly IBindable<WorkingBeatmap> beatmap = new Bindable<WorkingBeatmap>();
private IAdjustableClock adjustableClock;
public AudioVisualiser()
{
Size = new Vector2(250, 25);
InternalChildren = new[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Alpha = 0.25f,
},
marker = new Box
{
RelativePositionAxes = Axes.X,
RelativeSizeAxes = Axes.Y,
Width = 2,
}
};
}
[BackgroundDependencyLoader]
private void load(IAdjustableClock adjustableClock, IBindableBeatmap beatmap)
{
this.adjustableClock = adjustableClock;
this.beatmap.BindTo(beatmap);
}
protected override void Update()
{
base.Update();
if (beatmap.Value.Track.IsLoaded)
marker.X = (float)(adjustableClock.CurrentTime / beatmap.Value.Track.Length);
}
}
private class StartStopButton : Button
{
private IAdjustableClock adjustableClock;
private bool started;
public StartStopButton()
{
BackgroundColour = Color4.SlateGray;
Size = new Vector2(100, 50);
Text = "Start";
Action = onClick;
}
[BackgroundDependencyLoader]
private void load(IAdjustableClock adjustableClock)
{
this.adjustableClock = adjustableClock;
}
private void onClick()
{
if (started)
{
adjustableClock.Stop();
Text = "Start";
}
else
{
adjustableClock.Start();
Text = "Stop";
}
started = !started;
}
}
} }
} }

View File

@ -8,6 +8,8 @@ using osu.Framework.Allocation;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Online.Multiplayer; using osu.Game.Online.Multiplayer;
using osu.Game.Rulesets; using osu.Game.Rulesets;
using osu.Game.Screens;
using osu.Game.Screens.Backgrounds;
using osu.Game.Screens.Multi.Components; using osu.Game.Screens.Multi.Components;
using osu.Game.Screens.Multi.Screens.Lounge; using osu.Game.Screens.Multi.Screens.Lounge;
using osu.Game.Users; using osu.Game.Users;
@ -198,6 +200,8 @@ namespace osu.Game.Tests.Visual
private class TestLounge : Lounge private class TestLounge : Lounge
{ {
protected override BackgroundScreen CreateBackground() => new BackgroundScreenDefault();
public IEnumerable<DrawableRoom> ChildRooms => RoomsContainer.Children.Where(r => r.MatchingFilter); public IEnumerable<DrawableRoom> ChildRooms => RoomsContainer.Children.Where(r => r.MatchingFilter);
public Room SelectedRoom => Inspector.Room; public Room SelectedRoom => Inspector.Room;

View 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);
}
}
}

View 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);
}
}
}

View 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",
},
});
}
}
}

View 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);
}
}
}

View 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);
}
}
}

View 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 };
}
}
}
}

View File

@ -10,7 +10,6 @@ using osu.Framework.Graphics.Audio;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Sprites;
using osu.Game.Overlays;
namespace osu.Game.Tests.Visual namespace osu.Game.Tests.Visual
{ {
@ -20,22 +19,14 @@ namespace osu.Game.Tests.Visual
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load() private void load()
{ {
Beatmap.Value = new WaveformTestBeatmap();
FillFlowContainer flow; FillFlowContainer flow;
Child = flow = new FillFlowContainer Child = flow = new FillFlowContainer
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Direction = FillDirection.Vertical, Direction = FillDirection.Vertical,
Spacing = new Vector2(0, 10), Spacing = new Vector2(0, 10),
Children = new Drawable[]
{
new MusicController
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
Y = 100,
State = Visibility.Visible
},
}
}; };
for (int i = 1; i <= 16; i *= 2) for (int i = 1; i <= 16; i *= 2)
@ -44,10 +35,9 @@ namespace osu.Game.Tests.Visual
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Resolution = 1f / i, Resolution = 1f / i,
Waveform = Beatmap.Value.Waveform,
}; };
Beatmap.ValueChanged += b => newDisplay.Waveform = b.Waveform;
flow.Add(new Container flow.Add(new Container
{ {
RelativeSizeAxes = Axes.X, RelativeSizeAxes = Axes.X,

View File

@ -13,7 +13,6 @@ using osu.Game.Graphics.Cursor;
using osu.Game.Screens.Edit.Screens.Compose.Timeline; using osu.Game.Screens.Edit.Screens.Compose.Timeline;
using OpenTK; using OpenTK;
using OpenTK.Graphics; using OpenTK.Graphics;
using OpenTK.Input;
namespace osu.Game.Tests.Visual namespace osu.Game.Tests.Visual
{ {
@ -66,7 +65,7 @@ namespace osu.Game.Tests.Visual
{ {
reset(); reset();
AddStep("Set zoom = 10", () => scrollContainer.Zoom = 10); AddStep("Set zoom = 10", () => scrollContainer.Zoom = 10);
AddAssert("Box at 1/2", () => Precision.AlmostEquals(boxQuad.Centre, scrollQuad.Centre)); AddAssert("Box at 1/2", () => Precision.AlmostEquals(boxQuad.Centre, scrollQuad.Centre, 1));
AddAssert("Box width = 10x", () => Precision.AlmostEquals(boxQuad.Size.X, 10 * scrollQuad.Size.X)); AddAssert("Box width = 10x", () => Precision.AlmostEquals(boxQuad.Size.X, 10 * scrollQuad.Size.X));
} }
@ -77,16 +76,12 @@ namespace osu.Game.Tests.Visual
// Scroll in at 0.25 // Scroll in at 0.25
AddStep("Move mouse to 0.25x", () => InputManager.MoveMouseTo(new Vector2(scrollQuad.TopLeft.X + 0.25f * scrollQuad.Size.X, scrollQuad.Centre.Y))); AddStep("Move mouse to 0.25x", () => InputManager.MoveMouseTo(new Vector2(scrollQuad.TopLeft.X + 0.25f * scrollQuad.Size.X, scrollQuad.Centre.Y)));
AddStep("Press ctrl", () => InputManager.PressKey(Key.LControl)); AddStep("Scroll by 3", () => InputManager.ScrollBy(new Vector2(0, 3)));
AddStep("Scroll by 3", () => InputManager.ScrollBy(new Vector2(3, 0)));
AddStep("Release ctrl", () => InputManager.ReleaseKey(Key.LControl));
AddAssert("Box not at 0", () => !Precision.AlmostEquals(boxQuad.TopLeft, scrollQuad.TopLeft)); AddAssert("Box not at 0", () => !Precision.AlmostEquals(boxQuad.TopLeft, scrollQuad.TopLeft));
AddAssert("Box 1/4 at 1/4", () => Precision.AlmostEquals(boxQuad.TopLeft.X + 0.25f * boxQuad.Size.X, scrollQuad.TopLeft.X + 0.25f * scrollQuad.Size.X)); AddAssert("Box 1/4 at 1/4", () => Precision.AlmostEquals(boxQuad.TopLeft.X + 0.25f * boxQuad.Size.X, scrollQuad.TopLeft.X + 0.25f * scrollQuad.Size.X));
// Scroll out at 0.25 // Scroll out at 0.25
AddStep("Press ctrl", () => InputManager.PressKey(Key.LControl)); AddStep("Scroll by -3", () => InputManager.ScrollBy(new Vector2(0, -3)));
AddStep("Scroll by -3", () => InputManager.ScrollBy(new Vector2(-3, 0)));
AddStep("Release ctrl", () => InputManager.ReleaseKey(Key.LControl));
AddAssert("Box at 0", () => Precision.AlmostEquals(boxQuad.TopLeft, scrollQuad.TopLeft)); AddAssert("Box at 0", () => Precision.AlmostEquals(boxQuad.TopLeft, scrollQuad.TopLeft));
AddAssert("Box 1/4 at 1/4", () => Precision.AlmostEquals(boxQuad.TopLeft.X + 0.25f * boxQuad.Size.X, scrollQuad.TopLeft.X + 0.25f * scrollQuad.Size.X)); AddAssert("Box 1/4 at 1/4", () => Precision.AlmostEquals(boxQuad.TopLeft.X + 0.25f * boxQuad.Size.X, scrollQuad.TopLeft.X + 0.25f * scrollQuad.Size.X));
} }
@ -98,15 +93,11 @@ namespace osu.Game.Tests.Visual
// Scroll in at 0.25 // Scroll in at 0.25
AddStep("Move mouse to 0.25x", () => InputManager.MoveMouseTo(new Vector2(scrollQuad.TopLeft.X + 0.25f * scrollQuad.Size.X, scrollQuad.Centre.Y))); AddStep("Move mouse to 0.25x", () => InputManager.MoveMouseTo(new Vector2(scrollQuad.TopLeft.X + 0.25f * scrollQuad.Size.X, scrollQuad.Centre.Y)));
AddStep("Press ctrl", () => InputManager.PressKey(Key.LControl)); AddStep("Scroll by 1", () => InputManager.ScrollBy(new Vector2(0, 1)));
AddStep("Scroll by 1", () => InputManager.ScrollBy(new Vector2(1, 0)));
AddStep("Release ctrl", () => InputManager.ReleaseKey(Key.LControl));
// Scroll in at 0.6 // Scroll in at 0.6
AddStep("Move mouse to 0.75x", () => InputManager.MoveMouseTo(new Vector2(scrollQuad.TopLeft.X + 0.75f * scrollQuad.Size.X, scrollQuad.Centre.Y))); AddStep("Move mouse to 0.75x", () => InputManager.MoveMouseTo(new Vector2(scrollQuad.TopLeft.X + 0.75f * scrollQuad.Size.X, scrollQuad.Centre.Y)));
AddStep("Press ctrl", () => InputManager.PressKey(Key.LControl)); AddStep("Scroll by 1", () => InputManager.ScrollBy(new Vector2(0, 1)));
AddStep("Scroll by 1", () => InputManager.ScrollBy(new Vector2(1, 0)));
AddStep("Release ctrl", () => InputManager.ReleaseKey(Key.LControl));
AddAssert("Box not at 0", () => !Precision.AlmostEquals(boxQuad.TopLeft, scrollQuad.TopLeft)); AddAssert("Box not at 0", () => !Precision.AlmostEquals(boxQuad.TopLeft, scrollQuad.TopLeft));
// Very hard to determine actual position, so approximate // Very hard to determine actual position, so approximate
@ -115,15 +106,11 @@ namespace osu.Game.Tests.Visual
AddAssert("Box at correct position (3)", () => Precision.DefinitelyBigger(boxQuad.TopLeft.X + 0.6f * boxQuad.Size.X, scrollQuad.TopLeft.X + 0.6f * scrollQuad.Size.X)); AddAssert("Box at correct position (3)", () => Precision.DefinitelyBigger(boxQuad.TopLeft.X + 0.6f * boxQuad.Size.X, scrollQuad.TopLeft.X + 0.6f * scrollQuad.Size.X));
// Scroll out at 0.6 // Scroll out at 0.6
AddStep("Press ctrl", () => InputManager.PressKey(Key.LControl)); AddStep("Scroll by -1", () => InputManager.ScrollBy(new Vector2(0, -1)));
AddStep("Scroll by -1", () => InputManager.ScrollBy(new Vector2(-1, 0)));
AddStep("Release ctrl", () => InputManager.ReleaseKey(Key.LControl));
// Scroll out at 0.25 // Scroll out at 0.25
AddStep("Move mouse to 0.25x", () => InputManager.MoveMouseTo(new Vector2(scrollQuad.TopLeft.X + 0.25f * scrollQuad.Size.X, scrollQuad.Centre.Y))); AddStep("Move mouse to 0.25x", () => InputManager.MoveMouseTo(new Vector2(scrollQuad.TopLeft.X + 0.25f * scrollQuad.Size.X, scrollQuad.Centre.Y)));
AddStep("Press ctrl", () => InputManager.PressKey(Key.LControl)); AddStep("Scroll by -1", () => InputManager.ScrollBy(new Vector2(0, -1)));
AddStep("Scroll by -1", () => InputManager.ScrollBy(new Vector2(-1, 0)));
AddStep("Release ctrl", () => InputManager.ReleaseKey(Key.LControl));
AddAssert("Box at 0", () => Precision.AlmostEquals(boxQuad.TopLeft, scrollQuad.TopLeft)); AddAssert("Box at 0", () => Precision.AlmostEquals(boxQuad.TopLeft, scrollQuad.TopLeft));
} }

View 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);
}
}
}

View 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
{
}
}

View 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();
}
}

View 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");
}
}
}

View File

@ -366,8 +366,7 @@ namespace osu.Game.Beatmaps
if (ruleset != null) if (ruleset != null)
{ {
// TODO: this should be done in a better place once we actually need to dynamically update it. // TODO: this should be done in a better place once we actually need to dynamically update it.
var converted = new DummyConversionBeatmap(beatmap).GetPlayableBeatmap(ruleset); beatmap.BeatmapInfo.StarDifficulty = ruleset.CreateInstance().CreateDifficultyCalculator(new DummyConversionBeatmap(beatmap)).Calculate().StarRating;
beatmap.BeatmapInfo.StarDifficulty = ruleset.CreateInstance().CreateDifficultyCalculator(converted).Calculate();
} }
else else
beatmap.BeatmapInfo.StarDifficulty = 0; beatmap.BeatmapInfo.StarDifficulty = 0;

View File

@ -62,7 +62,7 @@ namespace osu.Game.Beatmaps
public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new DummyBeatmapConverter { Beatmap = beatmap }; public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new DummyBeatmapConverter { Beatmap = beatmap };
public override DifficultyCalculator CreateDifficultyCalculator(IBeatmap beatmap, Mod[] mods = null) => null; public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => null;
public override string Description => "dummy"; public override string Description => "dummy";

View File

@ -13,7 +13,6 @@ using osu.Game.Storyboards;
using osu.Framework.IO.File; using osu.Framework.IO.File;
using System.IO; using System.IO;
using osu.Game.IO.Serialization; using osu.Game.IO.Serialization;
using System.Diagnostics;
using osu.Game.Rulesets; using osu.Game.Rulesets;
using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI;
using osu.Game.Skinning; using osu.Game.Skinning;
@ -49,12 +48,13 @@ namespace osu.Game.Beatmaps
/// <summary> /// <summary>
/// Saves the <see cref="Beatmaps.Beatmap"/>. /// Saves the <see cref="Beatmaps.Beatmap"/>.
/// </summary> /// </summary>
public void Save() /// <returns>The absolute path of the output file.</returns>
public string Save()
{ {
var path = FileSafety.GetTempPath(Guid.NewGuid().ToString().Replace("-", string.Empty) + ".json"); var path = FileSafety.GetTempPath(Guid.NewGuid().ToString().Replace("-", string.Empty) + ".json");
using (var sw = new StreamWriter(path)) using (var sw = new StreamWriter(path))
sw.WriteLine(Beatmap.Serialize()); sw.WriteLine(Beatmap.Serialize());
Process.Start(path); return path;
} }
protected abstract IBeatmap GetBeatmap(); protected abstract IBeatmap GetBeatmap();

View File

@ -181,24 +181,6 @@ namespace osu.Game.Database
} }
} }
public void Migrate() public void Migrate() => Database.Migrate();
{
try
{
Database.Migrate();
}
catch (Exception e)
{
throw new MigrationFailedException(e);
}
}
}
public class MigrationFailedException : Exception
{
public MigrationFailedException(Exception exception)
: base("sqlite-net migration failed", exception)
{
}
} }
} }

View File

@ -3,11 +3,11 @@
using osu.Game.Online.Chat; using osu.Game.Online.Chat;
using System; using System;
using System.Diagnostics;
using System.Linq; using System.Linq;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Sprites;
using System.Collections.Generic; using System.Collections.Generic;
using osu.Framework.Platform;
using osu.Game.Overlays; using osu.Game.Overlays;
using osu.Game.Overlays.Notifications; using osu.Game.Overlays.Notifications;
@ -25,12 +25,14 @@ namespace osu.Game.Graphics.Containers
private OsuGame game; private OsuGame game;
private Action showNotImplementedError; private Action showNotImplementedError;
private GameHost host;
[BackgroundDependencyLoader(true)] [BackgroundDependencyLoader(true)]
private void load(OsuGame game, NotificationOverlay notifications) private void load(OsuGame game, NotificationOverlay notifications, GameHost host)
{ {
// will be null in tests // will be null in tests
this.game = game; this.game = game;
this.host = host;
showNotImplementedError = () => notifications?.Post(new SimpleNotification showNotImplementedError = () => notifications?.Post(new SimpleNotification
{ {
@ -88,7 +90,7 @@ namespace osu.Game.Graphics.Containers
showNotImplementedError?.Invoke(); showNotImplementedError?.Invoke();
break; break;
case LinkAction.External: case LinkAction.External:
Process.Start(url); host.OpenUrlExternally(url);
break; break;
case LinkAction.OpenUserProfile: case LinkAction.OpenUserProfile:
if (long.TryParse(linkArgument, out long userId)) if (long.TryParse(linkArgument, out long userId))

View File

@ -8,20 +8,32 @@ using osu.Framework.Graphics.Containers;
using osu.Framework.Input; using osu.Framework.Input;
using OpenTK; using OpenTK;
using osu.Framework.Configuration; using osu.Framework.Configuration;
using osu.Game.Audio;
using osu.Game.Overlays; using osu.Game.Overlays;
namespace osu.Game.Graphics.Containers namespace osu.Game.Graphics.Containers
{ {
public class OsuFocusedOverlayContainer : FocusedOverlayContainer public class OsuFocusedOverlayContainer : FocusedOverlayContainer, IPreviewTrackOwner
{ {
private SampleChannel samplePopIn; private SampleChannel samplePopIn;
private SampleChannel samplePopOut; private SampleChannel samplePopOut;
private PreviewTrackManager previewTrackManager;
protected readonly Bindable<OverlayActivation> OverlayActivationMode = new Bindable<OverlayActivation>(OverlayActivation.All); protected readonly Bindable<OverlayActivation> OverlayActivationMode = new Bindable<OverlayActivation>(OverlayActivation.All);
[BackgroundDependencyLoader(true)] protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent)
private void load(OsuGame osuGame, AudioManager audio)
{ {
var dependencies = new DependencyContainer(base.CreateLocalDependencies(parent));
dependencies.CacheAs<IPreviewTrackOwner>(this);
return dependencies;
}
[BackgroundDependencyLoader(true)]
private void load(OsuGame osuGame, AudioManager audio, PreviewTrackManager previewTrackManager)
{
this.previewTrackManager = previewTrackManager;
if (osuGame != null) if (osuGame != null)
OverlayActivationMode.BindTo(osuGame.OverlayActivationMode); OverlayActivationMode.BindTo(osuGame.OverlayActivationMode);
@ -33,7 +45,7 @@ namespace osu.Game.Graphics.Containers
/// <summary> /// <summary>
/// Whether mouse input should be blocked screen-wide while this overlay is visible. /// Whether mouse input should be blocked screen-wide while this overlay is visible.
/// Performing mouse actions outside of the valid extents will hide the overlay but pass the events through. /// Performing mouse actions outside of the valid extents will hide the overlay.
/// </summary> /// </summary>
public virtual bool BlockScreenWideMouse => BlockPassThroughMouse; public virtual bool BlockScreenWideMouse => BlockPassThroughMouse;
@ -66,5 +78,11 @@ namespace osu.Game.Graphics.Containers
break; break;
} }
} }
protected override void PopOut()
{
base.PopOut();
previewTrackManager.StopAnyPlaying(this);
}
} }
} }

View File

@ -16,6 +16,9 @@ namespace osu.Game.Graphics.Containers
{ {
public const float DEFAULT_PARALLAX_AMOUNT = 0.02f; public const float DEFAULT_PARALLAX_AMOUNT = 0.02f;
/// <summary>
/// The amount of parallax movement. Negative values will reverse the direction of parallax relative to user input.
/// </summary>
public float ParallaxAmount = DEFAULT_PARALLAX_AMOUNT; public float ParallaxAmount = DEFAULT_PARALLAX_AMOUNT;
private Bindable<bool> parallaxEnabled; private Bindable<bool> parallaxEnabled;
@ -45,7 +48,7 @@ namespace osu.Game.Graphics.Containers
if (!parallaxEnabled) if (!parallaxEnabled)
{ {
content.MoveTo(Vector2.Zero, firstUpdate ? 0 : 1000, Easing.OutQuint); content.MoveTo(Vector2.Zero, firstUpdate ? 0 : 1000, Easing.OutQuint);
content.Scale = new Vector2(1 + ParallaxAmount); content.Scale = new Vector2(1 + System.Math.Abs(ParallaxAmount));
} }
}; };
} }
@ -69,7 +72,7 @@ namespace osu.Game.Graphics.Containers
double elapsed = MathHelper.Clamp(Clock.ElapsedFrameTime, 0, 1000); double elapsed = MathHelper.Clamp(Clock.ElapsedFrameTime, 0, 1000);
content.Position = Interpolation.ValueAt(elapsed, content.Position, offset, 0, 1000, Easing.OutQuint); content.Position = Interpolation.ValueAt(elapsed, content.Position, offset, 0, 1000, Easing.OutQuint);
content.Scale = Interpolation.ValueAt(elapsed, content.Scale, new Vector2(1 + ParallaxAmount), 0, 1000, Easing.OutQuint); content.Scale = Interpolation.ValueAt(elapsed, content.Scale, new Vector2(1 + System.Math.Abs(ParallaxAmount)), 0, 1000, Easing.OutQuint);
} }
firstUpdate = false; firstUpdate = false;

View File

@ -1,12 +1,12 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Diagnostics;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Cursor;
using osu.Framework.Input; using osu.Framework.Input;
using osu.Framework.Platform;
using OpenTK; using OpenTK;
using OpenTK.Graphics; using OpenTK.Graphics;
@ -17,6 +17,7 @@ namespace osu.Game.Graphics.UserInterface
public string Link { get; set; } public string Link { get; set; }
private Color4 hoverColour; private Color4 hoverColour;
private GameHost host;
public ExternalLinkButton(string link = null) public ExternalLinkButton(string link = null)
{ {
@ -30,9 +31,10 @@ namespace osu.Game.Graphics.UserInterface
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(OsuColour colours) private void load(OsuColour colours, GameHost host)
{ {
hoverColour = colours.Yellow; hoverColour = colours.Yellow;
this.host = host;
} }
protected override bool OnHover(InputState state) protected override bool OnHover(InputState state)
@ -50,11 +52,7 @@ namespace osu.Game.Graphics.UserInterface
protected override bool OnClick(InputState state) protected override bool OnClick(InputState state)
{ {
if(Link != null) if(Link != null)
Process.Start(new ProcessStartInfo host.OpenUrlExternally(Link);
{
FileName = Link,
UseShellExecute = true //see https://github.com/dotnet/corefx/issues/10361
});
return true; return true;
} }

View File

@ -0,0 +1,376 @@
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using osu.Game.Database;
namespace osu.Game.Migrations
{
[DbContext(typeof(OsuDbContext))]
[Migration("20180621044111_UpdateTaikoDefaultBindings")]
partial class UpdateTaikoDefaultBindings
{
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "2.1.1-rtm-30846");
modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b =>
{
b.Property<int>("ID")
.ValueGeneratedOnAdd();
b.Property<float>("ApproachRate");
b.Property<float>("CircleSize");
b.Property<float>("DrainRate");
b.Property<float>("OverallDifficulty");
b.Property<double>("SliderMultiplier");
b.Property<double>("SliderTickRate");
b.HasKey("ID");
b.ToTable("BeatmapDifficulty");
});
modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b =>
{
b.Property<int>("ID")
.ValueGeneratedOnAdd();
b.Property<int>("AudioLeadIn");
b.Property<int>("BaseDifficultyID");
b.Property<int>("BeatDivisor");
b.Property<int>("BeatmapSetInfoID");
b.Property<bool>("Countdown");
b.Property<double>("DistanceSpacing");
b.Property<int>("GridSize");
b.Property<string>("Hash");
b.Property<bool>("Hidden");
b.Property<bool>("LetterboxInBreaks");
b.Property<string>("MD5Hash");
b.Property<int?>("MetadataID");
b.Property<int?>("OnlineBeatmapID");
b.Property<string>("Path");
b.Property<int>("RulesetID");
b.Property<bool>("SpecialStyle");
b.Property<float>("StackLeniency");
b.Property<double>("StarDifficulty");
b.Property<string>("StoredBookmarks");
b.Property<double>("TimelineZoom");
b.Property<string>("Version");
b.Property<bool>("WidescreenStoryboard");
b.HasKey("ID");
b.HasIndex("BaseDifficultyID");
b.HasIndex("BeatmapSetInfoID");
b.HasIndex("Hash");
b.HasIndex("MD5Hash");
b.HasIndex("MetadataID");
b.HasIndex("OnlineBeatmapID")
.IsUnique();
b.HasIndex("RulesetID");
b.ToTable("BeatmapInfo");
});
modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b =>
{
b.Property<int>("ID")
.ValueGeneratedOnAdd();
b.Property<string>("Artist");
b.Property<string>("ArtistUnicode");
b.Property<string>("AudioFile");
b.Property<string>("AuthorString")
.HasColumnName("Author");
b.Property<string>("BackgroundFile");
b.Property<int>("PreviewTime");
b.Property<string>("Source");
b.Property<string>("Tags");
b.Property<string>("Title");
b.Property<string>("TitleUnicode");
b.HasKey("ID");
b.ToTable("BeatmapMetadata");
});
modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b =>
{
b.Property<int>("ID")
.ValueGeneratedOnAdd();
b.Property<int>("BeatmapSetInfoID");
b.Property<int>("FileInfoID");
b.Property<string>("Filename")
.IsRequired();
b.HasKey("ID");
b.HasIndex("BeatmapSetInfoID");
b.HasIndex("FileInfoID");
b.ToTable("BeatmapSetFileInfo");
});
modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b =>
{
b.Property<int>("ID")
.ValueGeneratedOnAdd();
b.Property<bool>("DeletePending");
b.Property<string>("Hash");
b.Property<int?>("MetadataID");
b.Property<int?>("OnlineBeatmapSetID");
b.Property<bool>("Protected");
b.HasKey("ID");
b.HasIndex("DeletePending");
b.HasIndex("Hash")
.IsUnique();
b.HasIndex("MetadataID");
b.HasIndex("OnlineBeatmapSetID")
.IsUnique();
b.ToTable("BeatmapSetInfo");
});
modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b =>
{
b.Property<int>("ID")
.ValueGeneratedOnAdd();
b.Property<int>("IntKey")
.HasColumnName("Key");
b.Property<int?>("RulesetID");
b.Property<string>("StringValue")
.HasColumnName("Value");
b.Property<int?>("Variant");
b.HasKey("ID");
b.HasIndex("RulesetID", "Variant");
b.ToTable("Settings");
});
modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b =>
{
b.Property<int>("ID")
.ValueGeneratedOnAdd();
b.Property<int>("IntAction")
.HasColumnName("Action");
b.Property<string>("KeysString")
.HasColumnName("Keys");
b.Property<int?>("RulesetID");
b.Property<int?>("Variant");
b.HasKey("ID");
b.HasIndex("IntAction");
b.HasIndex("RulesetID", "Variant");
b.ToTable("KeyBinding");
});
modelBuilder.Entity("osu.Game.IO.FileInfo", b =>
{
b.Property<int>("ID")
.ValueGeneratedOnAdd();
b.Property<string>("Hash");
b.Property<int>("ReferenceCount");
b.HasKey("ID");
b.HasIndex("Hash")
.IsUnique();
b.HasIndex("ReferenceCount");
b.ToTable("FileInfo");
});
modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b =>
{
b.Property<int?>("ID")
.ValueGeneratedOnAdd();
b.Property<bool>("Available");
b.Property<string>("InstantiationInfo");
b.Property<string>("Name");
b.Property<string>("ShortName");
b.HasKey("ID");
b.HasIndex("Available");
b.HasIndex("ShortName")
.IsUnique();
b.ToTable("RulesetInfo");
});
modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b =>
{
b.Property<int>("ID")
.ValueGeneratedOnAdd();
b.Property<int>("FileInfoID");
b.Property<string>("Filename")
.IsRequired();
b.Property<int>("SkinInfoID");
b.HasKey("ID");
b.HasIndex("FileInfoID");
b.HasIndex("SkinInfoID");
b.ToTable("SkinFileInfo");
});
modelBuilder.Entity("osu.Game.Skinning.SkinInfo", b =>
{
b.Property<int>("ID")
.ValueGeneratedOnAdd();
b.Property<string>("Creator");
b.Property<bool>("DeletePending");
b.Property<string>("Name");
b.HasKey("ID");
b.ToTable("SkinInfo");
});
modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b =>
{
b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty")
.WithMany()
.HasForeignKey("BaseDifficultyID")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet")
.WithMany("Beatmaps")
.HasForeignKey("BeatmapSetInfoID")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata")
.WithMany("Beatmaps")
.HasForeignKey("MetadataID");
b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset")
.WithMany()
.HasForeignKey("RulesetID")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b =>
{
b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo")
.WithMany("Files")
.HasForeignKey("BeatmapSetInfoID")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("osu.Game.IO.FileInfo", "FileInfo")
.WithMany()
.HasForeignKey("FileInfoID")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b =>
{
b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata")
.WithMany("BeatmapSets")
.HasForeignKey("MetadataID");
});
modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b =>
{
b.HasOne("osu.Game.IO.FileInfo", "FileInfo")
.WithMany()
.HasForeignKey("FileInfoID")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("osu.Game.Skinning.SkinInfo")
.WithMany("Files")
.HasForeignKey("SkinInfoID")
.OnDelete(DeleteBehavior.Cascade);
});
#pragma warning restore 612, 618
}
}
}

View File

@ -0,0 +1,19 @@
using Microsoft.EntityFrameworkCore.Migrations;
using osu.Framework.Logging;
namespace osu.Game.Migrations
{
public partial class UpdateTaikoDefaultBindings : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.Sql("DELETE FROM KeyBinding WHERE RulesetID = 1");
Logger.Log("osu!taiko bindings have been reset due to new defaults", LoggingTarget.Runtime, LogLevel.Important);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
// we can't really tell if these should be restored or not, so let's just not do so.
}
}
}

View File

@ -1,11 +1,9 @@
// <auto-generated /> // <auto-generated />
using System;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage;
using osu.Game.Database; using osu.Game.Database;
using System;
namespace osu.Game.Migrations namespace osu.Game.Migrations
{ {
@ -16,7 +14,7 @@ namespace osu.Game.Migrations
{ {
#pragma warning disable 612, 618 #pragma warning disable 612, 618
modelBuilder modelBuilder
.HasAnnotation("ProductVersion", "2.0.3-rtm-10026"); .HasAnnotation("ProductVersion", "2.1.1-rtm-30846");
modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b =>
{ {

View File

@ -101,6 +101,8 @@ namespace osu.Game
public OsuGame(string[] args = null) public OsuGame(string[] args = null)
{ {
this.args = args; this.args = args;
forwardLoggedErrorsToNotifications();
} }
public void ToggleSettings() => settings.ToggleVisibility(); public void ToggleSettings() => settings.ToggleVisibility();
@ -305,8 +307,6 @@ namespace osu.Game
Depth = -6, Depth = -6,
}, overlayContent.Add); }, overlayContent.Add);
forwardLoggedErrorsToNotifications();
dependencies.Cache(settings); dependencies.Cache(settings);
dependencies.Cache(onscreenDisplay); dependencies.Cache(onscreenDisplay);
dependencies.Cache(social); dependencies.Cache(social);
@ -394,31 +394,40 @@ namespace osu.Game
private void forwardLoggedErrorsToNotifications() private void forwardLoggedErrorsToNotifications()
{ {
int recentErrorCount = 0; int recentLogCount = 0;
const double debounce = 5000; const double debounce = 5000;
Logger.NewEntry += entry => Logger.NewEntry += entry =>
{ {
if (entry.Level < LogLevel.Error || entry.Target == null) return; if (entry.Level < LogLevel.Important || entry.Target == null) return;
if (recentErrorCount < 2) const int short_term_display_limit = 3;
if (recentLogCount < short_term_display_limit)
{ {
notifications.Post(new SimpleNotification Schedule(() => notifications.Post(new SimpleNotification
{ {
Icon = FontAwesome.fa_bomb, Icon = entry.Level == LogLevel.Important ? FontAwesome.fa_exclamation_circle : FontAwesome.fa_bomb,
Text = (recentErrorCount == 0 ? entry.Message : "Subsequent errors occurred and have been logged.") + "\nClick to view log files.", Text = entry.Message,
}));
}
else if (recentLogCount == short_term_display_limit)
{
Schedule(() => notifications.Post(new SimpleNotification
{
Icon = FontAwesome.fa_ellipsis_h,
Text = "Subsequent messages have been logged. Click to view log files.",
Activated = () => Activated = () =>
{ {
Host.Storage.GetStorageForDirectory("logs").OpenInNativeExplorer(); Host.Storage.GetStorageForDirectory("logs").OpenInNativeExplorer();
return true; return true;
} }
}); }));
} }
Interlocked.Increment(ref recentErrorCount); Interlocked.Increment(ref recentLogCount);
Scheduler.AddDelayed(() => Interlocked.Decrement(ref recentLogCount), debounce);
Scheduler.AddDelayed(() => Interlocked.Decrement(ref recentErrorCount), debounce);
}; };
} }

View File

@ -21,6 +21,7 @@ using osu.Game.Online.API;
using osu.Framework.Graphics.Performance; using osu.Framework.Graphics.Performance;
using osu.Framework.Graphics.Textures; using osu.Framework.Graphics.Textures;
using osu.Framework.Logging; using osu.Framework.Logging;
using osu.Game.Audio;
using osu.Game.Database; using osu.Game.Database;
using osu.Game.Graphics.Textures; using osu.Game.Graphics.Textures;
using osu.Game.Input; using osu.Game.Input;
@ -187,6 +188,10 @@ namespace osu.Game
KeyBindingStore.Register(globalBinding); KeyBindingStore.Register(globalBinding);
dependencies.Cache(globalBinding); dependencies.Cache(globalBinding);
PreviewTrackManager previewTrackManager;
dependencies.Cache(previewTrackManager = new PreviewTrackManager());
Add(previewTrackManager);
} }
protected override void LoadComplete() protected override void LoadComplete()

View File

@ -2,13 +2,13 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Audio.Track;
using osu.Framework.Configuration; using osu.Framework.Configuration;
using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Framework.Input; using osu.Framework.Input;
using osu.Game.Audio;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Graphics.Containers; using osu.Game.Graphics.Containers;
@ -25,7 +25,7 @@ namespace osu.Game.Overlays.BeatmapSet.Buttons
private readonly Box bg, progress; private readonly Box bg, progress;
private readonly PlayButton playButton; private readonly PlayButton playButton;
private Track preview => playButton.Preview; private PreviewTrack preview => playButton.Preview;
public Bindable<bool> Playing => playButton.Playing; public Bindable<bool> Playing => playButton.Playing;
public BeatmapSetInfo BeatmapSet public BeatmapSetInfo BeatmapSet
@ -66,7 +66,7 @@ namespace osu.Game.Overlays.BeatmapSet.Buttons
}, },
}; };
Action = () => Playing.Value = !Playing.Value; Action = () => playButton.TriggerOnClick();
Playing.ValueChanged += newValue => progress.FadeTo(newValue ? 1 : 0, 100); Playing.ValueChanged += newValue => progress.FadeTo(newValue ? 1 : 0, 100);
} }
@ -89,12 +89,6 @@ namespace osu.Game.Overlays.BeatmapSet.Buttons
progress.Width = 0; progress.Width = 0;
} }
protected override void Dispose(bool isDisposing)
{
Playing.Value = false;
base.Dispose(isDisposing);
}
protected override bool OnHover(InputState state) protected override bool OnHover(InputState state)
{ {
bg.FadeColour(Color4.Black.Opacity(0.5f), 100); bg.FadeColour(Color4.Black.Opacity(0.5f), 100);

View File

@ -102,8 +102,6 @@ namespace osu.Game.Overlays.BeatmapSet
updateDisplay(); updateDisplay();
} }
public void StopPreview() => preview.Playing.Value = false;
private class DetailBox : Container private class DetailBox : Container
{ {
private readonly Container content; private readonly Container content;

View File

@ -1,9 +1,8 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Linq;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using OpenTK;
using OpenTK.Graphics;
using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
@ -15,9 +14,10 @@ using osu.Game.Graphics.Containers;
using osu.Game.Online.API; using osu.Game.Online.API;
using osu.Game.Online.API.Requests; using osu.Game.Online.API.Requests;
using osu.Game.Overlays.BeatmapSet; using osu.Game.Overlays.BeatmapSet;
using osu.Game.Rulesets;
using osu.Game.Overlays.BeatmapSet.Scores; using osu.Game.Overlays.BeatmapSet.Scores;
using System.Linq; using osu.Game.Rulesets;
using OpenTK;
using OpenTK.Graphics;
namespace osu.Game.Overlays namespace osu.Game.Overlays
{ {
@ -124,8 +124,6 @@ namespace osu.Game.Overlays
protected override void PopOut() protected override void PopOut()
{ {
base.PopOut(); base.PopOut();
header.Details.StopPreview();
FadeEdgeEffectTo(0, WaveContainer.DISAPPEAR_DURATION, Easing.Out).OnComplete(_ => BeatmapSet = null); FadeEdgeEffectTo(0, WaveContainer.DISAPPEAR_DURATION, Easing.Out).OnComplete(_ => BeatmapSet = null);
} }

View File

@ -4,22 +4,22 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Configuration;
using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Extensions.Color4Extensions;
using OpenTK;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Sprites;
using osu.Framework.Input;
using osu.Game.Audio;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Drawables; using osu.Game.Beatmaps.Drawables;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Sprites;
using OpenTK.Graphics;
using osu.Framework.Input;
using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterface;
using osu.Game.Online.API.Requests; using osu.Game.Online.API.Requests;
using osu.Framework.Configuration; using OpenTK;
using osu.Framework.Audio.Track; using OpenTK.Graphics;
namespace osu.Game.Overlays.Direct namespace osu.Game.Overlays.Direct
{ {
@ -35,7 +35,7 @@ namespace osu.Game.Overlays.Direct
private BeatmapManager beatmaps; private BeatmapManager beatmaps;
private BeatmapSetOverlay beatmapSetOverlay; private BeatmapSetOverlay beatmapSetOverlay;
public Track Preview => PlayButton.Preview; public PreviewTrack Preview => PlayButton.Preview;
public Bindable<bool> PreviewPlaying => PlayButton.Playing; public Bindable<bool> PreviewPlaying => PlayButton.Playing;
protected abstract PlayButton PlayButton { get; } protected abstract PlayButton PlayButton { get; }
protected abstract Box PreviewBar { get; } protected abstract Box PreviewBar { get; }
@ -113,7 +113,7 @@ namespace osu.Game.Overlays.Direct
{ {
base.Update(); base.Update();
if (PreviewPlaying && Preview != null && Preview.IsLoaded) if (PreviewPlaying && Preview != null && Preview.TrackLoaded)
{ {
PreviewBar.Width = (float)(Preview.CurrentTime / Preview.Length); PreviewBar.Width = (float)(Preview.CurrentTime / Preview.Length);
} }
@ -141,7 +141,6 @@ namespace osu.Game.Overlays.Direct
protected override bool OnClick(InputState state) protected override bool OnClick(InputState state)
{ {
ShowInformation(); ShowInformation();
PreviewPlaying.Value = false;
return true; return true;
} }

View File

@ -1,25 +1,23 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using OpenTK.Graphics;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Audio.Track;
using osu.Framework.Configuration; using osu.Framework.Configuration;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Input; using osu.Framework.Input;
using osu.Framework.IO.Stores; using osu.Game.Audio;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterface;
using OpenTK.Graphics;
namespace osu.Game.Overlays.Direct namespace osu.Game.Overlays.Direct
{ {
public class PlayButton : Container public class PlayButton : Container
{ {
public readonly Bindable<bool> Playing = new Bindable<bool>(); public readonly BindableBool Playing = new BindableBool();
public Track Preview { get; private set; } public PreviewTrack Preview { get; private set; }
private BeatmapSetInfo beatmapSet; private BeatmapSetInfo beatmapSet;
@ -31,9 +29,11 @@ namespace osu.Game.Overlays.Direct
if (value == beatmapSet) return; if (value == beatmapSet) return;
beatmapSet = value; beatmapSet = value;
Playing.Value = false; Preview?.Stop();
trackLoader = null; Preview?.Expire();
Preview = null; Preview = null;
Playing.Value = false;
} }
} }
@ -41,8 +41,6 @@ namespace osu.Game.Overlays.Direct
private readonly SpriteIcon icon; private readonly SpriteIcon icon;
private readonly LoadingAnimation loadingAnimation; private readonly LoadingAnimation loadingAnimation;
private readonly BindableDouble muteBindable = new BindableDouble();
private const float transition_duration = 500; private const float transition_duration = 500;
private bool loading private bool loading
@ -50,15 +48,9 @@ namespace osu.Game.Overlays.Direct
set set
{ {
if (value) if (value)
{
loadingAnimation.Show(); loadingAnimation.Show();
icon.FadeOut(transition_duration * 5, Easing.OutQuint);
}
else else
{
loadingAnimation.Hide(); loadingAnimation.Hide();
icon.FadeIn(transition_duration, Easing.OutQuint);
}
} }
} }
@ -78,19 +70,22 @@ namespace osu.Game.Overlays.Direct
loadingAnimation = new LoadingAnimation(), loadingAnimation = new LoadingAnimation(),
}); });
Playing.ValueChanged += updatePreviewTrack; Playing.ValueChanged += playingStateChanged;
} }
private PreviewTrackManager previewTrackManager;
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(OsuColour colour, AudioManager audio) private void load(OsuColour colour, PreviewTrackManager previewTrackManager)
{ {
this.previewTrackManager = previewTrackManager;
hoverColour = colour.Yellow; hoverColour = colour.Yellow;
this.audio = audio;
} }
protected override bool OnClick(InputState state) protected override bool OnClick(InputState state)
{ {
Playing.Value = !Playing.Value; Playing.Toggle();
return true; return true;
} }
@ -107,44 +102,44 @@ namespace osu.Game.Overlays.Direct
base.OnHoverLost(state); base.OnHoverLost(state);
} }
protected override void Update() private void playingStateChanged(bool playing)
{ {
base.Update();
if (Preview?.HasCompleted ?? false)
{
Playing.Value = false;
Preview = null;
}
}
private void updatePreviewTrack(bool playing)
{
if (playing && BeatmapSet == null)
{
Playing.Value = false;
return;
}
icon.Icon = playing ? FontAwesome.fa_stop : FontAwesome.fa_play; icon.Icon = playing ? FontAwesome.fa_stop : FontAwesome.fa_play;
icon.FadeColour(playing || IsHovered ? hoverColour : Color4.White, 120, Easing.InOutQuint); icon.FadeColour(playing || IsHovered ? hoverColour : Color4.White, 120, Easing.InOutQuint);
if (playing) if (playing)
{ {
if (Preview == null) if (BeatmapSet == null)
{ {
beginAudioLoad(); Playing.Value = false;
return; return;
} }
Preview.Restart(); if (Preview != null)
{
Preview.Start();
return;
}
audio.Track.AddAdjustment(AdjustableProperty.Volume, muteBindable); loading = true;
LoadComponentAsync(Preview = previewTrackManager.Get(beatmapSet), preview =>
{
// beatmapset may have changed.
if (Preview != preview)
return;
AddInternal(preview);
loading = false;
preview.Stopped += () => Playing.Value = false;
// user may have changed their mind.
if (Playing)
preview.Start();
});
} }
else else
{ {
audio.Track.RemoveAdjustment(AdjustableProperty.Volume, muteBindable);
Preview?.Stop(); Preview?.Stop();
loading = false; loading = false;
} }
@ -155,64 +150,5 @@ namespace osu.Game.Overlays.Direct
base.Dispose(isDisposing); base.Dispose(isDisposing);
Playing.Value = false; Playing.Value = false;
} }
private TrackLoader trackLoader;
private AudioManager audio;
private void beginAudioLoad()
{
if (trackLoader != null)
{
Preview = trackLoader.Preview;
Playing.TriggerChange();
return;
}
loading = true;
LoadComponentAsync(trackLoader = new TrackLoader($"https://b.ppy.sh/preview/{BeatmapSet.OnlineBeatmapSetID}.mp3"),
d =>
{
// We may have been replaced by another loader
if (trackLoader != d) return;
Preview = d?.Preview;
updatePreviewTrack(Playing);
loading = false;
Add(trackLoader);
});
}
private class TrackLoader : Drawable
{
private readonly string preview;
public Track Preview;
private TrackManager trackManager;
public TrackLoader(string preview)
{
this.preview = preview;
}
[BackgroundDependencyLoader]
private void load(AudioManager audio, FrameworkConfigManager config)
{
// create a local trackManager to bypass the mute we are applying above.
audio.AddItem(trackManager = new TrackManager(new OnlineStore()));
// add back the user's music volume setting (since we are no longer in the global TrackManager's hierarchy).
config.BindWith(FrameworkSetting.VolumeMusic, trackManager.Volume);
Preview = trackManager.Get(preview);
}
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
trackManager?.Dispose();
}
}
} }
} }

View File

@ -4,12 +4,12 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using OpenTK;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Configuration; using osu.Framework.Configuration;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Threading; using osu.Framework.Threading;
using osu.Game.Audio;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Sprites;
@ -18,6 +18,7 @@ using osu.Game.Online.API.Requests;
using osu.Game.Overlays.Direct; using osu.Game.Overlays.Direct;
using osu.Game.Overlays.SearchableList; using osu.Game.Overlays.SearchableList;
using osu.Game.Rulesets; using osu.Game.Rulesets;
using OpenTK;
using OpenTK.Graphics; using OpenTK.Graphics;
namespace osu.Game.Overlays namespace osu.Game.Overlays
@ -32,7 +33,6 @@ namespace osu.Game.Overlays
private readonly FillFlowContainer resultCountsContainer; private readonly FillFlowContainer resultCountsContainer;
private readonly OsuSpriteText resultCountsText; private readonly OsuSpriteText resultCountsText;
private FillFlowContainer<DirectPanel> panels; private FillFlowContainer<DirectPanel> panels;
private DirectPanel playing;
protected override Color4 BackgroundColour => OsuColour.FromHex(@"485e74"); protected override Color4 BackgroundColour => OsuColour.FromHex(@"485e74");
protected override Color4 TrianglesColourLight => OsuColour.FromHex(@"465b71"); protected override Color4 TrianglesColourLight => OsuColour.FromHex(@"465b71");
@ -176,10 +176,11 @@ namespace osu.Game.Overlays
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(OsuColour colours, APIAccess api, RulesetStore rulesets) private void load(OsuColour colours, APIAccess api, RulesetStore rulesets, PreviewTrackManager previewTrackManager)
{ {
this.api = api; this.api = api;
this.rulesets = rulesets; this.rulesets = rulesets;
this.previewTrackManager = previewTrackManager;
resultCountsContainer.Colour = colours.Yellow; resultCountsContainer.Colour = colours.Yellow;
} }
@ -206,12 +207,6 @@ namespace osu.Game.Overlays
panels.FadeOut(200); panels.FadeOut(200);
panels.Expire(); panels.Expire();
panels = null; panels = null;
if (playing != null)
{
playing.PreviewPlaying.Value = false;
playing = null;
}
} }
if (BeatmapSets == null) return; if (BeatmapSets == null) return;
@ -242,17 +237,6 @@ namespace osu.Game.Overlays
{ {
if (panels != null) ScrollFlow.Remove(panels); if (panels != null) ScrollFlow.Remove(panels);
ScrollFlow.Add(panels = newPanels); ScrollFlow.Add(panels = newPanels);
foreach (DirectPanel panel in p.Children)
panel.PreviewPlaying.ValueChanged += newValue =>
{
if (newValue)
{
if (playing != null && playing != panel)
playing.PreviewPlaying.Value = false;
playing = panel;
}
};
}); });
} }
@ -261,6 +245,7 @@ namespace osu.Game.Overlays
private readonly Bindable<string> currentQuery = new Bindable<string>(); private readonly Bindable<string> currentQuery = new Bindable<string>();
private ScheduledDelegate queryChangedDebounce; private ScheduledDelegate queryChangedDebounce;
private PreviewTrackManager previewTrackManager;
private void updateSearch() private void updateSearch()
{ {
@ -277,6 +262,8 @@ namespace osu.Game.Overlays
if (Header.Tabs.Current.Value == DirectTab.Search && (Filter.Search.Text == string.Empty || currentQuery == string.Empty)) return; if (Header.Tabs.Current.Value == DirectTab.Search && (Filter.Search.Text == string.Empty || currentQuery == string.Empty)) return;
previewTrackManager.StopAnyPlaying(this);
getSetsRequest = new SearchBeatmapSetsRequest(currentQuery.Value ?? string.Empty, getSetsRequest = new SearchBeatmapSetsRequest(currentQuery.Value ?? string.Empty,
((FilterControl)Filter).Ruleset.Value, ((FilterControl)Filter).Ruleset.Value,
Filter.DisplayStyleControl.Dropdown.Current.Value, Filter.DisplayStyleControl.Dropdown.Current.Value,
@ -300,14 +287,6 @@ namespace osu.Game.Overlays
api.Queue(getSetsRequest); api.Queue(getSetsRequest);
} }
protected override void PopOut()
{
base.PopOut();
if (playing != null)
playing.PreviewPlaying.Value = false;
}
private int distinctCount(List<string> list) => list.Distinct().ToArray().Length; private int distinctCount(List<string> list) => list.Distinct().ToArray().Length;
public class ResultCounts public class ResultCounts

View File

@ -66,34 +66,6 @@ namespace osu.Game.Overlays
AlwaysPresent = true; AlwaysPresent = true;
} }
private Vector2 dragStart;
protected override bool OnDragStart(InputState state)
{
base.OnDragStart(state);
dragStart = state.Mouse.Position;
return true;
}
protected override bool OnDrag(InputState state)
{
if (base.OnDrag(state)) return true;
Vector2 change = state.Mouse.Position - dragStart;
// Diminish the drag distance as we go further to simulate "rubber band" feeling.
change *= change.Length <= 0 ? 0 : (float)Math.Pow(change.Length, 0.7f) / change.Length;
dragContainer.MoveTo(change);
return true;
}
protected override bool OnDragEnd(InputState state)
{
dragContainer.MoveTo(Vector2.Zero, 800, Easing.OutElastic);
return base.OnDragEnd(state);
}
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(BindableBeatmap beatmap, BeatmapManager beatmaps, OsuColour colours, LocalisationEngine localisation) private void load(BindableBeatmap beatmap, BeatmapManager beatmaps, OsuColour colours, LocalisationEngine localisation)
{ {
@ -103,7 +75,7 @@ namespace osu.Game.Overlays
Children = new Drawable[] Children = new Drawable[]
{ {
dragContainer = new Container dragContainer = new DragContainer
{ {
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
Origin = Anchor.Centre, Origin = Anchor.Centre,
@ -470,5 +442,36 @@ namespace osu.Game.Overlays
sprite.Texture = beatmap?.Background ?? textures.Get(@"Backgrounds/bg4"); sprite.Texture = beatmap?.Background ?? textures.Get(@"Backgrounds/bg4");
} }
} }
private class DragContainer : Container
{
private Vector2 dragStart;
protected override bool OnDragStart(InputState state)
{
base.OnDragStart(state);
dragStart = state.Mouse.Position;
return true;
}
protected override bool OnDrag(InputState state)
{
if (base.OnDrag(state)) return true;
Vector2 change = state.Mouse.Position - dragStart;
// Diminish the drag distance as we go further to simulate "rubber band" feeling.
change *= change.Length <= 0 ? 0 : (float)Math.Pow(change.Length, 0.7f) / change.Length;
this.MoveTo(change);
return true;
}
protected override bool OnDragEnd(InputState state)
{
this.MoveTo(Vector2.Zero, 800, Easing.OutElastic);
return base.OnDragEnd(state);
}
}
} }
} }

View File

@ -59,7 +59,7 @@ namespace osu.Game.Overlays.Notifications
} }
}); });
Content.Add(textDrawable = new OsuTextFlowContainer(t => t.TextSize = 16) Content.Add(textDrawable = new OsuTextFlowContainer(t => t.TextSize = 14)
{ {
Colour = OsuColour.Gray(128), Colour = OsuColour.Gray(128),
AutoSizeAxes = Axes.Y, AutoSizeAxes = Axes.Y,

View File

@ -1,14 +1,13 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System; using System.Linq;
using OpenTK;
using osu.Framework.Configuration; using osu.Framework.Configuration;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Game.Online.API.Requests; using osu.Game.Online.API.Requests;
using osu.Game.Overlays.Direct; using osu.Game.Overlays.Direct;
using osu.Game.Users; using osu.Game.Users;
using System.Linq; using OpenTK;
namespace osu.Game.Overlays.Profile.Sections.Beatmaps namespace osu.Game.Overlays.Profile.Sections.Beatmaps
{ {
@ -18,10 +17,6 @@ namespace osu.Game.Overlays.Profile.Sections.Beatmaps
private readonly BeatmapSetType type; private readonly BeatmapSetType type;
private DirectPanel currentlyPlaying;
public event Action<PaginatedBeatmapContainer> BeganPlayingPreview;
public PaginatedBeatmapContainer(BeatmapSetType type, Bindable<User> user, string header, string missing = "None... yet.") public PaginatedBeatmapContainer(BeatmapSetType type, Bindable<User> user, string header, string missing = "None... yet.")
: base(user, header, missing) : base(user, header, missing)
{ {
@ -56,28 +51,10 @@ namespace osu.Game.Overlays.Profile.Sections.Beatmaps
var panel = new DirectGridPanel(s.ToBeatmapSet(Rulesets)); var panel = new DirectGridPanel(s.ToBeatmapSet(Rulesets));
ItemsContainer.Add(panel); ItemsContainer.Add(panel);
panel.PreviewPlaying.ValueChanged += isPlaying =>
{
StopPlayingPreview();
if (isPlaying)
{
BeganPlayingPreview?.Invoke(this);
currentlyPlaying = panel;
}
};
} }
}; };
Api.Queue(req); Api.Queue(req);
} }
public void StopPlayingPreview()
{
if (currentlyPlaying == null) return;
currentlyPlaying.PreviewPlaying.Value = false;
currentlyPlaying = null;
}
} }
} }

View File

@ -1,7 +1,6 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Linq;
using osu.Game.Online.API.Requests; using osu.Game.Online.API.Requests;
using osu.Game.Overlays.Profile.Sections.Beatmaps; using osu.Game.Overlays.Profile.Sections.Beatmaps;
@ -22,15 +21,6 @@ namespace osu.Game.Overlays.Profile.Sections
new PaginatedBeatmapContainer(BeatmapSetType.Unranked, User, "Pending Beatmaps"), new PaginatedBeatmapContainer(BeatmapSetType.Unranked, User, "Pending Beatmaps"),
new PaginatedBeatmapContainer(BeatmapSetType.Graveyard, User, "Graveyarded Beatmaps"), new PaginatedBeatmapContainer(BeatmapSetType.Graveyard, User, "Graveyarded Beatmaps"),
}; };
foreach (var paginatedBeatmapContainer in Children.OfType<PaginatedBeatmapContainer>())
{
paginatedBeatmapContainer.BeganPlayingPreview += _ =>
{
foreach (var bc in Children.OfType<PaginatedBeatmapContainer>())
bc.StopPlayingPreview();
};
}
} }
} }
} }

View File

@ -64,6 +64,7 @@ namespace osu.Game.Overlays.Profile.Sections
{ {
TextSize = 14, TextSize = 14,
Text = "show more", Text = "show more",
Padding = new MarginPadding {Vertical = 10, Horizontal = 15 },
} }
}, },
ShowMoreLoading = new LoadingAnimation ShowMoreLoading = new LoadingAnimation

View File

@ -2,8 +2,6 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Linq; using System.Linq;
using OpenTK;
using OpenTK.Graphics;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
@ -18,6 +16,8 @@ using osu.Game.Online.API.Requests;
using osu.Game.Overlays.Profile; using osu.Game.Overlays.Profile;
using osu.Game.Overlays.Profile.Sections; using osu.Game.Overlays.Profile.Sections;
using osu.Game.Users; using osu.Game.Users;
using OpenTK;
using OpenTK.Graphics;
namespace osu.Game.Overlays namespace osu.Game.Overlays
{ {

View File

@ -0,0 +1,19 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Rulesets.Mods;
namespace osu.Game.Rulesets.Difficulty
{
public class DifficultyAttributes
{
public readonly Mod[] Mods;
public readonly double StarRating;
public DifficultyAttributes(Mod[] mods, double starRating)
{
Mods = mods;
StarRating = starRating;
}
}
}

View File

@ -13,28 +13,44 @@ namespace osu.Game.Rulesets.Difficulty
{ {
public abstract class DifficultyCalculator public abstract class DifficultyCalculator
{ {
protected readonly IBeatmap Beatmap; private readonly Ruleset ruleset;
protected readonly Mod[] Mods; private readonly WorkingBeatmap beatmap;
protected double TimeRate { get; private set; } = 1; protected DifficultyCalculator(Ruleset ruleset, WorkingBeatmap beatmap)
protected DifficultyCalculator(IBeatmap beatmap, Mod[] mods = null)
{ {
Beatmap = beatmap; this.ruleset = ruleset;
Mods = mods ?? new Mod[0]; this.beatmap = beatmap;
ApplyMods(Mods);
} }
protected virtual void ApplyMods(Mod[] mods) /// <summary>
/// Calculates the difficulty of the beatmap using a specific mod combination.
/// </summary>
/// <param name="mods">The mods that should be applied to the beatmap.</param>
/// <returns>A structure describing the difficulty of the beatmap.</returns>
public DifficultyAttributes Calculate(params Mod[] mods)
{ {
beatmap.Mods.Value = mods;
IBeatmap playableBeatmap = beatmap.GetPlayableBeatmap(ruleset.RulesetInfo);
var clock = new StopwatchClock(); var clock = new StopwatchClock();
mods.OfType<IApplicableToClock>().ForEach(m => m.ApplyToClock(clock)); mods.OfType<IApplicableToClock>().ForEach(m => m.ApplyToClock(clock));
TimeRate = clock.Rate;
return Calculate(playableBeatmap, mods, clock.Rate);
} }
protected virtual void PreprocessHitObjects() /// <summary>
/// Calculates the difficulty of the beatmap using all mod combinations applicable to the beatmap.
/// </summary>
/// <returns>A collection of structures describing the difficulty of the beatmap for each mod combination.</returns>
public IEnumerable<DifficultyAttributes> CalculateAll()
{ {
foreach (var combination in CreateDifficultyAdjustmentModCombinations())
{
if (combination is MultiMod multi)
yield return Calculate(multi.Mods);
else
yield return Calculate(combination);
}
} }
/// <summary> /// <summary>
@ -75,6 +91,13 @@ namespace osu.Game.Rulesets.Difficulty
/// </summary> /// </summary>
protected virtual Mod[] DifficultyAdjustmentMods => Array.Empty<Mod>(); protected virtual Mod[] DifficultyAdjustmentMods => Array.Empty<Mod>();
public abstract double Calculate(Dictionary<string, double> categoryDifficulty = null); /// <summary>
/// Calculates the difficulty of a <see cref="Beatmap"/> using a specific <see cref="Mod"/> combination.
/// </summary>
/// <param name="beatmap">The <see cref="IBeatmap"/> to compute the difficulty for.</param>
/// <param name="mods">The <see cref="Mod"/>s that should be applied.</param>
/// <param name="timeRate">The rate of time in <paramref name="beatmap"/>.</param>
/// <returns>A structure containing the difficulty attributes.</returns>
protected abstract DifficultyAttributes Calculate(IBeatmap beatmap, Mod[] mods, double timeRate);
} }
} }

View File

@ -13,8 +13,7 @@ namespace osu.Game.Rulesets.Difficulty
{ {
public abstract class PerformanceCalculator public abstract class PerformanceCalculator
{ {
private readonly Dictionary<string, double> attributes = new Dictionary<string, double>(); protected readonly DifficultyAttributes Attributes;
protected IDictionary<string, double> Attributes => attributes;
protected readonly Ruleset Ruleset; protected readonly Ruleset Ruleset;
protected readonly IBeatmap Beatmap; protected readonly IBeatmap Beatmap;
@ -22,14 +21,15 @@ namespace osu.Game.Rulesets.Difficulty
protected double TimeRate { get; private set; } = 1; protected double TimeRate { get; private set; } = 1;
protected PerformanceCalculator(Ruleset ruleset, IBeatmap beatmap, Score score) protected PerformanceCalculator(Ruleset ruleset, WorkingBeatmap beatmap, Score score)
{ {
Ruleset = ruleset; Ruleset = ruleset;
Beatmap = beatmap;
Score = score; Score = score;
var diffCalc = ruleset.CreateDifficultyCalculator(beatmap, score.Mods); beatmap.Mods.Value = score.Mods;
diffCalc.Calculate(attributes); Beatmap = beatmap.GetPlayableBeatmap(ruleset.RulesetInfo);
Attributes = ruleset.CreateDifficultyCalculator(beatmap).Calculate(score.Mods);
ApplyMods(score.Mods); ApplyMods(score.Mods);
} }

View File

@ -61,9 +61,9 @@ namespace osu.Game.Rulesets
public virtual IBeatmapProcessor CreateBeatmapProcessor(IBeatmap beatmap) => null; public virtual IBeatmapProcessor CreateBeatmapProcessor(IBeatmap beatmap) => null;
public abstract DifficultyCalculator CreateDifficultyCalculator(IBeatmap beatmap, Mod[] mods = null); public abstract DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap);
public virtual PerformanceCalculator CreatePerformanceCalculator(IBeatmap beatmap, Score score) => null; public virtual PerformanceCalculator CreatePerformanceCalculator(WorkingBeatmap beatmap, Score score) => null;
public virtual HitObjectComposer CreateHitObjectComposer() => null; public virtual HitObjectComposer CreateHitObjectComposer() => null;

View File

@ -155,7 +155,7 @@ namespace osu.Game.Rulesets.UI
/// RulesetContainer that applies conversion to Beatmaps. Does not contain a Playfield /// RulesetContainer that applies conversion to Beatmaps. Does not contain a Playfield
/// and does not load drawable hit objects. /// and does not load drawable hit objects.
/// <para> /// <para>
/// Should not be derived - derive <see cref="RulesetContainer{TObject}"/> instead. /// Should not be derived - derive <see cref="RulesetContainer{TPlayfield, TObject}"/> instead.
/// </para> /// </para>
/// </summary> /// </summary>
/// <typeparam name="TObject">The type of HitObject contained by this RulesetContainer.</typeparam> /// <typeparam name="TObject">The type of HitObject contained by this RulesetContainer.</typeparam>

View File

@ -13,6 +13,7 @@ using osu.Game.Screens.Edit.Components.Timelines.Summary;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Graphics.UserInterface; using osu.Framework.Graphics.UserInterface;
using osu.Framework.Input; using osu.Framework.Input;
using osu.Framework.Platform;
using osu.Framework.Timing; using osu.Framework.Timing;
using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterface;
using osu.Game.Screens.Edit.Screens; using osu.Game.Screens.Edit.Screens;
@ -39,13 +40,16 @@ namespace osu.Game.Screens.Edit
private EditorClock clock; private EditorClock clock;
private DependencyContainer dependencies; private DependencyContainer dependencies;
private GameHost host;
protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent) protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent)
=> dependencies = new DependencyContainer(base.CreateLocalDependencies(parent)); => dependencies = new DependencyContainer(base.CreateLocalDependencies(parent));
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(OsuColour colours) private void load(OsuColour colours, GameHost host)
{ {
this.host = host;
// TODO: should probably be done at a RulesetContainer level to share logic with Player. // TODO: should probably be done at a RulesetContainer level to share logic with Player.
var sourceClock = (IAdjustableClock)Beatmap.Value.Track ?? new StopwatchClock(); var sourceClock = (IAdjustableClock)Beatmap.Value.Track ?? new StopwatchClock();
clock = new EditorClock(Beatmap.Value, beatDivisor) { IsCoupled = false }; clock = new EditorClock(Beatmap.Value, beatDivisor) { IsCoupled = false };
@ -155,7 +159,7 @@ namespace osu.Game.Screens.Edit
private void exportBeatmap() private void exportBeatmap()
{ {
Beatmap.Value.Save(); host.OpenFileExternally(Beatmap.Value.Save());
} }
private void onModeChanged(EditorScreenMode mode) private void onModeChanged(EditorScreenMode mode)
@ -181,9 +185,9 @@ namespace osu.Game.Screens.Edit
protected override bool OnScroll(InputState state) protected override bool OnScroll(InputState state)
{ {
if (state.Mouse.ScrollDelta.X + state.Mouse.ScrollDelta.Y > 0) if (state.Mouse.ScrollDelta.X + state.Mouse.ScrollDelta.Y > 0)
clock.SeekBackward(true); clock.SeekBackward(!clock.IsRunning);
else else
clock.SeekForward(true); clock.SeekForward(!clock.IsRunning);
return true; return true;
} }

View File

@ -0,0 +1,52 @@
// 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.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Game.Graphics;
using OpenTK;
namespace osu.Game.Screens.Edit.Screens.Compose.Timeline
{
public class CentreMarker : CompositeDrawable
{
private const float triangle_width = 20;
private const float triangle_height = 10;
private const float bar_width = 2;
public CentreMarker()
{
RelativeSizeAxes = Axes.Y;
Size = new Vector2(20, 1);
Anchor = Anchor.Centre;
Origin = Anchor.Centre;
InternalChildren = new Drawable[]
{
new Box
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Y,
Width = bar_width,
},
new Triangle
{
Anchor = Anchor.TopCentre,
Origin = Anchor.BottomCentre,
Size = new Vector2(triangle_width, triangle_height),
Scale = new Vector2(1, -1)
}
};
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
Colour = colours.Red;
}
}
}

View File

@ -2,9 +2,13 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Audio.Track;
using osu.Framework.Configuration; using osu.Framework.Configuration;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Audio; using osu.Framework.Graphics.Audio;
using osu.Framework.Input;
using osu.Framework.Timing;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Graphics; using osu.Game.Graphics;
@ -15,25 +19,36 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Timeline
public readonly Bindable<bool> WaveformVisible = new Bindable<bool>(); public readonly Bindable<bool> WaveformVisible = new Bindable<bool>();
public readonly IBindable<WorkingBeatmap> Beatmap = new Bindable<WorkingBeatmap>(); public readonly IBindable<WorkingBeatmap> Beatmap = new Bindable<WorkingBeatmap>();
private IAdjustableClock adjustableClock;
public Timeline() public Timeline()
{ {
ZoomDuration = 200; ZoomDuration = 200;
ZoomEasing = Easing.OutQuint; ZoomEasing = Easing.OutQuint;
Zoom = 10; Zoom = 10;
ScrollbarVisible = false;
} }
private WaveformGraph waveform; private WaveformGraph waveform;
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(IBindableBeatmap beatmap) private void load(IBindableBeatmap beatmap, IAdjustableClock adjustableClock, OsuColour colours)
{ {
this.adjustableClock = adjustableClock;
Child = waveform = new WaveformGraph Child = waveform = new WaveformGraph
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Colour = OsuColour.FromHex("222"), Colour = colours.Blue.Opacity(0.2f),
LowColour = colours.BlueLighter,
MidColour = colours.BlueDark,
HighColour = colours.BlueDarker,
Depth = float.MaxValue Depth = float.MaxValue
}; };
// We don't want the centre marker to scroll
AddInternal(new CentreMarker());
WaveformVisible.ValueChanged += visible => waveform.FadeTo(visible ? 1 : 0, 200, Easing.OutQuint); WaveformVisible.ValueChanged += visible => waveform.FadeTo(visible ? 1 : 0, 200, Easing.OutQuint);
Beatmap.BindTo(beatmap); Beatmap.BindTo(beatmap);
@ -46,12 +61,102 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Timeline
waveform.Waveform = Beatmap.Value.Waveform; waveform.Waveform = Beatmap.Value.Waveform;
} }
/// <summary>
/// The track's time in the previous frame.
/// </summary>
private double lastTrackTime;
/// <summary>
/// Whether the user is currently dragging the timeline.
/// </summary>
private bool handlingDragInput;
/// <summary>
/// Whether the track was playing before a user drag event.
/// </summary>
private bool trackWasPlaying;
protected override void Update() protected override void Update()
{ {
base.Update(); base.Update();
// We want time = 0 to be at the centre of the container when scrolled to the start // The extrema of track time should be positioned at the centre of the container when scrolled to the start or end
Content.Margin = new MarginPadding { Horizontal = DrawWidth / 2 }; Content.Margin = new MarginPadding { Horizontal = DrawWidth / 2 };
if (handlingDragInput)
{
// The user is dragging - the track should always follow the timeline
seekTrackToCurrent();
}
else if (adjustableClock.IsRunning)
{
// If the user hasn't provided mouse input but the track is running, always follow the track
scrollToTrackTime();
}
else
{
// The track isn't playing, so we want to smooth-scroll once more, and re-enable wheel scrolling
// There are two cases we have to be wary of:
// 1) The user scrolls on this timeline: We want the track to follow us
// 2) The user changes the track time through some other means (scrolling in the editor or overview timeline): We want to follow the track time
// The simplest way to cover both cases is by checking that inter-frame track times are identical
if (adjustableClock.CurrentTime == lastTrackTime)
{
// The track hasn't been seeked externally
seekTrackToCurrent();
}
else
{
// The track has been seeked externally
scrollToTrackTime();
}
}
lastTrackTime = adjustableClock.CurrentTime;
void seekTrackToCurrent()
{
if (!(Beatmap.Value.Track is TrackVirtual))
adjustableClock.Seek(Current / Content.DrawWidth * Beatmap.Value.Track.Length);
}
void scrollToTrackTime()
{
if (!(Beatmap.Value.Track is TrackVirtual))
ScrollTo((float)(adjustableClock.CurrentTime / Beatmap.Value.Track.Length) * Content.DrawWidth, false);
}
}
protected override bool OnMouseDown(InputState state, MouseDownEventArgs args)
{
if (base.OnMouseDown(state, args))
{
beginUserDrag();
return true;
}
return false;
}
protected override bool OnMouseUp(InputState state, MouseUpEventArgs args)
{
endUserDrag();
return base.OnMouseUp(state, args);
}
private void beginUserDrag()
{
handlingDragInput = true;
trackWasPlaying = adjustableClock.IsRunning;
adjustableClock.Stop();
}
private void endUserDrag()
{
handlingDragInput = false;
if (trackWasPlaying)
adjustableClock.Start();
} }
} }
} }

View File

@ -99,10 +99,11 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Timeline
protected override bool OnScroll(InputState state) protected override bool OnScroll(InputState state)
{ {
if (!state.Keyboard.ControlPressed) if (state.Mouse.HasPreciseScroll)
// for now, we don't support zoom when using a precision scroll device. this needs gesture support.
return base.OnScroll(state); return base.OnScroll(state);
setZoomTarget(zoomTarget + state.Mouse.ScrollDelta.X, zoomedContent.ToLocalSpace(state.Mouse.NativeState.Position).X); setZoomTarget(zoomTarget + state.Mouse.ScrollDelta.Y, zoomedContent.ToLocalSpace(state.Mouse.NativeState.Position).X);
return true; return true;
} }

View File

@ -164,7 +164,7 @@ namespace osu.Game.Screens.Multi.Screens.Lounge
// open the room if its selected and is clicked again // open the room if its selected and is clicked again
if (room.State == SelectionState.Selected) if (room.State == SelectionState.Selected)
Push(new Match()); Push(new Match.Match(room.Room));
} }
private class RoomsFilterContainer : FillFlowContainer<DrawableRoom>, IHasFilterableChildren private class RoomsFilterContainer : FillFlowContainer<DrawableRoom>, IHasFilterableChildren

View File

@ -1,37 +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 osu.Framework.Graphics;
using osu.Framework.Screens;
using osu.Game.Screens.Backgrounds;
using osu.Game.Screens.Play;
using osu.Game.Screens.Select;
using OpenTK.Graphics;
namespace osu.Game.Screens.Multi.Screens
{
public class Match : ScreenWhiteBox
{
protected override IEnumerable<Type> PossibleChildren => new[] {
typeof(MatchSongSelect),
typeof(Player),
};
protected override BackgroundScreen CreateBackground() => new BackgroundScreenCustom(@"Backgrounds/bg4");
protected override void OnEntering(Screen last)
{
base.OnEntering(last);
Background.FadeColour(Color4.DarkGray, 500);
}
protected override bool OnExiting(Screen next)
{
Background.FadeColour(Color4.White, 500);
return base.OnExiting(next);
}
}
}

View File

@ -0,0 +1,184 @@
// 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.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;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Drawables;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
using osu.Game.Overlays.SearchableList;
using OpenTK.Graphics;
namespace osu.Game.Screens.Multi.Screens.Match
{
public class Header : Container
{
public const float HEIGHT = 200;
private readonly Box tabStrip;
private readonly UpdateableBeatmapSetCover cover;
public readonly PageTabControl<MatchHeaderPage> Tabs;
public BeatmapSetInfo BeatmapSet
{
set => cover.BeatmapSet = value;
}
public Action OnRequestSelectBeatmap;
public Header()
{
RelativeSizeAxes = Axes.X;
Height = HEIGHT;
BeatmapSelectButton beatmapButton;
Children = new Drawable[]
{
cover = new UpdateableBeatmapSetCover
{
RelativeSizeAxes = Axes.Both,
Masking = true,
},
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = ColourInfo.GradientVertical(Color4.Black.Opacity(0), Color4.Black.Opacity(0.5f)),
},
tabStrip = new Box
{
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
RelativeSizeAxes = Axes.X,
Height = 1,
},
new Container
{
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding { Horizontal = SearchableListOverlay.WIDTH_PADDING },
Children = new Drawable[]
{
new Container
{
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
RelativeSizeAxes = Axes.Y,
Width = 200,
Padding = new MarginPadding { Vertical = 5 },
Child = beatmapButton = new BeatmapSelectButton
{
RelativeSizeAxes = Axes.Both,
},
},
Tabs = new PageTabControl<MatchHeaderPage>
{
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
RelativeSizeAxes = Axes.X,
},
},
},
};
beatmapButton.Action = () => OnRequestSelectBeatmap?.Invoke();
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
tabStrip.Colour = colours.Yellow;
}
private class BeatmapSelectButton : OsuClickableContainer
{
private const float corner_radius = 5;
private const float bg_opacity = 0.5f;
private const float transition_duration = 100;
private readonly Box bg;
private readonly Container border;
public BeatmapSelectButton()
{
Masking = true;
CornerRadius = corner_radius;
Children = new Drawable[]
{
bg = new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Color4.Black,
Alpha = bg_opacity,
},
new OsuSpriteText
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Font = @"Exo2.0-Bold",
Text = "Select Beatmap",
},
border = new Container
{
RelativeSizeAxes = Axes.Both,
Masking = true,
CornerRadius = corner_radius,
BorderThickness = 4,
Alpha = 0,
Child = new Box // needs a child to show the border
{
RelativeSizeAxes = Axes.Both,
Alpha = 0,
AlwaysPresent = true
},
},
};
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
border.BorderColour = colours.Yellow;
}
protected override bool OnHover(InputState state)
{
border.FadeIn(transition_duration);
return base.OnHover(state);
}
protected override void OnHoverLost(InputState state)
{
base.OnHoverLost(state);
border.FadeOut(transition_duration);
}
protected override bool OnMouseDown(InputState state, MouseDownEventArgs args)
{
bg.FadeTo(0.75f, 1000, Easing.Out);
return base.OnMouseDown(state, args);
}
protected override bool OnMouseUp(InputState state, MouseUpEventArgs args)
{
bg.FadeTo(bg_opacity, transition_duration);
return base.OnMouseUp(state, args);
}
}
}
public enum MatchHeaderPage
{
Settings,
Room,
}
}

View File

@ -0,0 +1,210 @@
// 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;
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.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
using osu.Game.Online.Multiplayer;
using osu.Game.Overlays.SearchableList;
using osu.Game.Screens.Multi.Components;
using OpenTK;
namespace osu.Game.Screens.Multi.Screens.Match
{
public class Info : Container
{
public const float HEIGHT = 128;
private readonly OsuSpriteText name, availabilityStatus;
private readonly BeatmapTypeInfo beatmapTypeInfo;
private readonly ReadyButton readyButton;
private OsuColour colours;
public Bindable<bool> Ready => readyButton.Ready;
public string Name
{
set { name.Text = value; }
}
private RoomAvailability availability;
public RoomAvailability Availability
{
set
{
if (value == availability) return;
availability = value;
if (IsLoaded)
updateAvailabilityStatus();
}
}
private RoomStatus status;
public RoomStatus Status
{
set
{
if (value == status) return;
status = value;
if (IsLoaded)
updateAvailabilityStatus();
}
}
public BeatmapInfo Beatmap
{
set { beatmapTypeInfo.Beatmap = value; }
}
public GameType Type
{
set { beatmapTypeInfo.Type = value; }
}
public Info()
{
RelativeSizeAxes = Axes.X;
Height = HEIGHT;
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = OsuColour.FromHex(@"28242d"),
},
new Container
{
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding { Horizontal = SearchableListOverlay.WIDTH_PADDING },
Children = new Drawable[]
{
new Container
{
RelativeSizeAxes = Axes.Y,
AutoSizeAxes = Axes.X,
Padding = new MarginPadding { Vertical = 20 },
Children = new Drawable[]
{
new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Vertical,
Children = new Drawable[]
{
name = new OsuSpriteText
{
TextSize = 30,
},
availabilityStatus = new OsuSpriteText
{
TextSize = 14,
},
},
},
beatmapTypeInfo = new BeatmapTypeInfo
{
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
},
},
},
readyButton = new ReadyButton
{
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
RelativeSizeAxes = Axes.Y,
Size = new Vector2(200, 1),
Padding = new MarginPadding { Vertical = 10 },
},
},
},
};
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
this.colours = colours;
}
protected override void LoadComplete()
{
base.LoadComplete();
updateAvailabilityStatus();
}
private void updateAvailabilityStatus()
{
if (status != null)
{
availabilityStatus.FadeColour(status.GetAppropriateColour(colours), 100);
availabilityStatus.Text = $"{availability.GetDescription()}, {status.Message}";
}
}
private class ReadyButton : TriangleButton
{
public readonly Bindable<bool> Ready = new Bindable<bool>();
protected override SpriteText CreateText() => new OsuSpriteText
{
Depth = -1,
Origin = Anchor.Centre,
Anchor = Anchor.Centre,
Font = @"Exo2.0-Light",
TextSize = 30,
};
[BackgroundDependencyLoader]
private void load()
{
BackgroundColour = OsuColour.FromHex(@"1187aa");
Triangles.ColourLight = OsuColour.FromHex(@"277b9c");
Triangles.ColourDark = OsuColour.FromHex(@"1f6682");
Triangles.TriangleScale = 1.5f;
Container active;
Add(active = new Container
{
RelativeSizeAxes = Axes.Both,
Alpha = 0f,
Child = new Box
{
RelativeSizeAxes = Axes.Both,
Alpha = 0.15f,
Blending = BlendingMode.Additive,
},
});
Action = () => Ready.Value = !Ready.Value;
Ready.BindValueChanged(value =>
{
if (value)
{
Text = "Not Ready";
active.FadeIn(200);
}
else
{
Text = "Ready";
active.FadeOut(200);
}
}, true);
}
}
}
}

View File

@ -0,0 +1,81 @@
// 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 osu.Framework.Configuration;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Beatmaps;
using osu.Game.Online.Multiplayer;
using osu.Game.Screens.Select;
using osu.Game.Users;
namespace osu.Game.Screens.Multi.Screens.Match
{
public class Match : MultiplayerScreen
{
private readonly Room room;
private readonly Participants participants;
private readonly Bindable<string> nameBind = new Bindable<string>();
private readonly Bindable<RoomStatus> statusBind = new Bindable<RoomStatus>();
private readonly Bindable<RoomAvailability> availabilityBind = new Bindable<RoomAvailability>();
private readonly Bindable<GameType> typeBind = new Bindable<GameType>();
private readonly Bindable<BeatmapInfo> beatmapBind = new Bindable<BeatmapInfo>();
private readonly Bindable<int?> maxParticipantsBind = new Bindable<int?>();
private readonly Bindable<IEnumerable<User>> participantsBind = new Bindable<IEnumerable<User>>();
protected override Container<Drawable> TransitionContent => participants;
public override string Type => "room";
public override string Title => room.Name.Value;
public Match(Room room)
{
this.room = room;
Header header;
Info info;
Children = new Drawable[]
{
header = new Header(),
info = new Info
{
Margin = new MarginPadding { Top = Header.HEIGHT },
},
participants = new Participants
{
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding { Top = Header.HEIGHT + Info.HEIGHT },
},
};
header.OnRequestSelectBeatmap = () => Push(new MatchSongSelect());
beatmapBind.BindTo(room.Beatmap);
beatmapBind.BindValueChanged(b =>
{
header.BeatmapSet = b?.BeatmapSet;
info.Beatmap = b;
}, true);
nameBind.BindTo(room.Name);
nameBind.BindValueChanged(n => info.Name = n, true);
statusBind.BindTo(room.Status);
statusBind.BindValueChanged(s => info.Status = s, true);
availabilityBind.BindTo(room.Availability);
availabilityBind.BindValueChanged(a => info.Availability = a, true);
typeBind.BindTo(room.Type);
typeBind.BindValueChanged(t => info.Type = t, true);
maxParticipantsBind.BindTo(room.MaxParticipants);
maxParticipantsBind.BindValueChanged(m => { participants.Max = m; }, true);
participantsBind.BindTo(room.Participants);
participantsBind.BindValueChanged(p => participants.Users = p, true);
}
}
}

View File

@ -0,0 +1,74 @@
// 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 System.Linq;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Overlays.SearchableList;
using osu.Game.Screens.Multi.Components;
using osu.Game.Users;
using OpenTK;
namespace osu.Game.Screens.Multi.Screens.Match
{
public class Participants : Container
{
private readonly ParticipantCount count;
private readonly FillFlowContainer<UserPanel> usersFlow;
public IEnumerable<User> Users
{
set {
usersFlow.Children = value.Select(u => new UserPanel(u)
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
Width = 300,
OnLoadComplete = d => d.FadeInFromZero(60),
}).ToList();
count.Count = value.Count();
}
}
public int? Max
{
set => count.Max = value;
}
public Participants()
{
Child = new Container
{
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding { Horizontal = SearchableListOverlay.WIDTH_PADDING },
Children = new Drawable[]
{
new ScrollContainer
{
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding { Top = 10 },
Children = new Drawable[]
{
count = new ParticipantCount
{
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
},
usersFlow = new FillFlowContainer<UserPanel>
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Spacing = new Vector2(5),
Padding = new MarginPadding { Top = 40 },
LayoutDuration = 200,
LayoutEasing = Easing.OutQuint,
},
},
},
},
};
}
}
}

View File

@ -1,20 +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;
namespace osu.Game.Screens.Multi.Screens
{
public class MatchCreate : ScreenWhiteBox
{
protected override IEnumerable<Type> PossibleChildren => new[] {
typeof(Match)
};
public MatchCreate()
{
ValidForResume = false;
}
}
}

View File

@ -10,9 +10,6 @@ namespace osu.Game.Screens.Multi.Screens
{ {
public abstract class MultiplayerScreen : OsuScreen public abstract class MultiplayerScreen : OsuScreen
{ {
private const Easing in_easing = Easing.OutQuint;
private const Easing out_easing = Easing.InSine;
protected virtual Container<Drawable> TransitionContent => Content; protected virtual Container<Drawable> TransitionContent => Content;
/// <summary> /// <summary>
@ -24,16 +21,15 @@ namespace osu.Game.Screens.Multi.Screens
{ {
base.OnEntering(last); base.OnEntering(last);
TransitionContent.MoveToX(200); Content.FadeInFromZero(WaveContainer.APPEAR_DURATION, Easing.OutQuint);
TransitionContent.FadeInFromZero(WaveContainer.APPEAR_DURATION, Easing.OutQuint);
TransitionContent.FadeInFromZero(WaveContainer.APPEAR_DURATION, in_easing); TransitionContent.MoveToX(200).MoveToX(0, WaveContainer.APPEAR_DURATION, Easing.OutQuint);
TransitionContent.MoveToX(0, WaveContainer.APPEAR_DURATION, in_easing);
} }
protected override bool OnExiting(Screen next) protected override bool OnExiting(Screen next)
{ {
Content.FadeOut(WaveContainer.DISAPPEAR_DURATION, out_easing); Content.FadeOut(WaveContainer.DISAPPEAR_DURATION, Easing.OutQuint);
TransitionContent.MoveToX(200, WaveContainer.DISAPPEAR_DURATION, out_easing); TransitionContent.MoveToX(200, WaveContainer.DISAPPEAR_DURATION, Easing.OutQuint);
return base.OnExiting(next); return base.OnExiting(next);
} }
@ -42,16 +38,16 @@ namespace osu.Game.Screens.Multi.Screens
{ {
base.OnResuming(last); base.OnResuming(last);
Content.FadeIn(WaveContainer.APPEAR_DURATION, in_easing); Content.FadeIn(WaveContainer.APPEAR_DURATION, Easing.OutQuint);
TransitionContent.MoveToX(0, WaveContainer.APPEAR_DURATION, in_easing); TransitionContent.MoveToX(0, WaveContainer.APPEAR_DURATION, Easing.OutQuint);
} }
protected override void OnSuspending(Screen next) protected override void OnSuspending(Screen next)
{ {
base.OnSuspending(next); base.OnSuspending(next);
Content.FadeOut(WaveContainer.DISAPPEAR_DURATION, out_easing); Content.FadeOut(WaveContainer.DISAPPEAR_DURATION, Easing.OutQuint);
TransitionContent.MoveToX(-200, WaveContainer.DISAPPEAR_DURATION, out_easing); TransitionContent.MoveToX(-200, WaveContainer.DISAPPEAR_DURATION, Easing.OutQuint);
} }
} }
} }

View File

@ -7,12 +7,7 @@ namespace osu.Game.Screens.Select
{ {
protected override bool OnStart() protected override bool OnStart()
{ {
Schedule(() => if (IsCurrentScreen) Exit();
{
// needs to be scheduled else we enter an infinite feedback loop.
if (IsCurrentScreen) Exit();
});
return true; return true;
} }
} }

View File

@ -3,6 +3,7 @@
using osu.Framework.Platform; using osu.Framework.Platform;
using osu.Framework.Testing; using osu.Framework.Testing;
using osu.Game.Graphics;
using osu.Game.Screens.Backgrounds; using osu.Game.Screens.Backgrounds;
namespace osu.Game.Tests namespace osu.Game.Tests
@ -13,7 +14,11 @@ namespace osu.Game.Tests
{ {
base.LoadComplete(); base.LoadComplete();
LoadComponentAsync(new BackgroundScreenDefault { Depth = 10 }, AddInternal); LoadComponentAsync(new BackgroundScreenDefault
{
Colour = OsuColour.Gray(0.5f),
Depth = 10
}, AddInternal);
// Have to construct this here, rather than in the constructor, because // Have to construct this here, rather than in the constructor, because
// we depend on some dependencies to be loaded within OsuGameBase.load(). // we depend on some dependencies to be loaded within OsuGameBase.load().

View File

@ -25,13 +25,20 @@ namespace osu.Game.Tests.Visual
Clock = new EditorClock(new ControlPointInfo(), 5000, BeatDivisor) { IsCoupled = false }; Clock = new EditorClock(new ControlPointInfo(), 5000, BeatDivisor) { IsCoupled = false };
} }
protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent)
{
var dependencies = new DependencyContainer(base.CreateLocalDependencies(parent));
dependencies.Cache(BeatDivisor);
dependencies.CacheAs<IFrameBasedClock>(Clock);
dependencies.CacheAs<IAdjustableClock>(Clock);
return dependencies;
}
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load() private void load()
{ {
Dependencies.Cache(BeatDivisor);
Dependencies.CacheAs<IFrameBasedClock>(Clock);
Dependencies.CacheAs<IAdjustableClock>(Clock);
Beatmap.BindValueChanged(beatmapChanged, true); Beatmap.BindValueChanged(beatmapChanged, true);
} }

View File

@ -1,403 +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.Collections.Generic;
using System.Linq;
using OpenTK;
using OpenTK.Graphics;
using osu.Framework.Allocation;
using osu.Framework.Caching;
using osu.Framework.Configuration;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Cursor;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Input;
using osu.Game.Beatmaps;
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Scoring;
namespace osu.Game.Tests.Visual
{
public abstract class TestCasePerformancePoints : OsuTestCase
{
protected TestCasePerformancePoints(Ruleset ruleset)
{
Child = new GridContainer
{
RelativeSizeAxes = Axes.Both,
Content = new[]
{
new Drawable[]
{
new Container
{
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Color4.Black,
Alpha = 0.5f,
},
new ScrollContainer
{
RelativeSizeAxes = Axes.Both,
Child = new BeatmapList(ruleset, Beatmap)
}
}
},
null,
new Container
{
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Color4.Black,
Alpha = 0.5f,
},
new ScrollContainer
{
RelativeSizeAxes = Axes.Both,
Child = new StarRatingGrid()
}
}
},
null,
new Container
{
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Color4.Black,
Alpha = 0.5f,
},
new ScrollContainer
{
RelativeSizeAxes = Axes.Both,
Child = new PerformanceList()
}
}
},
}
},
ColumnDimensions = new[]
{
new Dimension(),
new Dimension(GridSizeMode.Absolute, 20),
new Dimension(),
new Dimension(GridSizeMode.Absolute, 20)
}
};
}
private class BeatmapList : CompositeDrawable
{
private readonly Container<BeatmapDisplay> beatmapDisplays;
private readonly Ruleset ruleset;
private readonly BindableBeatmap beatmapBindable;
public BeatmapList(Ruleset ruleset, BindableBeatmap beatmapBindable)
{
this.ruleset = ruleset;
this.beatmapBindable = beatmapBindable;
RelativeSizeAxes = Axes.X;
AutoSizeAxes = Axes.Y;
InternalChild = beatmapDisplays = new FillFlowContainer<BeatmapDisplay>
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Direction = FillDirection.Vertical,
Spacing = new Vector2(0, 4)
};
}
[BackgroundDependencyLoader]
private void load(BeatmapManager beatmaps)
{
var sets = beatmaps.GetAllUsableBeatmapSets();
var allBeatmaps = sets.SelectMany(s => s.Beatmaps).Where(b => ruleset.LegacyID == null || b.RulesetID == ruleset.LegacyID);
allBeatmaps.ForEach(b => beatmapDisplays.Add(new BeatmapDisplay(b, beatmapBindable)));
}
private class BeatmapDisplay : CompositeDrawable, IHasTooltip
{
private readonly OsuSpriteText text;
private readonly BeatmapInfo beatmap;
private readonly BindableBeatmap beatmapBindable;
private BeatmapManager beatmaps;
private bool isSelected;
public string TooltipText => text.Text;
public BeatmapDisplay(BeatmapInfo beatmap, BindableBeatmap beatmapBindable)
{
this.beatmap = beatmap;
this.beatmapBindable = beatmapBindable;
AutoSizeAxes = Axes.Both;
InternalChild = text = new OsuSpriteText();
this.beatmapBindable.ValueChanged += beatmapChanged;
}
[BackgroundDependencyLoader]
private void load(BeatmapManager beatmaps)
{
this.beatmaps = beatmaps;
var working = beatmaps.GetWorkingBeatmap(beatmap);
text.Text = $"{working.Metadata.Artist} - {working.Metadata.Title} ({working.Metadata.AuthorString}) [{working.BeatmapInfo.Version}]";
}
private void beatmapChanged(WorkingBeatmap newBeatmap)
{
if (isSelected)
this.FadeColour(Color4.White, 100);
isSelected = false;
}
protected override bool OnClick(InputState state)
{
if (beatmapBindable.Value.BeatmapInfo.ID == beatmap.ID)
return false;
beatmapBindable.Value = beatmaps.GetWorkingBeatmap(beatmap);
isSelected = true;
return true;
}
protected override bool OnHover(InputState state)
{
if (isSelected)
return false;
this.FadeColour(Color4.Yellow, 100);
return true;
}
protected override void OnHoverLost(InputState state)
{
if (isSelected)
return;
this.FadeColour(Color4.White, 100);
}
}
}
private class PerformanceList : CompositeDrawable
{
private readonly FillFlowContainer<PerformanceDisplay> scores;
private APIAccess api;
private readonly IBindable<WorkingBeatmap> currentBeatmap = new Bindable<WorkingBeatmap>();
public PerformanceList()
{
RelativeSizeAxes = Axes.X;
AutoSizeAxes = Axes.Y;
InternalChild = scores = new FillFlowContainer<PerformanceDisplay>
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Direction = FillDirection.Vertical,
Spacing = new Vector2(0, 4)
};
}
[BackgroundDependencyLoader]
private void load(IBindableBeatmap beatmap, APIAccess api)
{
this.api = api;
if (!api.IsLoggedIn)
{
InternalChild = new OsuSpriteText
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
Text = "Please sign in to see online scores",
};
}
currentBeatmap.ValueChanged += beatmapChanged;
currentBeatmap.BindTo(beatmap);
}
private GetScoresRequest lastRequest;
private void beatmapChanged(WorkingBeatmap newBeatmap)
{
if (!IsAlive) return;
lastRequest?.Cancel();
scores.Clear();
if (!api.IsLoggedIn)
return;
lastRequest = new GetScoresRequest(newBeatmap.BeatmapInfo, newBeatmap.BeatmapInfo.Ruleset);
lastRequest.Success += res => res.Scores.ForEach(s => scores.Add(new PerformanceDisplay(s, newBeatmap.Beatmap)));
api.Queue(lastRequest);
}
private class PerformanceDisplay : CompositeDrawable
{
private readonly OsuSpriteText text;
private readonly Score score;
private readonly IBeatmap beatmap;
public PerformanceDisplay(Score score, IBeatmap beatmap)
{
this.score = score;
this.beatmap = beatmap;
RelativeSizeAxes = Axes.X;
AutoSizeAxes = Axes.Y;
InternalChild = text = new OsuSpriteText();
}
[BackgroundDependencyLoader]
private void load()
{
var ruleset = beatmap.BeatmapInfo.Ruleset.CreateInstance();
var calculator = ruleset.CreatePerformanceCalculator(beatmap, score);
if (calculator == null)
return;
var attributes = new Dictionary<string, double>();
double performance = calculator.Calculate(attributes);
text.Text = $"{score.User.Username} -> online: {score.PP:n2}pp | local: {performance:n2}pp";
}
}
}
private class StarRatingGrid : CompositeDrawable
{
private readonly FillFlowContainer<OsuCheckbox> modFlow;
private readonly OsuSpriteText totalText;
private readonly FillFlowContainer categoryTexts;
public StarRatingGrid()
{
RelativeSizeAxes = Axes.X;
AutoSizeAxes = Axes.Y;
InternalChild = new FillFlowContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Children = new Drawable[]
{
modFlow = new FillFlowContainer<OsuCheckbox>
{
Name = "Checkbox flow",
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Spacing = new Vector2(4, 4)
},
new FillFlowContainer
{
Name = "Information display",
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Spacing = new Vector2(0, 4),
Direction = FillDirection.Vertical,
Children = new Drawable[]
{
totalText = new OsuSpriteText { TextSize = 24 },
categoryTexts = new FillFlowContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Direction = FillDirection.Vertical
}
}
}
}
};
}
[BackgroundDependencyLoader]
private void load(IBindableBeatmap beatmap)
{
beatmap.ValueChanged += beatmapChanged;
}
private Cached informationCache = new Cached();
private Ruleset ruleset;
private WorkingBeatmap beatmap;
private void beatmapChanged(WorkingBeatmap newBeatmap)
{
beatmap = newBeatmap;
modFlow.Clear();
ruleset = newBeatmap.BeatmapInfo.Ruleset.CreateInstance();
foreach (var mod in ruleset.GetAllMods())
{
var checkBox = new OsuCheckbox
{
RelativeSizeAxes = Axes.None,
Width = 50,
LabelText = mod.ShortenedName
};
checkBox.Current.ValueChanged += v => informationCache.Invalidate();
modFlow.Add(checkBox);
}
informationCache.Invalidate();
}
protected override void Update()
{
base.Update();
if (ruleset == null)
return;
if (!informationCache.IsValid)
{
totalText.Text = string.Empty;
categoryTexts.Clear();
var allMods = ruleset.GetAllMods().ToList();
Mod[] activeMods = modFlow.Where(c => c.Current.Value).Select(c => allMods.First(m => m.ShortenedName == c.LabelText)).ToArray();
var diffCalc = ruleset.CreateDifficultyCalculator(beatmap.Beatmap, activeMods);
if (diffCalc != null)
{
var categories = new Dictionary<string, double>();
double totalSr = diffCalc.Calculate(categories);
totalText.Text = $"Star rating: {totalSr:n2}";
foreach (var kvp in categories)
categoryTexts.Add(new OsuSpriteText { Text = $"{kvp.Key}: {kvp.Value:n2}" });
}
informationCache.Validate();
}
}
}
}
}

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