1
0
mirror of https://github.com/ppy/osu.git synced 2025-01-15 06:42:56 +08:00

Merge remote-tracking branch 'upstream/master' into chat-improvement

This commit is contained in:
Dean Herbert 2017-02-21 15:46:28 +09:00
commit afba4fbfad
No known key found for this signature in database
GPG Key ID: 46D71BF4958ABB49
34 changed files with 630 additions and 38 deletions

View File

@ -15,6 +15,7 @@ using osu.Framework.Graphics.Textures;
using osu.Game.Graphics; using osu.Game.Graphics;
using OpenTK; using OpenTK;
using OpenTK.Graphics; using OpenTK.Graphics;
using System.Net.Http;
namespace osu.Desktop.Overlays namespace osu.Desktop.Overlays
{ {
@ -115,7 +116,16 @@ namespace osu.Desktop.Overlays
private async void updateChecker() private async void updateChecker()
{ {
updateManager = await UpdateManager.GitHubUpdateManager(@"https://github.com/ppy/osu", @"osulazer", null, null, true); try
{
updateManager = await UpdateManager.GitHubUpdateManager(@"https://github.com/ppy/osu", @"osulazer", null, null, true);
}
catch(HttpRequestException)
{
//check again every 30 minutes.
Scheduler.AddDelayed(updateChecker, 60000 * 30);
return;
}
if (!updateManager.IsInstalledApp) if (!updateManager.IsInstalledApp)
return; return;

View File

@ -137,6 +137,16 @@
</Reference> </Reference>
<Reference Include="System" /> <Reference Include="System" />
<Reference Include="System.Drawing" /> <Reference Include="System.Drawing" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Net.Http.Extensions, Version=2.2.29.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Net.Http.2.2.29\lib\net45\System.Net.Http.Extensions.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Net.Http.Primitives, Version=4.2.29.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Net.Http.2.2.29\lib\net45\System.Net.Http.Primitives.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Net.Http.WebRequest" />
<Reference Include="System.Windows.Forms" /> <Reference Include="System.Windows.Forms" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
@ -219,7 +229,6 @@
<ItemGroup> <ItemGroup>
<Content Include="lazer.ico" /> <Content Include="lazer.ico" />
</ItemGroup> </ItemGroup>
<ItemGroup />
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" /> <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it. <!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets. Other similar extension points exist, see Microsoft.Common.targets.

View File

@ -5,6 +5,7 @@ Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/maste
--> -->
<packages> <packages>
<package id="DeltaCompressionDotNet" version="1.0.0" targetFramework="net45" /> <package id="DeltaCompressionDotNet" version="1.0.0" targetFramework="net45" />
<package id="Microsoft.Net.Http" version="2.2.29" targetFramework="net45" />
<package id="Mono.Cecil" version="0.9.6.1" targetFramework="net45" /> <package id="Mono.Cecil" version="0.9.6.1" targetFramework="net45" />
<package id="Splat" version="1.6.2" targetFramework="net45" /> <package id="Splat" version="1.6.2" targetFramework="net45" />
<package id="squirrel.windows" version="1.5.2" targetFramework="net45" /> <package id="squirrel.windows" version="1.5.2" targetFramework="net45" />

View File

@ -0,0 +1,27 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Beatmaps;
using osu.Game.Modes.Catch.Objects;
using osu.Game.Modes.Objects;
using System;
using System.Collections.Generic;
namespace osu.Game.Modes.Catch
{
public class CatchDifficultyCalculator : DifficultyCalculator<CatchBaseHit>
{
protected override PlayMode PlayMode => PlayMode.Catch;
public CatchDifficultyCalculator(Beatmap beatmap) : base(beatmap)
{
}
protected override HitObjectConverter<CatchBaseHit> Converter => new CatchConverter();
protected override double ComputeDifficulty(Dictionary<String, String> categoryDifficulty)
{
return 0;
}
}
}

View File

@ -1,14 +1,13 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Collections.Generic;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Modes.Catch.UI; using osu.Game.Modes.Catch.UI;
using osu.Game.Modes.Objects; using osu.Game.Modes.Objects;
using osu.Game.Modes.Osu.Objects;
using osu.Game.Modes.Osu.UI; using osu.Game.Modes.Osu.UI;
using osu.Game.Modes.UI; using osu.Game.Modes.UI;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using System;
namespace osu.Game.Modes.Catch namespace osu.Game.Modes.Catch
{ {
@ -25,5 +24,7 @@ namespace osu.Game.Modes.Catch
public override ScoreProcessor CreateScoreProcessor(int hitObjectCount) => null; public override ScoreProcessor CreateScoreProcessor(int hitObjectCount) => null;
public override HitObjectParser CreateHitObjectParser() => new NullHitObjectParser(); public override HitObjectParser CreateHitObjectParser() => new NullHitObjectParser();
public override DifficultyCalculator CreateDifficultyCalculator(Beatmap beatmap) => new CatchDifficultyCalculator(beatmap);
} }
} }

View File

@ -47,6 +47,7 @@
<Reference Include="System.Xml" /> <Reference Include="System.Xml" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="CatchDifficultyCalculator.cs" />
<Compile Include="Objects\CatchBaseHit.cs" /> <Compile Include="Objects\CatchBaseHit.cs" />
<Compile Include="Objects\CatchConverter.cs" /> <Compile Include="Objects\CatchConverter.cs" />
<Compile Include="Objects\Drawable\DrawableFruit.cs" /> <Compile Include="Objects\Drawable\DrawableFruit.cs" />

View File

@ -0,0 +1,30 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Beatmaps;
using osu.Game.Modes.Mania.Objects;
using osu.Game.Modes.Objects;
using System;
using System.Collections.Generic;
namespace osu.Game.Modes.Mania
{
public class ManiaDifficultyCalculator : DifficultyCalculator<ManiaBaseHit>
{
protected override PlayMode PlayMode => PlayMode.Mania;
private int columns;
public ManiaDifficultyCalculator(Beatmap beatmap, int columns = 5) : base(beatmap)
{
this.columns = columns;
}
protected override HitObjectConverter<ManiaBaseHit> Converter => new ManiaConverter(columns);
protected override double ComputeDifficulty(Dictionary<String, String> categoryDifficulty)
{
return 0;
}
}
}

View File

@ -1,15 +1,13 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Collections.Generic;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Modes.Mania.UI; using osu.Game.Modes.Mania.UI;
using osu.Game.Modes.Objects; using osu.Game.Modes.Objects;
using osu.Game.Modes.Osu;
using osu.Game.Modes.Osu.Objects;
using osu.Game.Modes.Osu.UI; using osu.Game.Modes.Osu.UI;
using osu.Game.Modes.UI; using osu.Game.Modes.UI;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using System;
namespace osu.Game.Modes.Mania namespace osu.Game.Modes.Mania
{ {
@ -26,5 +24,7 @@ namespace osu.Game.Modes.Mania
public override ScoreProcessor CreateScoreProcessor(int hitObjectCount) => null; public override ScoreProcessor CreateScoreProcessor(int hitObjectCount) => null;
public override HitObjectParser CreateHitObjectParser() => new NullHitObjectParser(); public override HitObjectParser CreateHitObjectParser() => new NullHitObjectParser();
public override DifficultyCalculator CreateDifficultyCalculator(Beatmap beatmap) => new ManiaDifficultyCalculator(beatmap);
} }
} }

View File

@ -47,6 +47,7 @@
<Reference Include="System.Xml" /> <Reference Include="System.Xml" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="ManiaDifficultyCalculator.cs" />
<Compile Include="Objects\Drawable\DrawableNote.cs" /> <Compile Include="Objects\Drawable\DrawableNote.cs" />
<Compile Include="Objects\HoldNote.cs" /> <Compile Include="Objects\HoldNote.cs" />
<Compile Include="Objects\ManiaBaseHit.cs" /> <Compile Include="Objects\ManiaBaseHit.cs" />

View File

@ -108,7 +108,7 @@ namespace osu.Game.Modes.Osu.Objects.Drawables
} }
} }
private Vector2 scaleToCircle => new Vector2(circle.Scale * circle.DrawWidth / DrawWidth) * 0.95f; private Vector2 scaleToCircle => (circle.Scale * circle.DrawWidth / DrawWidth) * 0.95f;
private float spinsPerMinuteNeeded = 100 + (5 * 15); //TODO: read per-map OD and place it on the 5 private float spinsPerMinuteNeeded = 100 + (5 * 15); //TODO: read per-map OD and place it on the 5

View File

@ -1,9 +1,12 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
namespace osu.Game.Modes.Osu.Objects namespace osu.Game.Modes.Osu.Objects
{ {
public class HitCircle : OsuHitObject public class HitCircle : OsuHitObject
{ {
public override HitObjectType Type => HitObjectType.Circle;
} }
} }

View File

@ -25,6 +25,8 @@ namespace osu.Game.Modes.Osu.Objects
public float Scale { get; set; } = 1; public float Scale { get; set; } = 1;
public abstract HitObjectType Type { get; }
public override void SetDefaultsFromBeatmap(Beatmap beatmap) public override void SetDefaultsFromBeatmap(Beatmap beatmap)
{ {
base.SetDefaultsFromBeatmap(beatmap); base.SetDefaultsFromBeatmap(beatmap);
@ -36,14 +38,12 @@ namespace osu.Game.Modes.Osu.Objects
[Flags] [Flags]
public enum HitObjectType public enum HitObjectType
{ {
Circle = 1, Circle = 1 << 0,
Slider = 2, Slider = 1 << 1,
NewCombo = 4, NewCombo = 1 << 2,
CircleNewCombo = 5, Spinner = 1 << 3,
SliderNewCombo = 6,
Spinner = 8,
ColourHax = 122, ColourHax = 122,
Hold = 128, Hold = 1 << 7,
ManiaLong = 128, SliderTick = 1 << 8,
} }
} }

View File

@ -0,0 +1,201 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using OpenTK;
using System;
using System.Diagnostics;
using System.Linq;
namespace osu.Game.Modes.Osu.Objects
{
class OsuHitObjectDifficulty
{
/// <summary>
/// Factor by how much speed / aim strain decays per second.
/// </summary>
/// <remarks>
/// These values are results of tweaking a lot and taking into account general feedback.
/// Opinionated observation: Speed is easier to maintain than accurate jumps.
/// </remarks>
internal static readonly double[] DECAY_BASE = { 0.3, 0.15 };
/// <summary>
/// Pseudo threshold values to distinguish between "singles" and "streams"
/// </summary>
/// <remarks>
/// Of course the border can not be defined clearly, therefore the algorithm has a smooth transition between those values.
/// They also are based on tweaking and general feedback.
/// </remarks>
private const double stream_spacing_threshold = 110,
single_spacing_threshold = 125;
/// <summary>
/// Scaling values for weightings to keep aim and speed difficulty in balance.
/// </summary>
/// <remarks>
/// Found from testing a very large map pool (containing all ranked maps) and keeping the average values the same.
/// </remarks>
private static readonly double[] spacing_weight_scaling = { 1400, 26.25 };
/// <summary>
/// Almost the normed diameter of a circle (104 osu pixel). That is -after- position transforming.
/// </summary>
private const double almost_diameter = 90;
internal OsuHitObject BaseHitObject;
internal double[] Strains = { 1, 1 };
internal int MaxCombo = 1;
private float scalingFactor;
private float lazySliderLength;
private Vector2 startPosition;
private Vector2 endPosition;
internal OsuHitObjectDifficulty(OsuHitObject baseHitObject)
{
BaseHitObject = baseHitObject;
float circleRadius = baseHitObject.Scale * 64;
Slider slider = BaseHitObject as Slider;
if (slider != null)
MaxCombo += slider.Ticks.Count();
// We will scale everything by this factor, so we can assume a uniform CircleSize among beatmaps.
scalingFactor = (52.0f / circleRadius);
if (circleRadius < 30)
{
float smallCircleBonus = Math.Min(30.0f - circleRadius, 5.0f) / 50.0f;
scalingFactor *= 1.0f + smallCircleBonus;
}
lazySliderLength = 0;
startPosition = baseHitObject.StackedPosition;
// Calculate approximation of lazy movement on the slider
if (slider != null)
{
float sliderFollowCircleRadius = circleRadius * 3; // Not sure if this is correct, but here we do not need 100% exact values. This comes pretty darn close in my tests.
// For simplifying this step we use actual osu! coordinates and simply scale the length, that we obtain by the ScalingFactor later
Vector2 cursorPos = startPosition;
Action<Vector2> addSliderVertex = delegate (Vector2 pos)
{
Vector2 difference = pos - cursorPos;
float distance = difference.Length;
// Did we move away too far?
if (distance > sliderFollowCircleRadius)
{
// Yep, we need to move the cursor
difference.Normalize(); // Obtain the direction of difference. We do no longer need the actual difference
distance -= sliderFollowCircleRadius;
cursorPos += difference * distance; // We move the cursor just as far as needed to stay in the follow circle
lazySliderLength += distance;
}
};
// Actual computation of the first lazy curve
foreach (var tick in slider.Ticks)
addSliderVertex(tick.StackedPosition);
addSliderVertex(baseHitObject.StackedEndPosition);
lazySliderLength *= scalingFactor;
endPosition = cursorPos;
}
// We have a normal HitCircle or a spinner
else
endPosition = startPosition;
}
internal void CalculateStrains(OsuHitObjectDifficulty previousHitObject, double timeRate)
{
calculateSpecificStrain(previousHitObject, OsuDifficultyCalculator.DifficultyType.Speed, timeRate);
calculateSpecificStrain(previousHitObject, OsuDifficultyCalculator.DifficultyType.Aim, timeRate);
}
// Caution: The subjective values are strong with this one
private static double spacingWeight(double distance, OsuDifficultyCalculator.DifficultyType type)
{
switch (type)
{
case OsuDifficultyCalculator.DifficultyType.Speed:
if (distance > single_spacing_threshold)
return 2.5;
else if (distance > stream_spacing_threshold)
return 1.6 + 0.9 * (distance - stream_spacing_threshold) / (single_spacing_threshold - stream_spacing_threshold);
else if (distance > almost_diameter)
return 1.2 + 0.4 * (distance - almost_diameter) / (stream_spacing_threshold - almost_diameter);
else if (distance > almost_diameter / 2)
return 0.95 + 0.25 * (distance - (almost_diameter / 2)) / (almost_diameter / 2);
else
return 0.95;
case OsuDifficultyCalculator.DifficultyType.Aim:
return Math.Pow(distance, 0.99);
}
Debug.Assert(false, "Invalid osu difficulty hit object type.");
return 0;
}
private void calculateSpecificStrain(OsuHitObjectDifficulty previousHitObject, OsuDifficultyCalculator.DifficultyType type, double timeRate)
{
double addition = 0;
double timeElapsed = (BaseHitObject.StartTime - previousHitObject.BaseHitObject.StartTime) / timeRate;
double decay = Math.Pow(DECAY_BASE[(int)type], timeElapsed / 1000);
if (BaseHitObject.Type == HitObjectType.Spinner)
{
// Do nothing for spinners
}
else if (BaseHitObject.Type == HitObjectType.Slider)
{
switch (type)
{
case OsuDifficultyCalculator.DifficultyType.Speed:
// For speed strain we treat the whole slider as a single spacing entity, since "Speed" is about how hard it is to click buttons fast.
// The spacing weight exists to differentiate between being able to easily alternate or having to single.
addition =
spacingWeight(previousHitObject.lazySliderLength +
DistanceTo(previousHitObject), type) *
spacing_weight_scaling[(int)type];
break;
case OsuDifficultyCalculator.DifficultyType.Aim:
// For Aim strain we treat each slider segment and the jump after the end of the slider as separate jumps, since movement-wise there is no difference
// to multiple jumps.
addition =
(
spacingWeight(previousHitObject.lazySliderLength, type) +
spacingWeight(DistanceTo(previousHitObject), type)
) *
spacing_weight_scaling[(int)type];
break;
}
}
else if (BaseHitObject.Type == HitObjectType.Circle)
{
addition = spacingWeight(DistanceTo(previousHitObject), type) * spacing_weight_scaling[(int)type];
}
// Scale addition by the time, that elapsed. Filter out HitObjects that are too close to be played anyway to avoid crazy values by division through close to zero.
// You will never find maps that require this amongst ranked maps.
addition /= Math.Max(timeElapsed, 50);
Strains[(int)type] = previousHitObject.Strains[(int)type] * decay + addition;
}
internal double DistanceTo(OsuHitObjectDifficulty other)
{
// Scale the distance by circle size.
return (startPosition - other.endPosition).Length * scalingFactor;
}
}
}

View File

@ -110,6 +110,8 @@ namespace osu.Game.Modes.Osu.Objects
} }
} }
} }
public override HitObjectType Type => HitObjectType.Slider;
} }
public enum CurveTypes public enum CurveTypes

View File

@ -5,5 +5,7 @@ namespace osu.Game.Modes.Osu.Objects
public class SliderTick : OsuHitObject public class SliderTick : OsuHitObject
{ {
public int RepeatIndex { get; set; } public int RepeatIndex { get; set; }
public override HitObjectType Type => HitObjectType.SliderTick;
} }
} }

View File

@ -10,5 +10,7 @@ namespace osu.Game.Modes.Osu.Objects
public double Length; public double Length;
public override double EndTime => StartTime + Length; public override double EndTime => StartTime + Length;
public override HitObjectType Type => HitObjectType.Spinner;
} }
} }

View File

@ -0,0 +1,193 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Beatmaps;
using osu.Game.Modes.Osu.Objects;
using System;
using System.Collections.Generic;
using osu.Game.Modes.Objects;
namespace osu.Game.Modes.Osu
{
public class OsuDifficultyCalculator : DifficultyCalculator<OsuHitObject>
{
private const double star_scaling_factor = 0.0675;
private const double extreme_scaling_factor = 0.5;
protected override PlayMode PlayMode => PlayMode.Osu;
/// <summary>
/// HitObjects are stored as a member variable.
/// </summary>
internal List<OsuHitObjectDifficulty> DifficultyHitObjects = new List<OsuHitObjectDifficulty>();
public OsuDifficultyCalculator(Beatmap beatmap) : base(beatmap)
{
}
protected override HitObjectConverter<OsuHitObject> Converter => new OsuHitObjectConverter();
protected override void PreprocessHitObjects()
{
foreach (var h in Objects)
if (h.Type == HitObjectType.Slider)
((Slider)h).Curve.Calculate();
}
protected override double ComputeDifficulty(Dictionary<String, String> categoryDifficulty)
{
// Fill our custom DifficultyHitObject class, that carries additional information
DifficultyHitObjects.Clear();
foreach (var hitObject in Objects)
DifficultyHitObjects.Add(new OsuHitObjectDifficulty(hitObject));
// Sort DifficultyHitObjects by StartTime of the HitObjects - just to make sure.
DifficultyHitObjects.Sort((a, b) => a.BaseHitObject.StartTime.CompareTo(b.BaseHitObject.StartTime));
if (!CalculateStrainValues()) return 0;
double speedDifficulty = CalculateDifficulty(DifficultyType.Speed);
double aimDifficulty = CalculateDifficulty(DifficultyType.Aim);
// OverallDifficulty is not considered in this algorithm and neither is HpDrainRate. That means, that in this form the algorithm determines how hard it physically is
// to play the map, assuming, that too much of an error will not lead to a death.
// It might be desirable to include OverallDifficulty into map difficulty, but in my personal opinion it belongs more to the weighting of the actual peformance
// and is superfluous in the beatmap difficulty rating.
// If it were to be considered, then I would look at the hit window of normal HitCircles only, since Sliders and Spinners are (almost) "free" 300s and take map length
// into account as well.
// The difficulty can be scaled by any desired metric.
// In osu!tp it gets squared to account for the rapid increase in difficulty as the limit of a human is approached. (Of course it also gets scaled afterwards.)
// It would not be suitable for a star rating, therefore:
// The following is a proposal to forge a star rating from 0 to 5. It consists of taking the square root of the difficulty, since by simply scaling the easier
// 5-star maps would end up with one star.
double speedStars = Math.Sqrt(speedDifficulty) * star_scaling_factor;
double aimStars = Math.Sqrt(aimDifficulty) * star_scaling_factor;
if (categoryDifficulty != null)
{
categoryDifficulty.Add("Aim", aimStars.ToString("0.00"));
categoryDifficulty.Add("Speed", speedStars.ToString("0.00"));
double hitWindow300 = 30/*HitObjectManager.HitWindow300*/ / TimeRate;
double preEmpt = 450/*HitObjectManager.PreEmpt*/ / TimeRate;
categoryDifficulty.Add("OD", (-(hitWindow300 - 80.0) / 6.0).ToString("0.00"));
categoryDifficulty.Add("AR", (preEmpt > 1200.0 ? -(preEmpt - 1800.0) / 120.0 : -(preEmpt - 1200.0) / 150.0 + 5.0).ToString("0.00"));
int maxCombo = 0;
foreach (OsuHitObjectDifficulty hitObject in DifficultyHitObjects)
maxCombo += hitObject.MaxCombo;
categoryDifficulty.Add("Max combo", maxCombo.ToString());
}
// Again, from own observations and from the general opinion of the community a map with high speed and low aim (or vice versa) difficulty is harder,
// than a map with mediocre difficulty in both. Therefore we can not just add both difficulties together, but will introduce a scaling that favors extremes.
double starRating = speedStars + aimStars + Math.Abs(speedStars - aimStars) * extreme_scaling_factor;
// Another approach to this would be taking Speed and Aim separately to a chosen power, which again would be equivalent. This would be more convenient if
// the hit window size is to be considered as well.
// Note: The star rating is tuned extremely tight! Airman (/b/104229) and Freedom Dive (/b/126645), two of the hardest ranked maps, both score ~4.66 stars.
// Expect the easier kind of maps that officially get 5 stars to obtain around 2 by this metric. The tutorial still scores about half a star.
// Tune by yourself as you please. ;)
return starRating;
}
protected bool CalculateStrainValues()
{
// Traverse hitObjects in pairs to calculate the strain value of NextHitObject from the strain value of CurrentHitObject and environment.
List<OsuHitObjectDifficulty>.Enumerator hitObjectsEnumerator = DifficultyHitObjects.GetEnumerator();
if (!hitObjectsEnumerator.MoveNext()) return false;
OsuHitObjectDifficulty currentHitObject = hitObjectsEnumerator.Current;
OsuHitObjectDifficulty nextHitObject;
// First hitObject starts at strain 1. 1 is the default for strain values, so we don't need to set it here. See DifficultyHitObject.
while (hitObjectsEnumerator.MoveNext())
{
nextHitObject = hitObjectsEnumerator.Current;
nextHitObject.CalculateStrains(currentHitObject, TimeRate);
currentHitObject = nextHitObject;
}
return true;
}
/// <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>
protected const double STRAIN_STEP = 400;
/// <summary>
/// The weighting of each strain value decays to this number * it's previous value
/// </summary>
protected const double DECAY_WEIGHT = 0.9;
protected double CalculateDifficulty(DifficultyType type)
{
double actualStrainStep = STRAIN_STEP * TimeRate;
// Find the highest strain value within each strain step
List<double> highestStrains = new List<double>();
double intervalEndTime = actualStrainStep;
double maximumStrain = 0; // We need to keep track of the maximum strain in the current interval
OsuHitObjectDifficulty previousHitObject = null;
foreach (OsuHitObjectDifficulty hitObject in DifficultyHitObjects)
{
// 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(OsuHitObjectDifficulty.DECAY_BASE[(int)type], (intervalEndTime - previousHitObject.BaseHitObject.StartTime) / 1000);
maximumStrain = previousHitObject.Strains[(int)type] * decay;
}
// Go to the next time interval
intervalEndTime += actualStrainStep;
}
// Obtain maximum strain
maximumStrain = Math.Max(hitObject.Strains[(int)type], 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;
}
// Those values are used as array indices. Be careful when changing them!
public enum DifficultyType : int
{
Speed = 0,
Aim,
};
}
}

View File

@ -40,6 +40,8 @@ namespace osu.Game.Modes.Osu
public override ScoreProcessor CreateScoreProcessor(int hitObjectCount) => new OsuScoreProcessor(hitObjectCount); public override ScoreProcessor CreateScoreProcessor(int hitObjectCount) => new OsuScoreProcessor(hitObjectCount);
public override DifficultyCalculator CreateDifficultyCalculator(Beatmap beatmap) => new OsuDifficultyCalculator(beatmap);
protected override PlayMode PlayMode => PlayMode.Osu; protected override PlayMode PlayMode => PlayMode.Osu;
} }
} }

View File

@ -66,9 +66,11 @@
<Compile Include="Objects\Drawables\Pieces\TrianglesPiece.cs" /> <Compile Include="Objects\Drawables\Pieces\TrianglesPiece.cs" />
<Compile Include="Objects\Drawables\Pieces\SliderBall.cs" /> <Compile Include="Objects\Drawables\Pieces\SliderBall.cs" />
<Compile Include="Objects\Drawables\Pieces\SliderBody.cs" /> <Compile Include="Objects\Drawables\Pieces\SliderBody.cs" />
<Compile Include="Objects\OsuHitObjectDifficulty.cs" />
<Compile Include="Objects\OsuHitObjectParser.cs" /> <Compile Include="Objects\OsuHitObjectParser.cs" />
<Compile Include="Objects\SliderCurve.cs" /> <Compile Include="Objects\SliderCurve.cs" />
<Compile Include="Objects\SliderTick.cs" /> <Compile Include="Objects\SliderTick.cs" />
<Compile Include="OsuDifficultyCalculator.cs" />
<Compile Include="OsuScore.cs" /> <Compile Include="OsuScore.cs" />
<Compile Include="OsuScoreProcessor.cs" /> <Compile Include="OsuScoreProcessor.cs" />
<Compile Include="UI\OsuComboCounter.cs" /> <Compile Include="UI\OsuComboCounter.cs" />

View File

@ -0,0 +1,27 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Beatmaps;
using osu.Game.Modes.Objects;
using osu.Game.Modes.Taiko.Objects;
using System;
using System.Collections.Generic;
namespace osu.Game.Modes.Taiko
{
public class TaikoDifficultyCalculator : DifficultyCalculator<TaikoBaseHit>
{
protected override PlayMode PlayMode => PlayMode.Taiko;
public TaikoDifficultyCalculator(Beatmap beatmap) : base(beatmap)
{
}
protected override HitObjectConverter<TaikoBaseHit> Converter => new TaikoConverter();
protected override double ComputeDifficulty(Dictionary<String, String> categoryDifficulty)
{
return 0;
}
}
}

View File

@ -2,10 +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 osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Modes.Objects; using osu.Game.Modes.Objects;
using osu.Game.Modes.Osu.Objects;
using osu.Game.Modes.Osu.UI; using osu.Game.Modes.Osu.UI;
using osu.Game.Modes.Taiko.UI; using osu.Game.Modes.Taiko.UI;
using osu.Game.Modes.UI; using osu.Game.Modes.UI;
@ -26,5 +24,7 @@ namespace osu.Game.Modes.Taiko
public override ScoreProcessor CreateScoreProcessor(int hitObjectCount) => null; public override ScoreProcessor CreateScoreProcessor(int hitObjectCount) => null;
public override HitObjectParser CreateHitObjectParser() => new NullHitObjectParser(); public override HitObjectParser CreateHitObjectParser() => new NullHitObjectParser();
public override DifficultyCalculator CreateDifficultyCalculator(Beatmap beatmap) => new TaikoDifficultyCalculator(beatmap);
} }
} }

View File

@ -47,6 +47,7 @@
<Reference Include="System.Xml" /> <Reference Include="System.Xml" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="TaikoDifficultyCalculator.cs" />
<Compile Include="Objects\Drawable\DrawableTaikoHit.cs" /> <Compile Include="Objects\Drawable\DrawableTaikoHit.cs" />
<Compile Include="Objects\TaikoBaseHit.cs" /> <Compile Include="Objects\TaikoBaseHit.cs" />
<Compile Include="Objects\TaikoConverter.cs" /> <Compile Include="Objects\TaikoConverter.cs" />

View File

@ -1,18 +1,15 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using System.IO; using System.IO;
using NUnit.Framework; using NUnit.Framework;
using OpenTK; using OpenTK;
using OpenTK.Graphics; using OpenTK.Graphics;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Formats; using osu.Game.Beatmaps.Formats;
using osu.Game.Beatmaps.Samples; using osu.Game.Beatmaps.Samples;
using osu.Game.Modes; using osu.Game.Modes;
using osu.Game.Modes.Osu; using osu.Game.Modes.Osu;
using osu.Game.Modes.Osu.Objects; using osu.Game.Modes.Osu.Objects;
using osu.Game.Screens.Play;
using osu.Game.Tests.Resources; using osu.Game.Tests.Resources;
namespace osu.Game.Tests.Beatmaps.Formats namespace osu.Game.Tests.Beatmaps.Formats

View File

@ -0,0 +1,50 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Modes;
using osu.Game.Modes.Objects;
using System;
using System.Collections.Generic;
namespace osu.Game.Beatmaps
{
public abstract class DifficultyCalculator
{
protected abstract PlayMode PlayMode { get; }
protected double TimeRate = 1;
protected abstract double ComputeDifficulty(Dictionary<String, String> categoryDifficulty);
private void loadTiming()
{
// TODO: Handle mods
int audioRate = 100;
TimeRate = audioRate / 100.0;
}
public double GetDifficulty(Dictionary<string, string> categoryDifficulty = null)
{
loadTiming();
double difficulty = ComputeDifficulty(categoryDifficulty);
return difficulty;
}
}
public abstract class DifficultyCalculator<T> : DifficultyCalculator where T : HitObject
{
protected List<T> Objects;
protected abstract HitObjectConverter<T> Converter { get; }
public DifficultyCalculator(Beatmap beatmap)
{
Objects = Converter.Convert(beatmap);
PreprocessHitObjects();
}
protected virtual void PreprocessHitObjects()
{
}
}
}

View File

@ -60,7 +60,7 @@ namespace osu.Game.Beatmaps.Drawables
} }
} }
public BeatmapGroup(WorkingBeatmap beatmap, BeatmapSetInfo set = null) public BeatmapGroup(WorkingBeatmap beatmap)
{ {
Header = new BeatmapSetHeader(beatmap) Header = new BeatmapSetHeader(beatmap)
{ {
@ -68,6 +68,7 @@ namespace osu.Game.Beatmaps.Drawables
RelativeSizeAxes = Axes.X, RelativeSizeAxes = Axes.X,
}; };
BeatmapSet = beatmap.BeatmapSetInfo;
BeatmapPanels = beatmap.BeatmapSetInfo.Beatmaps.Select(b => new BeatmapPanel(b) BeatmapPanels = beatmap.BeatmapSetInfo.Beatmaps.Select(b => new BeatmapPanel(b)
{ {
Alpha = 0, Alpha = 0,
@ -76,7 +77,6 @@ namespace osu.Game.Beatmaps.Drawables
RelativeSizeAxes = Axes.X, RelativeSizeAxes = Axes.X,
}).ToList(); }).ToList();
BeatmapSet = set;
Header.AddDifficultyIcons(BeatmapPanels); Header.AddDifficultyIcons(BeatmapPanels);
} }

View File

@ -1,12 +1,6 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace osu.Game.Beatmaps.Timing namespace osu.Game.Beatmaps.Timing
{ {
class TimingChange : ControlPoint class TimingChange : ControlPoint

View File

@ -68,7 +68,6 @@ namespace osu.Game.Beatmaps
beatmap = decoder?.Decode(stream); beatmap = decoder?.Decode(stream);
} }
if (WithStoryboard && beatmap != null && BeatmapSetInfo.StoryboardFile != null) if (WithStoryboard && beatmap != null && BeatmapSetInfo.StoryboardFile != null)
using (var stream = new StreamReader(reader.GetStream(BeatmapSetInfo.StoryboardFile))) using (var stream = new StreamReader(reader.GetStream(BeatmapSetInfo.StoryboardFile)))
decoder?.Decode(stream, beatmap); decoder?.Decode(stream, beatmap);

View File

@ -31,6 +31,7 @@ namespace osu.Game.Configuration
Set(OsuConfig.SnakingInSliders, true); Set(OsuConfig.SnakingInSliders, true);
Set(OsuConfig.SnakingOutSliders, false); Set(OsuConfig.SnakingOutSliders, false);
Set(OsuConfig.MenuParallax, true);
//todo: implement all settings below this line (remove the Disabled set when doing so). //todo: implement all settings below this line (remove the Disabled set when doing so).
Set(OsuConfig.MouseSpeed, 1.0).Disabled = true; Set(OsuConfig.MouseSpeed, 1.0).Disabled = true;
@ -143,7 +144,6 @@ namespace osu.Game.Configuration
Set(OsuConfig.DetectPerformanceIssues, true).Disabled = true; Set(OsuConfig.DetectPerformanceIssues, true).Disabled = true;
Set(OsuConfig.MenuMusic, true).Disabled = true; Set(OsuConfig.MenuMusic, true).Disabled = true;
Set(OsuConfig.MenuVoice, true).Disabled = true; Set(OsuConfig.MenuVoice, true).Disabled = true;
Set(OsuConfig.MenuParallax, true).Disabled = true;
Set(OsuConfig.RawInput, false).Disabled = true; Set(OsuConfig.RawInput, false).Disabled = true;
Set(OsuConfig.AbsoluteToOsuWindow, Get<bool>(OsuConfig.RawInput)).Disabled = true; Set(OsuConfig.AbsoluteToOsuWindow, Get<bool>(OsuConfig.RawInput)).Disabled = true;
Set(OsuConfig.ShowMenuTips, true).Disabled = true; Set(OsuConfig.ShowMenuTips, true).Disabled = true;

View File

@ -8,6 +8,7 @@ using osu.Game.Modes;
using osu.Game.Screens.Play; using osu.Game.Screens.Play;
using SQLite.Net.Attributes; using SQLite.Net.Attributes;
using SQLiteNetExtensions.Attributes; using SQLiteNetExtensions.Attributes;
using osu.Game.Beatmaps;
namespace osu.Game.Database namespace osu.Game.Database
{ {
@ -73,7 +74,23 @@ namespace osu.Game.Database
// Metadata // Metadata
public string Version { get; set; } public string Version { get; set; }
public float StarDifficulty => BaseDifficulty?.OverallDifficulty ?? 5; //todo: implement properly //todo: background threaded computation of this
private float starDifficulty = -1;
public float StarDifficulty
{
get
{
return (starDifficulty < 0) ? (BaseDifficulty?.OverallDifficulty ?? 5) : starDifficulty;
}
set { starDifficulty = value; }
}
internal void ComputeDifficulty(BeatmapDatabase database)
{
WorkingBeatmap wb = new WorkingBeatmap(this, BeatmapSet, database);
StarDifficulty = (float)Ruleset.GetRuleset(Mode).CreateDifficultyCalculator(wb.Beatmap).GetDifficulty();
}
public bool Equals(BeatmapInfo other) public bool Equals(BeatmapInfo other)
{ {

View File

@ -8,6 +8,8 @@ using OpenTK;
using osu.Framework; using osu.Framework;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Graphics.Transformations; using osu.Framework.Graphics.Transformations;
using osu.Game.Configuration;
using osu.Framework.Configuration;
namespace osu.Game.Graphics.Containers namespace osu.Game.Graphics.Containers
{ {
@ -15,6 +17,8 @@ namespace osu.Game.Graphics.Containers
{ {
public float ParallaxAmount = 0.02f; public float ParallaxAmount = 0.02f;
private Bindable<bool> parallaxEnabled;
public override bool Contains(Vector2 screenSpacePos) => true; public override bool Contains(Vector2 screenSpacePos) => true;
public ParallaxContainer() public ParallaxContainer()
@ -34,9 +38,18 @@ namespace osu.Game.Graphics.Containers
protected override Container<Drawable> Content => content; protected override Container<Drawable> Content => content;
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(UserInputManager input) private void load(UserInputManager input, OsuConfigManager config)
{ {
this.input = input; this.input = input;
parallaxEnabled = config.GetBindable<bool>(OsuConfig.MenuParallax);
parallaxEnabled.ValueChanged += delegate
{
if (!parallaxEnabled)
{
content.MoveTo(Vector2.Zero, firstUpdate ? 0 : 1000, EasingTypes.OutQuint);
content.Scale = new Vector2(1 + ParallaxAmount);
}
};
} }
bool firstUpdate = true; bool firstUpdate = true;
@ -45,9 +58,12 @@ namespace osu.Game.Graphics.Containers
{ {
base.Update(); base.Update();
Vector2 offset = input.CurrentState.Mouse == null ? Vector2.Zero : ToLocalSpace(input.CurrentState.Mouse.NativeState.Position) - DrawSize / 2; if (parallaxEnabled)
content.MoveTo(offset * ParallaxAmount, firstUpdate ? 0 : 1000, EasingTypes.OutQuint); {
content.Scale = new Vector2(1 + ParallaxAmount); Vector2 offset = input.CurrentState.Mouse == null ? Vector2.Zero : ToLocalSpace(input.CurrentState.Mouse.NativeState.Position) - DrawSize / 2;
content.MoveTo(offset * ParallaxAmount, firstUpdate ? 0 : 1000, EasingTypes.OutQuint);
content.Scale = new Vector2(1 + ParallaxAmount);
}
firstUpdate = false; firstUpdate = false;
} }

View File

@ -32,6 +32,8 @@ namespace osu.Game.Modes
public abstract HitObjectParser CreateHitObjectParser(); public abstract HitObjectParser CreateHitObjectParser();
public abstract DifficultyCalculator CreateDifficultyCalculator(Beatmap beatmap);
public static void Register(Ruleset ruleset) => availableRulesets.TryAdd(ruleset.PlayMode, ruleset.GetType()); public static void Register(Ruleset ruleset) => availableRulesets.TryAdd(ruleset.PlayMode, ruleset.GetType());
protected abstract PlayMode PlayMode { get; } protected abstract PlayMode PlayMode { get; }

View File

@ -59,7 +59,6 @@ namespace osu.Game.Screens.Menu
background.Preload(game); background.Preload(game);
buttons.OnSettings = game.ToggleOptions; buttons.OnSettings = game.ToggleOptions;
} }
protected override void OnEntering(Screen last) protected override void OnEntering(Screen last)

View File

@ -337,11 +337,13 @@ namespace osu.Game.Screens.Select
if (b.Metadata == null) b.Metadata = beatmapSet.Metadata; if (b.Metadata == null) b.Metadata = beatmapSet.Metadata;
}); });
foreach (var b in beatmapSet.Beatmaps)
b.ComputeDifficulty(database);
beatmapSet.Beatmaps = beatmapSet.Beatmaps.OrderBy(b => b.StarDifficulty).ToList(); beatmapSet.Beatmaps = beatmapSet.Beatmaps.OrderBy(b => b.StarDifficulty).ToList();
var beatmap = new WorkingBeatmap(beatmapSet.Beatmaps.FirstOrDefault(), beatmapSet, database); var beatmap = new WorkingBeatmap(beatmapSet.Beatmaps.FirstOrDefault(), beatmapSet, database);
var group = new BeatmapGroup(beatmap, beatmapSet) var group = new BeatmapGroup(beatmap)
{ {
SelectionChanged = selectionChanged, SelectionChanged = selectionChanged,
StartRequested = b => start() StartRequested = b => start()

View File

@ -66,6 +66,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="Beatmaps\Drawables\BeatmapBackgroundSprite.cs" /> <Compile Include="Beatmaps\Drawables\BeatmapBackgroundSprite.cs" />
<Compile Include="Beatmaps\DifficultyCalculator.cs" />
<Compile Include="Graphics\Backgrounds\Triangles.cs" /> <Compile Include="Graphics\Backgrounds\Triangles.cs" />
<Compile Include="Graphics\Cursor\CursorTrail.cs" /> <Compile Include="Graphics\Cursor\CursorTrail.cs" />
<Compile Include="Graphics\Sprites\OsuSpriteText.cs" /> <Compile Include="Graphics\Sprites\OsuSpriteText.cs" />