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

Merge branch 'master' into drawable-multiplayer-room

This commit is contained in:
Seth 2017-05-24 04:39:57 -03:00 committed by GitHub
commit 3094a8267f
94 changed files with 3677 additions and 644 deletions

@ -1 +1 @@
Subproject commit 42e26d49b9046fcb96c123b0dfb48e06d741e162
Subproject commit 777996fb9731ba1895a5ab1323cbbc97259ff741

@ -1 +1 @@
Subproject commit ffccbeb98dc9e8f0965520270b5885e63f244c83
Subproject commit 9f46a456dc3a56dcbff09671a3f588b16a464106

View File

@ -0,0 +1,199 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Collections.Generic;
using osu.Framework.Allocation;
using osu.Framework.Testing;
using osu.Game.Database;
using osu.Game.Overlays;
namespace osu.Desktop.VisualTests.Tests
{
public class TestCaseDirect : TestCase
{
public override string Description => @"osu!direct overlay";
private DirectOverlay direct;
private RulesetDatabase rulesets;
public override void Reset()
{
base.Reset();
Add(direct = new DirectOverlay());
newBeatmaps();
AddStep(@"toggle", direct.ToggleVisibility);
AddStep(@"result counts", () => direct.ResultAmounts = new DirectOverlay.ResultCounts(1, 4, 13));
}
[BackgroundDependencyLoader]
private void load(RulesetDatabase rulesets)
{
this.rulesets = rulesets;
}
private void newBeatmaps()
{
var ruleset = rulesets.GetRuleset(0);
direct.BeatmapSets = new[]
{
new BeatmapSetInfo
{
Metadata = new BeatmapMetadata
{
Title = @"OrVid",
Artist = @"An",
Author = @"RLC",
Source = @"",
},
Beatmaps = new List<BeatmapInfo>
{
new BeatmapInfo
{
Ruleset = ruleset,
StarDifficulty = 5.35f,
Metadata = new BeatmapMetadata(),
OnlineInfo = new BeatmapOnlineInfo
{
Covers = new[] { @"https://assets.ppy.sh//beatmaps/578332/covers/cover.jpg?1494591390" },
Preview = @"https://b.ppy.sh/preview/578332.mp3",
PlayCount = 97,
FavouriteCount = 72,
},
},
},
},
new BeatmapSetInfo
{
Metadata = new BeatmapMetadata
{
Title = @"tiny lamp",
Artist = @"fhana",
Author = @"Sotarks",
Source = @"ぎんぎつね",
},
Beatmaps = new List<BeatmapInfo>
{
new BeatmapInfo
{
Ruleset = ruleset,
StarDifficulty = 5.81f,
Metadata = new BeatmapMetadata(),
OnlineInfo = new BeatmapOnlineInfo
{
Covers = new[] { @"https://assets.ppy.sh//beatmaps/599627/covers/cover.jpg?1494539318" },
Preview = @"https//b.ppy.sh/preview/599627.mp3",
PlayCount = 3082,
FavouriteCount = 14,
},
},
},
},
new BeatmapSetInfo
{
Metadata = new BeatmapMetadata
{
Title = @"At Gwanghwamun",
Artist = @"KYUHYUN",
Author = @"Cerulean Veyron",
Source = @"",
},
Beatmaps = new List<BeatmapInfo>
{
new BeatmapInfo
{
Ruleset = ruleset,
StarDifficulty = 0.9f,
Metadata = new BeatmapMetadata(),
OnlineInfo = new BeatmapOnlineInfo
{
Covers = new[] { @"https://assets.ppy.sh//beatmaps/513268/covers/cover.jpg?1494502863" },
Preview = @"https//b.ppy.sh/preview/513268.mp3",
PlayCount = 2762,
FavouriteCount = 15,
},
},
new BeatmapInfo
{
Ruleset = ruleset,
StarDifficulty = 1.1f,
},
new BeatmapInfo
{
Ruleset = ruleset,
StarDifficulty = 2.02f,
},
new BeatmapInfo
{
Ruleset = ruleset,
StarDifficulty = 3.49f,
},
},
},
new BeatmapSetInfo
{
Metadata = new BeatmapMetadata
{
Title = @"RHAPSODY OF BLUE SKY",
Artist = @"fhana",
Author = @"[Kamiya]",
Source = @"小林さんちのメイドラゴン",
},
Beatmaps = new List<BeatmapInfo>
{
new BeatmapInfo
{
Ruleset = ruleset,
StarDifficulty = 1.26f,
Metadata = new BeatmapMetadata(),
OnlineInfo = new BeatmapOnlineInfo
{
Covers = new[] { @"https://assets.ppy.sh//beatmaps/586841/covers/cover.jpg?1494052741" },
Preview = @"https//b.ppy.sh/preview/586841.mp3",
PlayCount = 62317,
FavouriteCount = 161,
},
},
new BeatmapInfo
{
Ruleset = ruleset,
StarDifficulty = 2.01f,
},
new BeatmapInfo
{
Ruleset = ruleset,
StarDifficulty = 2.87f,
},
new BeatmapInfo
{
Ruleset = ruleset,
StarDifficulty = 3.76f,
},
new BeatmapInfo
{
Ruleset = ruleset,
StarDifficulty = 3.93f,
},
new BeatmapInfo
{
Ruleset = ruleset,
StarDifficulty = 4.37f,
},
new BeatmapInfo
{
Ruleset = ruleset,
StarDifficulty = 5.13f,
},
new BeatmapInfo
{
Ruleset = ruleset,
StarDifficulty = 5.42f,
},
},
},
};
}
}
}

View File

@ -18,7 +18,7 @@ using osu.Game.Rulesets.Taiko.UI;
using System.Collections.Generic;
using osu.Desktop.VisualTests.Beatmaps;
using osu.Framework.Allocation;
using osu.Game.Beatmaps.Timing;
using osu.Game.Beatmaps.ControlPoints;
namespace osu.Desktop.VisualTests.Tests
{
@ -53,8 +53,8 @@ namespace osu.Desktop.VisualTests.Tests
time += RNG.Next(50, 500);
}
TimingInfo timing = new TimingInfo();
timing.ControlPoints.Add(new ControlPoint
var controlPointInfo = new ControlPointInfo();
controlPointInfo.TimingPoints.Add(new TimingControlPoint
{
BeatLength = 200
});
@ -73,7 +73,7 @@ namespace osu.Desktop.VisualTests.Tests
Author = @"peppy",
},
},
TimingInfo = timing
ControlPointInfo = controlPointInfo
});
Add(new Drawable[]

View File

@ -7,10 +7,10 @@ using osu.Framework.Graphics;
using osu.Game.Rulesets.Mania.UI;
using System;
using System.Collections.Generic;
using osu.Game.Beatmaps.Timing;
using OpenTK;
using osu.Game.Rulesets.Mania.Objects.Drawables;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.Timing;
namespace osu.Desktop.VisualTests.Tests
{
@ -27,7 +27,7 @@ namespace osu.Desktop.VisualTests.Tests
Action<int, SpecialColumnPosition> createPlayfield = (cols, pos) =>
{
Clear();
Add(new ManiaPlayfield(cols, new List<ControlPoint>())
Add(new ManiaPlayfield(cols, new List<TimingChange>())
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
@ -41,7 +41,7 @@ namespace osu.Desktop.VisualTests.Tests
Clear();
ManiaPlayfield playField;
Add(playField = new ManiaPlayfield(cols, new List<ControlPoint> { new ControlPoint { BeatLength = 200 } })
Add(playField = new ManiaPlayfield(cols, new List<TimingChange> { new TimingChange { BeatLength = 200 } })
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,

View File

@ -0,0 +1,57 @@
// 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.Framework.Testing;
using osu.Framework.Graphics;
using osu.Game.Users;
using osu.Framework.Graphics.Containers;
using OpenTK;
namespace osu.Desktop.VisualTests.Tests
{
internal class TestCaseUserPanel : TestCase
{
public override string Description => @"Panels for displaying a user's status";
public override void Reset()
{
base.Reset();
UserPanel flyte;
UserPanel peppy;
Add(new FillFlowContainer
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
AutoSizeAxes = Axes.Both,
Spacing = new Vector2(10f),
Children = new[]
{
flyte = new UserPanel(new User
{
Username = @"flyte",
Id = 3103765,
Country = new Country { FlagName = @"JP" },
CoverUrl = @"https://assets.ppy.sh/user-profile-covers/3103765/5b012e13611d5761caa7e24fecb3d3a16e1cf48fc2a3032cfd43dd444af83d82.jpeg"
}) { Width = 300 },
peppy = new UserPanel(new User
{
Username = @"peppy",
Id = 2,
Country = new Country { FlagName = @"AU" },
CoverUrl = @"https://assets.ppy.sh/user-profile-covers/2/08cad88747c235a64fca5f1b770e100f120827ded1ffe3b66bfcd19c940afa65.jpeg"
}) { Width = 300 },
},
});
flyte.Status.Value = new UserStatusOnline();
peppy.Status.Value = new UserStatusSoloGame();
AddStep(@"spectating", () => { flyte.Status.Value = new UserStatusSpectating(); });
AddStep(@"multiplaying", () => { flyte.Status.Value = new UserStatusMultiplayerGame(); });
AddStep(@"modding", () => { flyte.Status.Value = new UserStatusModding(); });
AddStep(@"offline", () => { flyte.Status.Value = new UserStatusOffline(); });
AddStep(@"null status", () => { flyte.Status.Value = null; });
}
}
}

View File

@ -221,6 +221,8 @@
<Compile Include="Beatmaps\TestWorkingBeatmap.cs" />
<Compile Include="Tests\TestCaseBeatmapDetailArea.cs" />
<Compile Include="Tests\TestCaseDrawableRoom.cs" />
<Compile Include="Tests\TestCaseUserPanel.cs" />
<Compile Include="Tests\TestCaseDirect.cs" />
</ItemGroup>
<ItemGroup />
<ItemGroup />

View File

@ -37,5 +37,7 @@ namespace osu.Desktop.Beatmaps.IO
{
// no-op
}
public override Stream GetUnderlyingStream() => null;
}
}

View File

@ -12,11 +12,17 @@ using osu.Game.Rulesets.Mania.Beatmaps.Patterns;
using osu.Game.Rulesets.Mania.MathUtils;
using osu.Game.Database;
using osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy;
using OpenTK;
namespace osu.Game.Rulesets.Mania.Beatmaps
{
public class ManiaBeatmapConverter : BeatmapConverter<ManiaHitObject>
{
/// <summary>
/// Maximum number of previous notes to consider for density calculation.
/// </summary>
private const int max_notes_for_density = 7;
protected override IEnumerable<Type> ValidConversionTypes { get; } = new[] { typeof(IHasXPosition) };
private Pattern lastPattern = new Pattern();
@ -56,6 +62,26 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
yield return obj;
}
private readonly List<double> prevNoteTimes = new List<double>(max_notes_for_density);
private double density = int.MaxValue;
private void computeDensity(double newNoteTime)
{
if (prevNoteTimes.Count == max_notes_for_density)
prevNoteTimes.RemoveAt(0);
prevNoteTimes.Add(newNoteTime);
density = (prevNoteTimes[prevNoteTimes.Count - 1] - prevNoteTimes[0]) / prevNoteTimes.Count;
}
private double lastTime;
private Vector2 lastPosition;
private PatternType lastStair;
private void recordNote(double time, Vector2 position)
{
lastTime = time;
lastPosition = position;
}
/// <summary>
/// Method that generates hit objects for osu!mania specific beatmaps.
/// </summary>
@ -92,7 +118,11 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
conversion = new EndTimeObjectPatternGenerator(random, original, beatmap);
else if (positionData != null)
{
// Circle
computeDensity(original.StartTime);
conversion = new HitObjectPatternGenerator(random, original, beatmap, lastPattern, lastTime, lastPosition, density, lastStair);
recordNote(original.StartTime, positionData.Position);
}
if (conversion == null)
@ -101,6 +131,9 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
Pattern newPattern = conversion.Generate();
lastPattern = newPattern;
var stairPatternGenerator = (HitObjectPatternGenerator)conversion;
lastStair = stairPatternGenerator.StairType;
return newPattern.HitObjects;
}

View File

@ -5,11 +5,11 @@ using System;
using System.Linq;
using osu.Game.Audio;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Timing;
using osu.Game.Rulesets.Mania.MathUtils;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Beatmaps.ControlPoints;
namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
{
@ -32,11 +32,8 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
public DistanceObjectPatternGenerator(FastRandom random, HitObject hitObject, Beatmap beatmap, Pattern previousPattern)
: base(random, hitObject, beatmap, previousPattern)
{
ControlPoint overridePoint;
ControlPoint controlPoint = Beatmap.TimingInfo.TimingPointAt(hitObject.StartTime, out overridePoint);
convertType = PatternType.None;
if ((overridePoint ?? controlPoint)?.KiaiMode == false)
if (Beatmap.ControlPointInfo.EffectPointAt(hitObject.StartTime).KiaiMode)
convertType = PatternType.LowProbability;
var distanceData = hitObject as IHasDistance;
@ -44,13 +41,13 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
repeatCount = repeatsData?.RepeatCount ?? 1;
double speedAdjustment = beatmap.TimingInfo.SpeedMultiplierAt(hitObject.StartTime);
double speedAdjustedBeatLength = beatmap.TimingInfo.BeatLengthAt(hitObject.StartTime) * speedAdjustment;
TimingControlPoint timingPoint = beatmap.ControlPointInfo.TimingPointAt(hitObject.StartTime);
DifficultyControlPoint difficultyPoint = beatmap.ControlPointInfo.DifficultyPointAt(hitObject.StartTime);
// The true distance, accounting for any repeats
double distance = (distanceData?.Distance ?? 0) * repeatCount;
// The velocity of the osu! hit object - calculated as the velocity of a slider
double osuVelocity = osu_base_scoring_distance * beatmap.BeatmapInfo.Difficulty.SliderMultiplier / speedAdjustedBeatLength;
double osuVelocity = osu_base_scoring_distance * beatmap.BeatmapInfo.Difficulty.SliderMultiplier / (timingPoint.BeatLength * difficultyPoint.SpeedMultiplier);
// The duration of the osu! hit object
double osuDuration = distance / osuVelocity;

View File

@ -0,0 +1,404 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using System.Linq;
using OpenTK;
using osu.Game.Audio;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Mania.MathUtils;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
{
internal class HitObjectPatternGenerator : PatternGenerator
{
public PatternType StairType { get; private set; }
private readonly PatternType convertType;
public HitObjectPatternGenerator(FastRandom random, HitObject hitObject, Beatmap beatmap, Pattern previousPattern, double previousTime, Vector2 previousPosition, double density, PatternType lastStair)
: base(random, hitObject, beatmap, previousPattern)
{
StairType = lastStair;
TimingControlPoint timingPoint = beatmap.ControlPointInfo.TimingPointAt(hitObject.StartTime);
EffectControlPoint effectPoint = beatmap.ControlPointInfo.EffectPointAt(hitObject.StartTime);
var positionData = hitObject as IHasPosition;
float positionSeparation = ((positionData?.Position ?? Vector2.Zero) - previousPosition).Length;
double timeSeparation = hitObject.StartTime - previousTime;
if (timeSeparation <= 125)
{
// More than 120 BPM
convertType |= PatternType.ForceNotStack;
}
if (timeSeparation <= 80)
{
// More than 187 BPM
convertType |= PatternType.ForceNotStack | PatternType.KeepSingle;
}
else if (timeSeparation <= 95)
{
// More than 157 BPM
convertType |= PatternType.ForceNotStack | PatternType.KeepSingle | lastStair;
}
else if (timeSeparation <= 105)
{
// More than 140 BPM
convertType |= PatternType.ForceNotStack | PatternType.LowProbability;
}
else if (timeSeparation <= 125)
{
// More than 120 BPM
convertType |= PatternType.ForceNotStack;
}
else if (timeSeparation <= 135 && positionSeparation < 20)
{
// More than 111 BPM stream
convertType |= PatternType.Cycle | PatternType.KeepSingle;
}
else if (timeSeparation <= 150 & positionSeparation < 20)
{
// More than 100 BPM stream
convertType |= PatternType.ForceStack | PatternType.LowProbability;
}
else if (positionSeparation < 20 && density >= timingPoint.BeatLength / 2.5)
{
// Low density stream
convertType |= PatternType.Reverse | PatternType.LowProbability;
}
else if (density < timingPoint.BeatLength / 2.5 || effectPoint.KiaiMode)
{
// High density
}
else
convertType |= PatternType.LowProbability;
}
public override Pattern Generate()
{
int lastColumn = PreviousPattern.HitObjects.FirstOrDefault()?.Column ?? 0;
if ((convertType & PatternType.Reverse) > 0 && PreviousPattern.HitObjects.Any())
{
// Generate a new pattern by copying the last hit objects in reverse-column order
var pattern = new Pattern();
for (int i = RandomStart; i < AvailableColumns; i++)
if (PreviousPattern.ColumnHasObject(i))
addToPattern(pattern, RandomStart + AvailableColumns - i - 1);
return pattern;
}
if ((convertType & PatternType.Cycle) > 0 && PreviousPattern.HitObjects.Count() == 1
// If we convert to 7K + 1, let's not overload the special key
&& (AvailableColumns != 8 || lastColumn != 0)
// Make sure the last column was not the centre column
&& (AvailableColumns % 2 == 0 || lastColumn != AvailableColumns / 2))
{
// Generate a new pattern by cycling backwards (similar to Reverse but for only one hit object)
var pattern = new Pattern();
int column = RandomStart + AvailableColumns - lastColumn - 1;
addToPattern(pattern, column);
return pattern;
}
if ((convertType & PatternType.ForceStack) > 0 && PreviousPattern.HitObjects.Any())
{
// Generate a new pattern by placing on the already filled columns
var pattern = new Pattern();
for (int i = RandomStart; i < AvailableColumns; i++)
if (PreviousPattern.ColumnHasObject(i))
addToPattern(pattern, i);
return pattern;
}
if ((convertType & PatternType.Stair) > 0 && PreviousPattern.HitObjects.Count() == 1)
{
// Generate a new pattern by placing on the next column, cycling back to the start if there is no "next"
var pattern = new Pattern();
int targetColumn = lastColumn + 1;
if (targetColumn == AvailableColumns)
{
targetColumn = RandomStart;
StairType = PatternType.ReverseStair;
}
addToPattern(pattern, targetColumn);
return pattern;
}
if ((convertType & PatternType.ReverseStair) > 0 && PreviousPattern.HitObjects.Count() == 1)
{
// Generate a new pattern by placing on the previous column, cycling back to the end if there is no "previous"
var pattern = new Pattern();
int targetColumn = lastColumn - 1;
if (targetColumn == RandomStart - 1)
{
targetColumn = AvailableColumns - 1;
StairType = PatternType.Stair;
}
addToPattern(pattern, targetColumn);
return pattern;
}
if ((convertType & PatternType.KeepSingle) > 0)
return generateRandomNotes(1);
if ((convertType & PatternType.Mirror) > 0)
{
if (ConversionDifficulty > 6.5)
return generateRandomPatternWithMirrored(0.12, 0.38, 0.12);
if (ConversionDifficulty > 4)
return generateRandomPatternWithMirrored(0.12, 0.17, 0);
return generateRandomPatternWithMirrored(0.12, 0, 0);
}
if (ConversionDifficulty > 6.5)
{
if ((convertType & PatternType.LowProbability) > 0)
return generateRandomPattern(0.78, 0.42, 0, 0);
return generateRandomPattern(1, 0.62, 0, 0);
}
if (ConversionDifficulty > 4)
{
if ((convertType & PatternType.LowProbability) > 0)
return generateRandomPattern(0.35, 0.08, 0, 0);
return generateRandomPattern(0.52, 0.15, 0, 0);
}
if (ConversionDifficulty > 2)
{
if ((convertType & PatternType.LowProbability) > 0)
return generateRandomPattern(0.18, 0, 0, 0);
return generateRandomPattern(0.45, 0, 0, 0);
}
return generateRandomPattern(0, 0, 0, 0);
}
/// <summary>
/// Generates random notes.
/// <para>
/// This will generate as many as it can up to <paramref name="noteCount"/>, accounting for
/// any stacks if <see cref="convertType"/> is forcing no stacks.
/// </para>
/// </summary>
/// <param name="noteCount">The amount of notes to generate.</param>
/// <returns>The <see cref="Pattern"/> containing the hit objects.</returns>
private Pattern generateRandomNotes(int noteCount)
{
var pattern = new Pattern();
bool allowStacking = (convertType & PatternType.ForceNotStack) == 0;
if (!allowStacking)
noteCount = Math.Min(noteCount, AvailableColumns - RandomStart - PreviousPattern.ColumnWithObjects);
int nextColumn = GetColumn((HitObject as IHasXPosition)?.X ?? 0, true);
for (int i = 0; i < noteCount; i++)
{
while (pattern.ColumnHasObject(nextColumn) || PreviousPattern.ColumnHasObject(nextColumn) && !allowStacking)
{
if ((convertType & PatternType.Gathered) > 0)
{
nextColumn++;
if (nextColumn == AvailableColumns)
nextColumn = RandomStart;
}
else
nextColumn = Random.Next(RandomStart, AvailableColumns);
}
addToPattern(pattern, nextColumn);
}
return pattern;
}
/// <summary>
/// Whether this hit object can generate a note in the special column.
/// </summary>
private bool hasSpecialColumn => HitObject.Samples.Any(s => s.Name == SampleInfo.HIT_CLAP) && HitObject.Samples.Any(s => s.Name == SampleInfo.HIT_FINISH);
/// <summary>
/// Generates a random pattern.
/// </summary>
/// <param name="p2">Probability for 2 notes to be generated.</param>
/// <param name="p3">Probability for 3 notes to be generated.</param>
/// <param name="p4">Probability for 4 notes to be generated.</param>
/// <param name="p5">Probability for 5 notes to be generated.</param>
/// <returns>The <see cref="Pattern"/> containing the hit objects.</returns>
private Pattern generateRandomPattern(double p2, double p3, double p4, double p5)
{
var pattern = new Pattern();
pattern.Add(generateRandomNotes(getRandomNoteCount(p2, p3, p4, p5)));
if (RandomStart > 0 && hasSpecialColumn)
addToPattern(pattern, 0);
return pattern;
}
/// <summary>
/// Generates a random pattern which has both normal and mirrored notes.
/// </summary>
/// <param name="centreProbability">The probability for a note to be added to the centre column.</param>
/// <param name="p2">Probability for 2 notes to be generated.</param>
/// <param name="p3">Probability for 3 notes to be generated.</param>
/// <returns>The <see cref="Pattern"/> containing the hit objects.</returns>
private Pattern generateRandomPatternWithMirrored(double centreProbability, double p2, double p3)
{
var pattern = new Pattern();
bool addToCentre;
int noteCount = getRandomNoteCountMirrored(centreProbability, p2, p3, out addToCentre);
int columnLimit = (AvailableColumns % 2 == 0 ? AvailableColumns : AvailableColumns - 1) / 2;
int nextColumn = Random.Next(RandomStart, columnLimit);
for (int i = 0; i < noteCount; i++)
{
while (pattern.ColumnHasObject(nextColumn))
nextColumn = Random.Next(RandomStart, columnLimit);
// Add normal note
addToPattern(pattern, nextColumn);
// Add mirrored note
addToPattern(pattern, RandomStart + AvailableColumns - nextColumn - 1);
}
if (addToCentre)
addToPattern(pattern, AvailableColumns / 2);
if (RandomStart > 0 && hasSpecialColumn)
addToPattern(pattern, 0);
return pattern;
}
/// <summary>
/// Generates a count of notes to be generated from a list of probabilities.
/// </summary>
/// <param name="p2">Probability for 2 notes to be generated.</param>
/// <param name="p3">Probability for 3 notes to be generated.</param>
/// <param name="p4">Probability for 4 notes to be generated.</param>
/// <param name="p5">Probability for 5 notes to be generated.</param>
/// <returns>The amount of notes to be generated.</returns>
private int getRandomNoteCount(double p2, double p3, double p4, double p5)
{
switch (AvailableColumns)
{
case 2:
p2 = 0;
p3 = 0;
p4 = 0;
p5 = 0;
break;
case 3:
p2 = Math.Max(p2, 0.1);
p3 = 0;
p4 = 0;
p5 = 0;
break;
case 4:
p2 = Math.Max(p2, 0.23);
p3 = Math.Max(p3, 0.04);
p4 = 0;
p5 = 0;
break;
case 5:
p3 = Math.Max(p3, 0.15);
p4 = Math.Max(p4, 0.03);
p5 = 0;
break;
}
if (HitObject.Samples.Any(s => s.Name == SampleInfo.HIT_CLAP))
p2 = 1;
return GetRandomNoteCount(p2, p3, p4, p5);
}
/// <summary>
/// Generates a count of notes to be generated from a list of probabilities.
/// </summary>
/// <param name="centreProbability">The probability for a note to be added to the centre column.</param>
/// <param name="p2">Probability for 2 notes to be generated.</param>
/// <param name="p3">Probability for 3 notes to be generated.</param>
/// <param name="addToCentre">Whether to add a note to the centre column.</param>
/// <returns>The amount of notes to be generated. The note to be added to the centre column will NOT be part of this count.</returns>
private int getRandomNoteCountMirrored(double centreProbability, double p2, double p3, out bool addToCentre)
{
addToCentre = false;
if ((convertType & PatternType.ForceNotStack) > 0)
return getRandomNoteCount(p2 / 2, p2, (p2 + p3) / 2, p3);
switch (AvailableColumns)
{
case 2:
centreProbability = 0;
p2 = 0;
p3 = 0;
break;
case 3:
centreProbability = Math.Max(centreProbability, 0.03);
p2 = Math.Max(p2, 0.1);
p3 = 0;
break;
case 4:
centreProbability = 0;
p2 = Math.Max(p2 * 2, 0.2);
p3 = 0;
break;
case 5:
centreProbability = Math.Max(centreProbability, 0.03);
p3 = 0;
break;
case 6:
centreProbability = 0;
p2 = Math.Max(p2 * 2, 0.5);
p3 = Math.Max(p3 * 2, 0.15);
break;
}
double centreVal = Random.NextDouble();
int noteCount = GetRandomNoteCount(p2, p3);
addToCentre = AvailableColumns % 2 != 0 && noteCount != 3 && centreVal > 1 - centreProbability;
return noteCount;
}
/// <summary>
/// Constructs and adds a note to a pattern.
/// </summary>
/// <param name="pattern">The pattern to add to.</param>
/// <param name="column">The column to add the note to.</param>
private void addToPattern(Pattern pattern, int column)
{
pattern.Add(new Note
{
StartTime = HitObject.StartTime,
Samples = HitObject.Samples,
Column = column
});
}
}
}

View File

@ -140,6 +140,26 @@ namespace osu.Game.Rulesets.Mania.Judgements
Miss = BeatmapDifficulty.DifficultyRange(difficulty, miss_max, miss_mid, miss_min);
}
/// <summary>
/// Retrieves the hit result for a time offset.
/// </summary>
/// <param name="hitOffset">The time offset.</param>
/// <returns>The hit result, or null if the time offset results in a miss.</returns>
public ManiaHitResult? ResultFor(double hitOffset)
{
if (hitOffset <= Perfect / 2)
return ManiaHitResult.Perfect;
if (hitOffset <= Great / 2)
return ManiaHitResult.Great;
if (hitOffset <= Good / 2)
return ManiaHitResult.Good;
if (hitOffset <= Ok / 2)
return ManiaHitResult.Ok;
if (hitOffset <= Bad / 2)
return ManiaHitResult.Bad;
return null;
}
/// <summary>
/// Constructs new hit windows which have been multiplied by a value.
/// </summary>

View File

@ -0,0 +1,21 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.ComponentModel;
namespace osu.Game.Rulesets.Mania.Judgements
{
public enum ManiaHitResult
{
[Description("PERFECT")]
Perfect,
[Description("GREAT")]
Great,
[Description("GOOD")]
Good,
[Description("OK")]
Ok,
[Description("BAD")]
Bad
}
}

View File

@ -10,5 +10,10 @@ namespace osu.Game.Rulesets.Mania.Judgements
public override string ResultString => string.Empty;
public override string MaxResultString => string.Empty;
/// <summary>
/// The hit result.
/// </summary>
public ManiaHitResult ManiaResult;
}
}

View File

@ -5,6 +5,8 @@ using osu.Game.Rulesets.Objects.Drawables;
using osu.Framework.Graphics;
using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces;
using OpenTK.Graphics;
using osu.Framework.Configuration;
using OpenTK.Input;
namespace osu.Game.Rulesets.Mania.Objects.Drawables
{
@ -14,8 +16,8 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
private readonly BodyPiece bodyPiece;
private readonly NotePiece tailPiece;
public DrawableHoldNote(HoldNote hitObject)
: base(hitObject)
public DrawableHoldNote(HoldNote hitObject, Bindable<Key> key = null)
: base(hitObject, key)
{
RelativeSizeAxes = Axes.Both;
Height = (float)HitObject.Duration;

View File

@ -2,6 +2,8 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using OpenTK.Graphics;
using OpenTK.Input;
using osu.Framework.Configuration;
using osu.Framework.Graphics;
using osu.Game.Rulesets.Mania.Judgements;
using osu.Game.Rulesets.Objects.Drawables;
@ -11,13 +13,21 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
public abstract class DrawableManiaHitObject<TObject> : DrawableHitObject<ManiaHitObject, ManiaJudgement>
where TObject : ManiaHitObject
{
/// <summary>
/// The key that will trigger input for this hit object.
/// </summary>
protected Bindable<Key> Key { get; private set; } = new Bindable<Key>();
public new TObject HitObject;
protected DrawableManiaHitObject(TObject hitObject)
protected DrawableManiaHitObject(TObject hitObject, Bindable<Key> key = null)
: base(hitObject)
{
HitObject = hitObject;
if (key != null)
Key.BindTo(key);
RelativePositionAxes = Axes.Y;
Y = (float)HitObject.StartTime;
}

View File

@ -1,8 +1,13 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using OpenTK.Graphics;
using OpenTK.Input;
using osu.Framework.Configuration;
using osu.Framework.Graphics;
using osu.Framework.Input;
using osu.Game.Rulesets.Mania.Judgements;
using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces;
using osu.Game.Rulesets.Objects.Drawables;
@ -12,8 +17,8 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
{
private readonly NotePiece headPiece;
public DrawableNote(Note hitObject)
: base(hitObject)
public DrawableNote(Note hitObject, Bindable<Key> key = null)
: base(hitObject, key)
{
RelativeSizeAxes = Axes.Both;
Height = 100;
@ -38,14 +43,53 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
}
}
protected override void Update()
protected override void CheckJudgement(bool userTriggered)
{
if (Time.Current > HitObject.StartTime)
Colour = Color4.Green;
if (!userTriggered)
{
if (Judgement.TimeOffset > HitObject.HitWindows.Bad / 2)
Judgement.Result = HitResult.Miss;
return;
}
double offset = Math.Abs(Judgement.TimeOffset);
if (offset > HitObject.HitWindows.Miss / 2)
return;
ManiaHitResult? tmpResult = HitObject.HitWindows.ResultFor(offset);
if (tmpResult.HasValue)
{
Judgement.Result = HitResult.Hit;
Judgement.ManiaResult = tmpResult.Value;
}
else
Judgement.Result = HitResult.Miss;
}
protected override void UpdateState(ArmedState state)
{
switch (State)
{
case ArmedState.Hit:
Colour = Color4.Green;
break;
}
}
protected override bool OnKeyDown(InputState state, KeyDownEventArgs args)
{
if (Judgement.Result != HitResult.None)
return false;
if (args.Key != Key)
return false;
if (args.Repeat)
return false;
return UpdateJudgement(true);
}
}
}

View File

@ -2,7 +2,7 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Audio;
using osu.Game.Beatmaps.Timing;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Database;
using osu.Game.Rulesets.Mania.Judgements;
using osu.Game.Rulesets.Objects.Types;
@ -31,11 +31,11 @@ namespace osu.Game.Rulesets.Mania.Objects
/// <summary>
/// The key-release hit windows for this hold note.
/// </summary>
protected HitWindows ReleaseHitWindows = new HitWindows();
public HitWindows ReleaseHitWindows { get; protected set; } = new HitWindows();
public override void ApplyDefaults(TimingInfo timing, BeatmapDifficulty difficulty)
public override void ApplyDefaults(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty)
{
base.ApplyDefaults(timing, difficulty);
base.ApplyDefaults(controlPointInfo, difficulty);
ReleaseHitWindows = HitWindows * release_window_lenience;
}

View File

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

View File

@ -22,5 +22,12 @@ namespace osu.Game.Rulesets.Mania.Scoring
protected override void OnNewJudgement(ManiaJudgement judgement)
{
}
protected override void Reset()
{
base.Reset();
Health.Value = 1;
}
}
}

View File

@ -7,7 +7,7 @@ using System.Linq;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using OpenTK;
using osu.Game.Beatmaps.Timing;
using osu.Game.Beatmaps.ControlPoints;
namespace osu.Game.Rulesets.Mania.Timing
{
@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Mania.Timing
private readonly List<DrawableControlPoint> drawableControlPoints;
public ControlPointContainer(IEnumerable<ControlPoint> timingChanges)
public ControlPointContainer(IEnumerable<TimingChange> timingChanges)
{
drawableControlPoints = timingChanges.Select(t => new DrawableControlPoint(t)).ToList();
Children = drawableControlPoints;
@ -64,7 +64,7 @@ namespace osu.Game.Rulesets.Mania.Timing
/// </summary>
private class DrawableControlPoint : Container
{
private readonly ControlPoint timingChange;
private readonly TimingChange timingChange;
protected override Container<Drawable> Content => content;
private readonly Container content;
@ -76,7 +76,7 @@ namespace osu.Game.Rulesets.Mania.Timing
/// the content container will scroll at twice the normal rate.
/// </summary>
/// <param name="timingChange">The control point to create the drawable control point for.</param>
public DrawableControlPoint(ControlPoint timingChange)
public DrawableControlPoint(TimingChange timingChange)
{
this.timingChange = timingChange;

View File

@ -0,0 +1,23 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
namespace osu.Game.Rulesets.Mania.Timing
{
public class TimingChange
{
/// <summary>
/// The time at which this timing change happened.
/// </summary>
public double Time;
/// <summary>
/// The beat length.
/// </summary>
public double BeatLength = 500;
/// <summary>
/// The speed multiplier.
/// </summary>
public double SpeedMultiplier = 1;
}
}

View File

@ -17,7 +17,8 @@ using System.Collections.Generic;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.Judgements;
using osu.Game.Beatmaps.Timing;
using System;
using osu.Framework.Configuration;
namespace osu.Game.Rulesets.Mania.UI
{
@ -33,7 +34,10 @@ namespace osu.Game.Rulesets.Mania.UI
private const float column_width = 45;
private const float special_column_width = 70;
public Key Key;
/// <summary>
/// The key that will trigger input actions for this column and hit objects contained inside it.
/// </summary>
public Bindable<Key> Key = new Bindable<Key>();
private readonly Box background;
private readonly Container hitTargetBar;
@ -41,7 +45,7 @@ namespace osu.Game.Rulesets.Mania.UI
public readonly ControlPointContainer ControlPointContainer;
public Column(IEnumerable<ControlPoint> timingChanges)
public Column(IEnumerable<TimingChange> timingChanges)
{
RelativeSizeAxes = Axes.Y;
Width = column_width;
@ -95,6 +99,12 @@ namespace osu.Game.Rulesets.Mania.UI
Name = "Hit objects",
RelativeSizeAxes = Axes.Both,
},
// For column lighting, we need to capture input events before the notes
new InputTarget
{
KeyDown = onKeyDown,
KeyUp = onKeyUp
}
}
},
new Container
@ -178,12 +188,9 @@ namespace osu.Game.Rulesets.Mania.UI
}
}
public void Add(DrawableHitObject<ManiaHitObject, ManiaJudgement> hitObject)
{
ControlPointContainer.Add(hitObject);
}
public void Add(DrawableHitObject<ManiaHitObject, ManiaJudgement> hitObject) => ControlPointContainer.Add(hitObject);
protected override bool OnKeyDown(InputState state, KeyDownEventArgs args)
private bool onKeyDown(InputState state, KeyDownEventArgs args)
{
if (args.Repeat)
return false;
@ -197,7 +204,7 @@ namespace osu.Game.Rulesets.Mania.UI
return false;
}
protected override bool OnKeyUp(InputState state, KeyUpEventArgs args)
private bool onKeyUp(InputState state, KeyUpEventArgs args)
{
if (args.Key == Key)
{
@ -207,5 +214,24 @@ namespace osu.Game.Rulesets.Mania.UI
return false;
}
/// <summary>
/// This is a simple container which delegates various input events that have to be captured before the notes.
/// </summary>
private class InputTarget : Container
{
public Func<InputState, KeyDownEventArgs, bool> KeyDown;
public Func<InputState, KeyUpEventArgs, bool> KeyUp;
public InputTarget()
{
RelativeSizeAxes = Axes.Both;
AlwaysPresent = true;
Alpha = 0;
}
protected override bool OnKeyDown(InputState state, KeyDownEventArgs args) => KeyDown?.Invoke(state, args) ?? false;
protected override bool OnKeyUp(InputState state, KeyUpEventArgs args) => KeyUp?.Invoke(state, args) ?? false;
}
}
}

View File

@ -2,17 +2,22 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using System.Collections.Generic;
using System.Linq;
using OpenTK;
using OpenTK.Input;
using osu.Framework.Configuration;
using osu.Framework.Graphics;
using osu.Framework.Lists;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Timing;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Beatmaps;
using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Mania.Judgements;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.Objects.Drawables;
using osu.Game.Rulesets.Mania.Scoring;
using osu.Game.Rulesets.Mania.Timing;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Scoring;
@ -31,22 +36,32 @@ namespace osu.Game.Rulesets.Mania.UI
protected override Playfield<ManiaHitObject, ManiaJudgement> CreatePlayfield()
{
ControlPoint firstTimingChange = Beatmap.TimingInfo.ControlPoints.FirstOrDefault(t => t.TimingChange);
double lastSpeedMultiplier = 1;
double lastBeatLength = 500;
if (firstTimingChange == null)
throw new InvalidOperationException("The Beatmap contains no timing points!");
// Merge timing + difficulty points
var allPoints = new SortedList<ControlPoint>(Comparer<ControlPoint>.Default);
allPoints.AddRange(Beatmap.ControlPointInfo.TimingPoints);
allPoints.AddRange(Beatmap.ControlPointInfo.DifficultyPoints);
// Generate the timing points, making non-timing changes use the previous timing change
var timingChanges = Beatmap.TimingInfo.ControlPoints.Select(c =>
var timingChanges = allPoints.Select(c =>
{
ControlPoint t = c.Clone();
var timingPoint = c as TimingControlPoint;
var difficultyPoint = c as DifficultyControlPoint;
if (c.TimingChange)
firstTimingChange = c;
else
t.BeatLength = firstTimingChange.BeatLength;
if (timingPoint != null)
lastBeatLength = timingPoint.BeatLength;
return t;
if (difficultyPoint != null)
lastSpeedMultiplier = difficultyPoint.SpeedMultiplier;
return new TimingChange
{
Time = c.Time,
BeatLength = lastBeatLength,
SpeedMultiplier = lastSpeedMultiplier
};
});
double lastObjectTime = (Objects.LastOrDefault() as IHasEndTime)?.EndTime ?? Objects.LastOrDefault()?.StartTime ?? double.MaxValue;
@ -76,13 +91,19 @@ namespace osu.Game.Rulesets.Mania.UI
protected override DrawableHitObject<ManiaHitObject, ManiaJudgement> GetVisualRepresentation(ManiaHitObject h)
{
var maniaPlayfield = Playfield as ManiaPlayfield;
if (maniaPlayfield == null)
return null;
Bindable<Key> key = maniaPlayfield.Columns.ElementAt(h.Column).Key;
var holdNote = h as HoldNote;
if (holdNote != null)
return new DrawableHoldNote(holdNote);
return new DrawableHoldNote(holdNote, key);
var note = h as Note;
if (note != null)
return new DrawableNote(note);
return new DrawableNote(note, key);
return null;
}

View File

@ -19,7 +19,6 @@ using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Mania.Timing;
using osu.Framework.Input;
using osu.Game.Beatmaps.Timing;
using osu.Framework.Graphics.Transforms;
using osu.Framework.MathUtils;
@ -55,7 +54,8 @@ namespace osu.Game.Rulesets.Mania.UI
}
}
public readonly FlowContainer<Column> Columns;
private readonly FlowContainer<Column> columns;
public IEnumerable<Column> Columns => columns.Children;
private readonly ControlPointContainer barlineContainer;
@ -64,7 +64,7 @@ namespace osu.Game.Rulesets.Mania.UI
private readonly int columnCount;
public ManiaPlayfield(int columnCount, IEnumerable<ControlPoint> timingChanges)
public ManiaPlayfield(int columnCount, IEnumerable<TimingChange> timingChanges)
{
this.columnCount = columnCount;
@ -87,7 +87,7 @@ namespace osu.Game.Rulesets.Mania.UI
RelativeSizeAxes = Axes.Both,
Colour = Color4.Black
},
Columns = new FillFlowContainer<Column>
columns = new FillFlowContainer<Column>
{
Name = "Columns",
RelativeSizeAxes = Axes.Y,
@ -114,7 +114,7 @@ namespace osu.Game.Rulesets.Mania.UI
};
for (int i = 0; i < columnCount; i++)
Columns.Add(new Column(timingChanges));
columns.Add(new Column(timingChanges));
TimeSpan = time_span_default;
}
@ -133,17 +133,17 @@ namespace osu.Game.Rulesets.Mania.UI
// Set the special column + colour + key
for (int i = 0; i < columnCount; i++)
{
Column column = Columns.Children.ElementAt(i);
Column column = Columns.ElementAt(i);
column.IsSpecial = isSpecialColumn(i);
if (!column.IsSpecial)
continue;
column.Key = Key.Space;
column.Key.Value = Key.Space;
column.AccentColour = specialColumnColour;
}
var nonSpecialColumns = Columns.Children.Where(c => !c.IsSpecial).ToList();
var nonSpecialColumns = Columns.Where(c => !c.IsSpecial).ToList();
// We'll set the colours of the non-special columns in a separate loop, because the non-special
// column colours are mirrored across their centre and special styles mess with this
@ -162,11 +162,11 @@ namespace osu.Game.Rulesets.Mania.UI
int keyOffset = default_keys.Length / 2 - nonSpecialColumns.Count / 2 + i;
if (keyOffset >= 0 && keyOffset < default_keys.Length)
column.Key = default_keys[keyOffset];
column.Key.Value = default_keys[keyOffset];
else
// There is no default key defined for this column. Let's set this to Unknown for now
// however note that this will be gone after bindings are in place
column.Key = Key.Unknown;
column.Key.Value = Key.Unknown;
}
}
@ -189,7 +189,7 @@ namespace osu.Game.Rulesets.Mania.UI
}
}
public override void Add(DrawableHitObject<ManiaHitObject, ManiaJudgement> h) => Columns.Children.ElementAt(h.HitObject.Column).Add(h);
public override void Add(DrawableHitObject<ManiaHitObject, ManiaJudgement> h) => Columns.ElementAt(h.HitObject.Column).Add(h);
protected override bool OnKeyDown(InputState state, KeyDownEventArgs args)
{
@ -225,7 +225,7 @@ namespace osu.Game.Rulesets.Mania.UI
timeSpan = MathHelper.Clamp(timeSpan, time_span_min, time_span_max);
barlineContainer.TimeSpan = value;
Columns.Children.ForEach(c => c.ControlPointContainer.TimeSpan = value);
Columns.ForEach(c => c.ControlPointContainer.TimeSpan = value);
}
}

View File

@ -51,11 +51,13 @@
<Compile Include="Beatmaps\Patterns\Legacy\DistanceObjectPatternGenerator.cs" />
<Compile Include="Beatmaps\Patterns\Legacy\PatternGenerator.cs" />
<Compile Include="Beatmaps\Patterns\PatternGenerator.cs" />
<Compile Include="Beatmaps\Patterns\Legacy\HitObjectPatternGenerator.cs" />
<Compile Include="Beatmaps\Patterns\Legacy\PatternType.cs" />
<Compile Include="Beatmaps\ManiaBeatmapConverter.cs" />
<Compile Include="Beatmaps\Patterns\Pattern.cs" />
<Compile Include="MathUtils\FastRandom.cs" />
<Compile Include="Judgements\HitWindows.cs" />
<Compile Include="Judgements\ManiaHitResult.cs" />
<Compile Include="Judgements\ManiaJudgement.cs" />
<Compile Include="ManiaDifficultyCalculator.cs" />
<Compile Include="Objects\Drawables\DrawableHoldNote.cs" />
@ -76,6 +78,7 @@
<Compile Include="Mods\ManiaMod.cs" />
<Compile Include="UI\SpecialColumnPosition.cs" />
<Compile Include="Timing\ControlPointContainer.cs" />
<Compile Include="Timing\TimingChange.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\osu-framework\osu.Framework\osu.Framework.csproj">

View File

@ -6,8 +6,8 @@ using OpenTK;
using osu.Game.Rulesets.Osu.Objects.Drawables;
using osu.Game.Rulesets.Objects.Types;
using OpenTK.Graphics;
using osu.Game.Beatmaps.Timing;
using osu.Game.Database;
using osu.Game.Beatmaps.ControlPoints;
namespace osu.Game.Rulesets.Osu.Objects
{
@ -68,9 +68,9 @@ namespace osu.Game.Rulesets.Osu.Objects
return OsuScoreResult.Miss;
}
public override void ApplyDefaults(TimingInfo timing, BeatmapDifficulty difficulty)
public override void ApplyDefaults(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty)
{
base.ApplyDefaults(timing, difficulty);
base.ApplyDefaults(controlPointInfo, difficulty);
Scale = (1.0f - 0.7f * (difficulty.CircleSize - 5) / 5) / 2;
}

View File

@ -2,7 +2,6 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using OpenTK;
using osu.Game.Beatmaps.Timing;
using osu.Game.Rulesets.Objects.Types;
using System;
using System.Collections.Generic;
@ -10,6 +9,7 @@ using osu.Game.Rulesets.Objects;
using osu.Game.Database;
using System.Linq;
using osu.Game.Audio;
using osu.Game.Beatmaps.ControlPoints;
namespace osu.Game.Rulesets.Osu.Objects
{
@ -62,13 +62,16 @@ namespace osu.Game.Rulesets.Osu.Objects
public double Velocity;
public double TickDistance;
public override void ApplyDefaults(TimingInfo timing, BeatmapDifficulty difficulty)
public override void ApplyDefaults(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty)
{
base.ApplyDefaults(timing, difficulty);
base.ApplyDefaults(controlPointInfo, difficulty);
double scoringDistance = base_scoring_distance * difficulty.SliderMultiplier / timing.SpeedMultiplierAt(StartTime);
TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(StartTime);
DifficultyControlPoint difficultyPoint = controlPointInfo.DifficultyPointAt(StartTime);
Velocity = scoringDistance / timing.BeatLengthAt(StartTime);
double scoringDistance = base_scoring_distance * difficulty.SliderMultiplier / difficultyPoint.SpeedMultiplier;
Velocity = scoringDistance / timingPoint.BeatLength;
TickDistance = scoringDistance / difficulty.SliderTickRate;
}

View File

@ -2,8 +2,8 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Beatmaps.Timing;
using osu.Game.Database;
using osu.Game.Beatmaps.ControlPoints;
namespace osu.Game.Rulesets.Osu.Objects
{
@ -19,9 +19,9 @@ namespace osu.Game.Rulesets.Osu.Objects
public override bool NewCombo => true;
public override void ApplyDefaults(TimingInfo timing, BeatmapDifficulty difficulty)
public override void ApplyDefaults(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty)
{
base.ApplyDefaults(timing, difficulty);
base.ApplyDefaults(controlPointInfo, difficulty);
SpinsRequired = (int)(Duration / 1000 * BeatmapDifficulty.DifficultyRange(difficulty.OverallDifficulty, 3, 5, 7.5));
}

View File

@ -12,6 +12,7 @@ using osu.Game.Database;
using osu.Game.IO.Serialization;
using osu.Game.Audio;
using osu.Game.Rulesets.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
namespace osu.Game.Rulesets.Taiko.Beatmaps
{
@ -77,8 +78,11 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
{
int repeats = repeatsData?.RepeatCount ?? 1;
double speedAdjustment = beatmap.TimingInfo.SpeedMultiplierAt(obj.StartTime);
double speedAdjustedBeatLength = beatmap.TimingInfo.BeatLengthAt(obj.StartTime) * speedAdjustment;
TimingControlPoint timingPoint = beatmap.ControlPointInfo.TimingPointAt(obj.StartTime);
DifficultyControlPoint difficultyPoint = beatmap.ControlPointInfo.DifficultyPointAt(obj.StartTime);
double speedAdjustment = difficultyPoint.SpeedMultiplier;
double speedAdjustedBeatLength = timingPoint.BeatLength * speedAdjustment;
// The true distance, accounting for any repeats. This ends up being the drum roll distance later
double distance = distanceData.Distance * repeats * legacy_velocity_multiplier;

View File

@ -5,9 +5,9 @@ using osu.Game.Rulesets.Objects.Types;
using System;
using System.Collections.Generic;
using System.Linq;
using osu.Game.Beatmaps.Timing;
using osu.Game.Database;
using osu.Game.Audio;
using osu.Game.Beatmaps.ControlPoints;
namespace osu.Game.Rulesets.Taiko.Objects
{
@ -55,11 +55,13 @@ namespace osu.Game.Rulesets.Taiko.Objects
/// </summary>
private double tickSpacing = 100;
public override void ApplyDefaults(TimingInfo timing, BeatmapDifficulty difficulty)
public override void ApplyDefaults(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty)
{
base.ApplyDefaults(timing, difficulty);
base.ApplyDefaults(controlPointInfo, difficulty);
tickSpacing = timing.BeatLengthAt(StartTime) / TickRate;
TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(StartTime);
tickSpacing = timingPoint.BeatLength / TickRate;
RequiredGoodHits = TotalTicks * Math.Min(0.15, 0.05 + 0.10 / 6 * difficulty.OverallDifficulty);
RequiredGreatHits = TotalTicks * Math.Min(0.30, 0.10 + 0.20 / 6 * difficulty.OverallDifficulty);

View File

@ -1,7 +1,7 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Beatmaps.Timing;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Database;
namespace osu.Game.Rulesets.Taiko.Objects
@ -23,9 +23,9 @@ namespace osu.Game.Rulesets.Taiko.Objects
/// </summary>
public double HitWindowMiss = 95;
public override void ApplyDefaults(TimingInfo timing, BeatmapDifficulty difficulty)
public override void ApplyDefaults(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty)
{
base.ApplyDefaults(timing, difficulty);
base.ApplyDefaults(controlPointInfo, difficulty);
HitWindowGreat = BeatmapDifficulty.DifficultyRange(difficulty.OverallDifficulty, 50, 35, 20);
HitWindowGood = BeatmapDifficulty.DifficultyRange(difficulty.OverallDifficulty, 120, 80, 50);

View File

@ -1,7 +1,7 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Beatmaps.Timing;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Database;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Taiko.UI;
@ -31,12 +31,12 @@ namespace osu.Game.Rulesets.Taiko.Objects
public const float DEFAULT_STRONG_CIRCLE_DIAMETER = DEFAULT_CIRCLE_DIAMETER * STRONG_CIRCLE_DIAMETER_SCALE;
/// <summary>
/// The time taken from the initial (off-screen) spawn position to the centre of the hit target for a <see cref="ControlPoint.BeatLength"/> of 1000ms.
/// The time taken from the initial (off-screen) spawn position to the centre of the hit target for a <see cref="TimingControlPoint.BeatLength"/> of 1000ms.
/// </summary>
private const double scroll_time = 6000;
/// <summary>
/// Our adjusted <see cref="scroll_time"/> taking into consideration local <see cref="ControlPoint.BeatLength"/> and other speed multipliers.
/// Our adjusted <see cref="scroll_time"/> taking into consideration local <see cref="TimingControlPoint.BeatLength"/> and other speed multipliers.
/// </summary>
public double ScrollTime;
@ -51,17 +51,17 @@ namespace osu.Game.Rulesets.Taiko.Objects
/// </summary>
public bool Kiai { get; protected set; }
public override void ApplyDefaults(TimingInfo timing, BeatmapDifficulty difficulty)
public override void ApplyDefaults(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty)
{
base.ApplyDefaults(timing, difficulty);
base.ApplyDefaults(controlPointInfo, difficulty);
ScrollTime = scroll_time * (timing.BeatLengthAt(StartTime) * timing.SpeedMultiplierAt(StartTime) / 1000) / difficulty.SliderMultiplier;
TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(StartTime);
DifficultyControlPoint difficultyPoint = controlPointInfo.DifficultyPointAt(StartTime);
EffectControlPoint effectPoint = controlPointInfo.EffectPointAt(StartTime);
ControlPoint overridePoint;
Kiai = timing.TimingPointAt(StartTime, out overridePoint).KiaiMode;
ScrollTime = scroll_time * (timingPoint.BeatLength * difficultyPoint.SpeedMultiplier / 1000) / difficulty.SliderMultiplier;
if (overridePoint != null)
Kiai |= overridePoint.KiaiMode;
Kiai |= effectPoint.KiaiMode;
}
}
}

View File

@ -17,6 +17,7 @@ using osu.Game.Rulesets.UI;
using osu.Game.Rulesets.Taiko.Replays;
using OpenTK;
using osu.Game.Rulesets.Beatmaps;
using System.Linq;
namespace osu.Game.Rulesets.Taiko.UI
{
@ -43,7 +44,7 @@ namespace osu.Game.Rulesets.Taiko.UI
TaikoHitObject lastObject = Beatmap.HitObjects[Beatmap.HitObjects.Count - 1];
double lastHitTime = 1 + (lastObject as IHasEndTime)?.EndTime ?? lastObject.StartTime;
var timingPoints = Beatmap.TimingInfo.ControlPoints.FindAll(cp => cp.TimingChange);
var timingPoints = Beatmap.ControlPointInfo.TimingPoints.ToList();
if (timingPoints.Count == 0)
return;
@ -68,7 +69,7 @@ namespace osu.Game.Rulesets.Taiko.UI
StartTime = time,
};
barLine.ApplyDefaults(Beatmap.TimingInfo, Beatmap.BeatmapInfo.Difficulty);
barLine.ApplyDefaults(Beatmap.ControlPointInfo, Beatmap.BeatmapInfo.Difficulty);
bool isMajor = currentBeat % (int)currentPoint.TimeSignature == 0;
taikoPlayfield.AddBarLine(isMajor ? new DrawableBarLineMajor(barLine) : new DrawableBarLine(barLine));

View File

@ -7,6 +7,7 @@ using osu.Game.Database;
using osu.Game.Rulesets.Objects;
using System.Collections.Generic;
using System.Linq;
using osu.Game.Beatmaps.ControlPoints;
namespace osu.Game.Beatmaps
{
@ -17,7 +18,7 @@ namespace osu.Game.Beatmaps
where T : HitObject
{
public BeatmapInfo BeatmapInfo;
public TimingInfo TimingInfo = new TimingInfo();
public ControlPointInfo ControlPointInfo = new ControlPointInfo();
public List<BreakPeriod> Breaks = new List<BreakPeriod>();
public readonly List<Color4> ComboColors = new List<Color4>
{
@ -46,7 +47,7 @@ namespace osu.Game.Beatmaps
public Beatmap(Beatmap original = null)
{
BeatmapInfo = original?.BeatmapInfo ?? BeatmapInfo;
TimingInfo = original?.TimingInfo ?? TimingInfo;
ControlPointInfo = original?.ControlPointInfo ?? ControlPointInfo;
Breaks = original?.Breaks ?? Breaks;
ComboColors = original?.ComboColors ?? ComboColors;
}

View File

@ -0,0 +1,17 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
namespace osu.Game.Beatmaps.ControlPoints
{
public class ControlPoint : IComparable<ControlPoint>
{
/// <summary>
/// The time at which the control point takes effect.
/// </summary>
public double Time;
public int CompareTo(ControlPoint other) => Time.CompareTo(other.Time);
}
}

View File

@ -0,0 +1,107 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Lists;
namespace osu.Game.Beatmaps.ControlPoints
{
public class ControlPointInfo
{
/// <summary>
/// All timing points.
/// </summary>
public readonly SortedList<TimingControlPoint> TimingPoints = new SortedList<TimingControlPoint>(Comparer<TimingControlPoint>.Default);
/// <summary>
/// All difficulty points.
/// </summary>
public readonly SortedList<DifficultyControlPoint> DifficultyPoints = new SortedList<DifficultyControlPoint>(Comparer<DifficultyControlPoint>.Default);
/// <summary>
/// All sound points.
/// </summary>
public readonly SortedList<SoundControlPoint> SoundPoints = new SortedList<SoundControlPoint>(Comparer<SoundControlPoint>.Default);
/// <summary>
/// All effect points.
/// </summary>
public readonly SortedList<EffectControlPoint> EffectPoints = new SortedList<EffectControlPoint>(Comparer<EffectControlPoint>.Default);
/// <summary>
/// Finds the difficulty control point that is active at <paramref name="time"/>.
/// </summary>
/// <param name="time">The time to find the difficulty control point at.</param>
/// <returns>The difficulty control point.</returns>
public DifficultyControlPoint DifficultyPointAt(double time) => binarySearch(DifficultyPoints, time);
/// <summary>
/// Finds the effect control point that is active at <paramref name="time"/>.
/// </summary>
/// <param name="time">The time to find the effect control point at.</param>
/// <returns>The effect control point.</returns>
public EffectControlPoint EffectPointAt(double time) => binarySearch(EffectPoints, time);
/// <summary>
/// Finds the sound control point that is active at <paramref name="time"/>.
/// </summary>
/// <param name="time">The time to find the sound control point at.</param>
/// <returns>The sound control point.</returns>
public SoundControlPoint SoundPointAt(double time) => binarySearch(SoundPoints, time);
/// <summary>
/// Finds the timing control point that is active at <paramref name="time"/>.
/// </summary>
/// <param name="time">The time to find the timing control point at.</param>
/// <returns>The timing control point.</returns>
public TimingControlPoint TimingPointAt(double time) => binarySearch(TimingPoints, time, TimingPoints.FirstOrDefault());
/// <summary>
/// Finds the maximum BPM represented by any timing control point.
/// </summary>
public double BPMMaximum =>
60000 / (TimingPoints.OrderBy(c => c.BeatLength).FirstOrDefault() ?? new TimingControlPoint()).BeatLength;
/// <summary>
/// Finds the minimum BPM represented by any timing control point.
/// </summary>
public double BPMMinimum =>
60000 / (TimingPoints.OrderByDescending(c => c.BeatLength).FirstOrDefault() ?? new TimingControlPoint()).BeatLength;
/// <summary>
/// Finds the mode BPM (most common BPM) represented by the control points.
/// </summary>
public double BPMMode =>
60000 / (TimingPoints.GroupBy(c => c.BeatLength).OrderByDescending(grp => grp.Count()).FirstOrDefault()?.FirstOrDefault() ?? new TimingControlPoint()).BeatLength;
/// <summary>
/// Binary searches one of the control point lists to find the active control point at <paramref name="time"/>.
/// </summary>
/// <param name="list">The list to search.</param>
/// <param name="time">The time to find the control point at.</param>
/// <param name="prePoint">The control point to use when <paramref name="time"/> is before any control points. If null, a new control point will be constructed.</param>
/// <returns>The active control point at <paramref name="time"/>.</returns>
private T binarySearch<T>(SortedList<T> list, double time, T prePoint = null)
where T : ControlPoint, new()
{
if (list.Count == 0)
return new T();
if (time < list[0].Time)
return prePoint ?? new T();
int index = list.BinarySearch(new T() { Time = time });
// Check if we've found an exact match (t == time)
if (index >= 0)
return list[index];
index = ~index;
// BinarySearch will return the index of the first element _greater_ than the search
// This is the inactive point - the active point is the one before it (index - 1)
return list[index - 1];
}
}
}

View File

@ -0,0 +1,13 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
namespace osu.Game.Beatmaps.ControlPoints
{
public class DifficultyControlPoint : ControlPoint
{
/// <summary>
/// The speed multiplier at this control point.
/// </summary>
public double SpeedMultiplier = 1;
}
}

View File

@ -0,0 +1,18 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
namespace osu.Game.Beatmaps.ControlPoints
{
public class EffectControlPoint : ControlPoint
{
/// <summary>
/// Whether this control point enables Kiai mode.
/// </summary>
public bool KiaiMode;
/// <summary>
/// Whether the first bar line of this control point is ignored.
/// </summary>
public bool OmitFirstBarLine;
}
}

View File

@ -0,0 +1,18 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
namespace osu.Game.Beatmaps.ControlPoints
{
public class SoundControlPoint : ControlPoint
{
/// <summary>
/// The default sample bank at this control point.
/// </summary>
public string SampleBank;
/// <summary>
/// The default sample volume at this control point.
/// </summary>
public int SampleVolume;
}
}

View File

@ -0,0 +1,20 @@
// 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.Timing;
namespace osu.Game.Beatmaps.ControlPoints
{
public class TimingControlPoint : ControlPoint
{
/// <summary>
/// The time signature at this control point.
/// </summary>
public TimeSignatures TimeSignature = TimeSignatures.SimpleQuadruple;
/// <summary>
/// The beat length at this control point.
/// </summary>
public double BeatLength = 500;
}
}

View File

@ -37,7 +37,7 @@ namespace osu.Game.Beatmaps
Objects = CreateBeatmapConverter().Convert(beatmap, true).HitObjects;
foreach (var h in Objects)
h.ApplyDefaults(beatmap.TimingInfo, beatmap.BeatmapInfo.Difficulty);
h.ApplyDefaults(beatmap.ControlPointInfo, beatmap.BeatmapInfo.Difficulty);
PreprocessHitObjects();
}

View File

@ -9,7 +9,7 @@ using OpenTK.Graphics;
namespace osu.Game.Beatmaps.Drawables
{
internal class DifficultyColouredContainer : Container, IHasAccentColour
public class DifficultyColouredContainer : Container, IHasAccentColour
{
public Color4 AccentColour { get; set; }

View File

@ -11,7 +11,7 @@ using OpenTK.Graphics;
namespace osu.Game.Beatmaps.Drawables
{
internal class DifficultyIcon : DifficultyColouredContainer
public class DifficultyIcon : DifficultyColouredContainer
{
private readonly BeatmapInfo beatmap;

View File

@ -8,6 +8,7 @@ using OpenTK.Graphics;
using osu.Game.Beatmaps.Timing;
using osu.Game.Beatmaps.Legacy;
using osu.Game.Rulesets.Objects.Legacy;
using osu.Game.Beatmaps.ControlPoints;
namespace osu.Game.Beatmaps.Formats
{
@ -241,6 +242,7 @@ namespace osu.Game.Beatmaps.Formats
double time = double.Parse(split[0].Trim(), NumberFormatInfo.InvariantInfo);
double beatLength = double.Parse(split[1].Trim(), NumberFormatInfo.InvariantInfo);
double speedMultiplier = beatLength < 0 ? -beatLength / 100.0 : 1;
TimeSignatures timeSignature = TimeSignatures.SimpleQuadruple;
if (split.Length >= 3)
@ -275,18 +277,48 @@ namespace osu.Game.Beatmaps.Formats
if (stringSampleSet == @"none")
stringSampleSet = @"normal";
beatmap.TimingInfo.ControlPoints.Add(new ControlPoint
DifficultyControlPoint difficultyPoint = beatmap.ControlPointInfo.DifficultyPointAt(time);
SoundControlPoint soundPoint = beatmap.ControlPointInfo.SoundPointAt(time);
EffectControlPoint effectPoint = beatmap.ControlPointInfo.EffectPointAt(time);
if (timingChange)
{
Time = time,
BeatLength = beatLength,
SpeedMultiplier = beatLength < 0 ? -beatLength / 100.0 : 1,
TimingChange = timingChange,
TimeSignature = timeSignature,
SampleBank = stringSampleSet,
SampleVolume = sampleVolume,
KiaiMode = kiaiMode,
OmitFirstBarLine = omitFirstBarSignature
});
beatmap.ControlPointInfo.TimingPoints.Add(new TimingControlPoint
{
Time = time,
BeatLength = beatLength,
TimeSignature = timeSignature
});
}
if (speedMultiplier != difficultyPoint.SpeedMultiplier)
{
beatmap.ControlPointInfo.DifficultyPoints.Add(new DifficultyControlPoint
{
Time = time,
SpeedMultiplier = speedMultiplier
});
}
if (stringSampleSet != soundPoint.SampleBank || sampleVolume != soundPoint.SampleVolume)
{
beatmap.ControlPointInfo.SoundPoints.Add(new SoundControlPoint
{
Time = time,
SampleBank = stringSampleSet,
SampleVolume = sampleVolume
});
}
if (kiaiMode != effectPoint.KiaiMode || omitFirstBarSignature != effectPoint.OmitFirstBarLine)
{
beatmap.ControlPointInfo.EffectPoints.Add(new EffectControlPoint
{
Time = time,
KiaiMode = kiaiMode,
OmitFirstBarLine = omitFirstBarSignature
});
}
}
private void handleColours(Beatmap beatmap, string key, string val, ref bool hasCustomColours)

View File

@ -63,5 +63,7 @@ namespace osu.Game.Beatmaps.IO
return buffer;
}
}
public abstract Stream GetUnderlyingStream();
}
}

View File

@ -49,5 +49,7 @@ namespace osu.Game.Beatmaps.IO
archive.Dispose();
archiveStream.Dispose();
}
public override Stream GetUnderlyingStream() => archiveStream;
}
}

View File

@ -1,20 +0,0 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
namespace osu.Game.Beatmaps.Timing
{
public class ControlPoint
{
public string SampleBank;
public int SampleVolume;
public TimeSignatures TimeSignature = TimeSignatures.SimpleQuadruple;
public double Time;
public double BeatLength = 500;
public double SpeedMultiplier = 1;
public bool TimingChange = true;
public bool KiaiMode;
public bool OmitFirstBarLine;
public ControlPoint Clone() => (ControlPoint)MemberwiseClone();
}
}

View File

@ -1,80 +0,0 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Collections.Generic;
using System.Linq;
namespace osu.Game.Beatmaps.Timing
{
public class TimingInfo
{
public readonly List<ControlPoint> ControlPoints = new List<ControlPoint>();
public double BPMMaximum => 60000 / (ControlPoints?.Where(c => c.BeatLength != 0).OrderBy(c => c.BeatLength).FirstOrDefault() ?? new ControlPoint()).BeatLength;
public double BPMMinimum => 60000 / (ControlPoints?.Where(c => c.BeatLength != 0).OrderByDescending(c => c.BeatLength).FirstOrDefault() ?? new ControlPoint()).BeatLength;
public double BPMMode => BPMAt(ControlPoints.Where(c => c.BeatLength != 0).GroupBy(c => c.BeatLength).OrderByDescending(grp => grp.Count()).First().First().Time);
public double BPMAt(double time)
{
return 60000 / BeatLengthAt(time);
}
/// <summary>
/// Finds the speed multiplier at a time.
/// </summary>
/// <param name="time">The time to find the speed multiplier at.</param>
/// <returns>The speed multiplier.</returns>
public double SpeedMultiplierAt(double time)
{
ControlPoint overridePoint;
ControlPoint timingPoint = TimingPointAt(time, out overridePoint);
return overridePoint?.SpeedMultiplier ?? timingPoint?.SpeedMultiplier ?? 1;
}
/// <summary>
/// Finds the beat length at a time. This is expressed in milliseconds.
/// </summary>
/// <param name="time">The time to find the beat length at.</param>
/// <returns>The beat length.</returns>
public double BeatLengthAt(double time)
{
ControlPoint overridePoint;
ControlPoint timingPoint = TimingPointAt(time, out overridePoint);
return timingPoint.BeatLength;
}
/// <summary>
/// Finds the timing point at a time.
/// </summary>
/// <param name="time">The time to find the timing point at.</param>
/// <param name="overridePoint">The timing point containing the velocity change of the returned timing point.</param>
/// <returns>The timing point.</returns>
public ControlPoint TimingPointAt(double time, out ControlPoint overridePoint)
{
overridePoint = null;
ControlPoint timingPoint = null;
foreach (var controlPoint in ControlPoints)
{
// Some beatmaps have the first timingPoint (accidentally) start after the first HitObject(s).
// This null check makes it so that the first ControlPoint that makes a timing change is used as
// the timingPoint for those HitObject(s).
if (controlPoint.Time <= time || timingPoint == null)
{
if (controlPoint.TimingChange)
{
timingPoint = controlPoint;
overridePoint = null;
}
else
overridePoint = controlPoint;
}
else break;
}
return timingPoint ?? new ControlPoint();
}
}
}

View File

@ -12,6 +12,7 @@ using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Formats;
using osu.Game.Beatmaps.IO;
using osu.Game.IPC;
using osu.Game.Screens.Menu;
using SQLite.Net;
using SQLiteNetExtensions.Extensions;
@ -38,6 +39,10 @@ namespace osu.Game.Database
{
foreach (var b in GetAllWithChildren<BeatmapSetInfo>(b => b.DeletePending))
{
if (b.Hash == Intro.MENU_MUSIC_BEATMAP_HASH)
// this is a bit hacky, but will do for now.
continue;
try
{
Storage.Delete(b.Path);
@ -97,50 +102,49 @@ namespace osu.Game.Database
typeof(BeatmapDifficulty),
};
public void Import(string path)
{
try
{
Import(ArchiveReader.GetReader(Storage, path));
// We may or may not want to delete the file depending on where it is stored.
// e.g. reconstructing/repairing database with beatmaps from default storage.
// Also, not always a single file, i.e. for LegacyFilesystemReader
// TODO: Add a check to prevent files from storage to be deleted.
try
{
File.Delete(path);
}
catch (Exception e)
{
Logger.Error(e, $@"Could not delete file at {path}");
}
}
catch (Exception e)
{
e = e.InnerException ?? e;
Logger.Error(e, @"Could not import beatmap set");
}
}
public void Import(ArchiveReader archiveReader)
{
BeatmapSetInfo set = getBeatmapSet(archiveReader);
//If we have an ID then we already exist in the database.
if (set.ID == 0)
Import(new[] { set });
}
/// <summary>
/// Import multiple <see cref="BeatmapSetInfo"/> from <paramref name="paths"/>.
/// </summary>
/// <param name="paths">Multiple locations on disk</param>
public void Import(IEnumerable<string> paths)
public void Import(params string[] paths)
{
foreach (string p in paths)
{
try
{
BeatmapSetInfo set = getBeatmapSet(p);
//If we have an ID then we already exist in the database.
if (set.ID == 0)
Import(new[] { set });
// We may or may not want to delete the file depending on where it is stored.
// e.g. reconstructing/repairing database with beatmaps from default storage.
// Also, not always a single file, i.e. for LegacyFilesystemReader
// TODO: Add a check to prevent files from storage to be deleted.
try
{
File.Delete(p);
}
catch (Exception e)
{
Logger.Error(e, $@"Could not delete file at {p}");
}
}
catch (Exception e)
{
e = e.InnerException ?? e;
Logger.Error(e, @"Could not import beatmap set");
}
}
}
/// <summary>
/// Import <see cref="BeatmapSetInfo"/> from <paramref name="path"/>.
/// </summary>
/// <param name="path">Location on disk</param>
public void Import(string path)
{
Import(new[] { path });
Import(p);
}
/// <summary>
@ -148,29 +152,26 @@ namespace osu.Game.Database
/// </summary>
/// <param name="path">Content location</param>
/// <returns><see cref="BeatmapSetInfo"/></returns>
private BeatmapSetInfo getBeatmapSet(string path)
{
string hash = null;
private BeatmapSetInfo getBeatmapSet(string path) => getBeatmapSet(ArchiveReader.GetReader(Storage, path));
private BeatmapSetInfo getBeatmapSet(ArchiveReader archiveReader)
{
BeatmapMetadata metadata;
using (var reader = ArchiveReader.GetReader(Storage, path))
{
using (var stream = new StreamReader(reader.GetStream(reader.BeatmapFilenames[0])))
metadata = BeatmapDecoder.GetDecoder(stream).Decode(stream).Metadata;
}
using (var stream = new StreamReader(archiveReader.GetStream(archiveReader.BeatmapFilenames[0])))
metadata = BeatmapDecoder.GetDecoder(stream).Decode(stream).Metadata;
if (File.Exists(path)) // Not always the case, i.e. for LegacyFilesystemReader
string hash;
string path;
using (var input = archiveReader.GetUnderlyingStream())
{
using (var input = Storage.GetStream(path))
{
hash = input.GetMd5Hash();
input.Seek(0, SeekOrigin.Begin);
path = Path.Combine(@"beatmaps", hash.Remove(1), hash.Remove(2), hash);
if (!Storage.Exists(path))
using (var output = Storage.GetStream(path, FileAccess.Write))
input.CopyTo(output);
}
hash = input.GetMd5Hash();
input.Seek(0, SeekOrigin.Begin);
path = Path.Combine(@"beatmaps", hash.Remove(1), hash.Remove(2), hash);
if (!Storage.Exists(path))
using (var output = Storage.GetStream(path, FileAccess.Write))
input.CopyTo(output);
}
var existing = Connection.Table<BeatmapSetInfo>().FirstOrDefault(b => b.Hash == hash);

View File

@ -43,6 +43,9 @@ namespace osu.Game.Database
[Ignore]
public BeatmapMetrics Metrics { get; set; }
[Ignore]
public BeatmapOnlineInfo OnlineInfo { get; set; }
public string Path { get; set; }
[JsonProperty("file_md5")]

View File

@ -0,0 +1,38 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using Newtonsoft.Json;
using System.Collections.Generic;
namespace osu.Game.Database
{
/// <summary>
/// Beatmap info retrieved for previewing locally without having the beatmap downloaded.
/// </summary>
public class BeatmapOnlineInfo
{
/// <summary>
/// The different sizes of cover art for this beatmap: cover, cover@2x, card, card@2x, list, list@2x.
/// </summary>
[JsonProperty(@"covers")]
public IEnumerable<string> Covers { get; set; }
/// <summary>
/// A small sample clip of this beatmap's song.
/// </summary>
[JsonProperty(@"previewUrl")]
public string Preview { get; set; }
/// <summary>
/// The amount of plays this beatmap has.
/// </summary>
[JsonProperty(@"play_count")]
public int PlayCount { get; set; }
/// <summary>
/// The amount of people who have favourited this map.
/// </summary>
[JsonProperty(@"favourite_count")]
public int FavouriteCount { get; set; }
}
}

View File

@ -0,0 +1,37 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Linq;
using osu.Framework.Audio.Track;
using osu.Framework.Graphics.Textures;
using osu.Game.Beatmaps;
namespace osu.Game.Database
{
internal class OnlineWorkingBeatmap : WorkingBeatmap
{
private readonly TextureStore textures;
private readonly TrackManager tracks;
public OnlineWorkingBeatmap(BeatmapInfo beatmapInfo, TextureStore textures, TrackManager tracks) : base(beatmapInfo)
{
this.textures = textures;
this.tracks = tracks;
}
protected override Beatmap GetBeatmap()
{
return new Beatmap();
}
protected override Texture GetBackground()
{
return textures.Get(BeatmapInfo.OnlineInfo.Covers.FirstOrDefault());
}
protected override Track GetTrack()
{
return tracks.Get(BeatmapInfo.OnlineInfo.Preview);
}
}
}

View File

@ -0,0 +1,23 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.ComponentModel;
namespace osu.Game.Database
{
public enum RankStatus
{
Any = 7,
[Description("Ranked & Approved")]
RankedApproved = 0,
Approved = 1,
Loved = 8,
Favourites = 2,
[Description("Mod Requests")]
ModRequests = 3,
Pending = 4,
Graveyard = 5,
[Description("My Maps")]
MyMaps = 6,
}
}

View File

@ -44,6 +44,11 @@ namespace osu.Game.Graphics.Backgrounds
/// </summary>
public bool HideAlphaDiscrepancies = true;
/// <summary>
/// The relative velocity of the triangles. Default is 1.
/// </summary>
public float Velocity = 1;
public float TriangleScale
{
get { return triangleScale; }
@ -78,7 +83,7 @@ namespace osu.Game.Graphics.Backgrounds
foreach (var t in Children)
{
t.Alpha = adjustedAlpha;
t.Position -= new Vector2(0, (float)(t.Scale.X * (50 / DrawHeight) * (Time.Elapsed / 950)) / triangleScale);
t.Position -= new Vector2(0, (float)(t.Scale.X * (50 / DrawHeight) * (Time.Elapsed / 950)) / triangleScale * Velocity);
if (ExpireOffScreenTriangles && t.DrawPosition.Y + t.DrawSize.Y * t.Scale.Y < 0)
t.Expire();
}

View File

@ -0,0 +1,67 @@
// 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.Framework.Allocation;
using osu.Framework.Audio.Track;
using osu.Framework.Configuration;
using osu.Framework.Graphics.Containers;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
namespace osu.Game.Graphics.Containers
{
public class BeatSyncedContainer : Container
{
protected readonly Bindable<WorkingBeatmap> Beatmap = new Bindable<WorkingBeatmap>();
private int lastBeat;
private TimingControlPoint lastTimingPoint;
/// <summary>
/// The amount of time before a beat we should fire <see cref="OnNewBeat(int, TimingControlPoint, EffectControlPoint, TrackAmplitudes)"/>.
/// This allows for adding easing to animations that may be synchronised to the beat.
/// </summary>
protected double EarlyActivationMilliseconds;
protected override void Update()
{
if (Beatmap.Value?.Track == null)
return;
double currentTrackTime = Beatmap.Value.Track.CurrentTime + EarlyActivationMilliseconds;
TimingControlPoint timingPoint = Beatmap.Value.Beatmap.ControlPointInfo.TimingPointAt(currentTrackTime);
EffectControlPoint effectPoint = Beatmap.Value.Beatmap.ControlPointInfo.EffectPointAt(currentTrackTime);
if (timingPoint.BeatLength == 0)
return;
int beatIndex = (int)((currentTrackTime - timingPoint.Time) / timingPoint.BeatLength);
// The beats before the start of the first control point are off by 1, this should do the trick
if (currentTrackTime < timingPoint.Time)
beatIndex--;
if (timingPoint == lastTimingPoint && beatIndex == lastBeat)
return;
double offsetFromBeat = (timingPoint.Time - currentTrackTime) % timingPoint.BeatLength;
using (BeginDelayedSequence(offsetFromBeat, true))
OnNewBeat(beatIndex, timingPoint, effectPoint, Beatmap.Value.Track.CurrentAmplitudes);
lastBeat = beatIndex;
lastTimingPoint = timingPoint;
}
[BackgroundDependencyLoader]
private void load(OsuGameBase game)
{
Beatmap.BindTo(game.Beatmap);
}
protected virtual void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, TrackAmplitudes amplitudes)
{
}
}
}

View File

@ -0,0 +1,16 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
namespace osu.Game.Graphics.Containers
{
public class ReverseDepthFillFlowContainer<T> : FillFlowContainer<T> where T : Drawable
{
protected override IComparer<Drawable> DepthComparer => new ReverseCreationOrderDepthComparer();
protected override IEnumerable<T> FlowingChildren => base.FlowingChildren.Reverse();
}
}

View File

@ -0,0 +1,171 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Configuration;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
namespace osu.Game.Graphics.Containers
{
/// <summary>
/// A container that can scroll to each section inside it.
/// </summary>
public class SectionsContainer : Container
{
private Drawable expandableHeader, fixedHeader, footer;
public readonly ScrollContainer ScrollContainer;
private readonly Container<Drawable> sectionsContainer;
public Drawable ExpandableHeader
{
get { return expandableHeader; }
set
{
if (value == expandableHeader) return;
if (expandableHeader != null)
Remove(expandableHeader);
expandableHeader = value;
if (value == null) return;
Add(expandableHeader);
lastKnownScroll = float.NaN;
}
}
public Drawable FixedHeader
{
get { return fixedHeader; }
set
{
if (value == fixedHeader) return;
if (fixedHeader != null)
Remove(fixedHeader);
fixedHeader = value;
if (value == null) return;
Add(fixedHeader);
lastKnownScroll = float.NaN;
}
}
public Drawable Footer
{
get { return footer; }
set
{
if (value == footer) return;
if (footer != null)
ScrollContainer.Remove(footer);
footer = value;
if (value == null) return;
footer.Anchor |= Anchor.y2;
footer.Origin |= Anchor.y2;
ScrollContainer.Add(footer);
lastKnownScroll = float.NaN;
}
}
public Bindable<Drawable> SelectedSection { get; } = new Bindable<Drawable>();
protected virtual Container<Drawable> CreateScrollContentContainer()
=> new FillFlowContainer
{
Direction = FillDirection.Vertical,
AutoSizeAxes = Axes.Both
};
private List<Drawable> sections = new List<Drawable>();
public IEnumerable<Drawable> Sections
{
get { return sections; }
set
{
foreach (var section in sections)
sectionsContainer.Remove(section);
sections = value.ToList();
if (sections.Count == 0) return;
sectionsContainer.Add(sections);
SelectedSection.Value = sections[0];
lastKnownScroll = float.NaN;
}
}
private float headerHeight, footerHeight;
private readonly MarginPadding originalSectionsMargin;
private void updateSectionsMargin()
{
if (sections.Count == 0) return;
var newMargin = originalSectionsMargin;
newMargin.Top += headerHeight;
newMargin.Bottom += footerHeight;
sectionsContainer.Margin = newMargin;
}
public SectionsContainer()
{
Add(ScrollContainer = new ScrollContainer()
{
RelativeSizeAxes = Axes.Both,
Masking = false,
Children = new Drawable[] { sectionsContainer = CreateScrollContentContainer() }
});
originalSectionsMargin = sectionsContainer.Margin;
}
private float lastKnownScroll;
protected override void UpdateAfterChildren()
{
base.UpdateAfterChildren();
float headerH = (ExpandableHeader?.LayoutSize.Y ?? 0) + (FixedHeader?.LayoutSize.Y ?? 0);
float footerH = Footer?.LayoutSize.Y ?? 0;
if (headerH != headerHeight || footerH != footerHeight)
{
headerHeight = headerH;
footerHeight = footerH;
updateSectionsMargin();
}
float currentScroll = Math.Max(0, ScrollContainer.Current);
if (currentScroll != lastKnownScroll)
{
lastKnownScroll = currentScroll;
if (expandableHeader != null && fixedHeader != null)
{
float offset = Math.Min(expandableHeader.LayoutSize.Y, currentScroll);
expandableHeader.Y = -offset;
fixedHeader.Y = -offset + expandableHeader.LayoutSize.Y;
}
Drawable bestMatch = null;
float minDiff = float.MaxValue;
foreach (var section in sections)
{
float diff = Math.Abs(ScrollContainer.GetChildPosInContent(section) - currentScroll);
if (diff < minDiff)
{
minDiff = diff;
bestMatch = section;
}
}
if (bestMatch != null)
SelectedSection.Value = bestMatch;
}
}
}
}

View File

@ -113,7 +113,7 @@ namespace osu.Game.Graphics.UserInterface
}
}
protected class OsuDropdownHeader : DropdownHeader
public class OsuDropdownHeader : DropdownHeader
{
private readonly SpriteText label;
protected override string Label

View File

@ -0,0 +1,32 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using System.ComponentModel;
using System.Reflection;
using System.Collections.Generic;
namespace osu.Game.Graphics.UserInterface
{
public class OsuEnumDropdown<T> : OsuDropdown<T>
{
public OsuEnumDropdown()
{
if (!typeof(T).IsEnum)
throw new InvalidOperationException("OsuEnumDropdown only supports enums as the generic type argument");
List<KeyValuePair<string, T>> items = new List<KeyValuePair<string, T>>();
foreach(var val in (T[])Enum.GetValues(typeof(T)))
{
var field = typeof(T).GetField(Enum.GetName(typeof(T), val));
items.Add(
new KeyValuePair<string, T>(
field.GetCustomAttribute<DescriptionAttribute>()?.Description ?? Enum.GetName(typeof(T), val),
val
)
);
}
Items = items;
}
}
}

View File

@ -57,9 +57,9 @@ namespace osu.Game.Graphics.UserInterface
}
}
private class OsuTabItem : TabItem<T>
public class OsuTabItem : TabItem<T>
{
private readonly SpriteText text;
protected readonly SpriteText Text;
private readonly Box box;
private Color4? accentColour;
@ -70,7 +70,7 @@ namespace osu.Game.Graphics.UserInterface
{
accentColour = value;
if (!Active)
text.Colour = value;
Text.Colour = value;
}
}
@ -94,13 +94,13 @@ namespace osu.Game.Graphics.UserInterface
private void fadeActive()
{
box.FadeIn(transition_length, EasingTypes.OutQuint);
text.FadeColour(Color4.White, transition_length, EasingTypes.OutQuint);
Text.FadeColour(Color4.White, transition_length, EasingTypes.OutQuint);
}
private void fadeInactive()
{
box.FadeOut(transition_length, EasingTypes.OutQuint);
text.FadeColour(AccentColour, transition_length, EasingTypes.OutQuint);
Text.FadeColour(AccentColour, transition_length, EasingTypes.OutQuint);
}
protected override bool OnHover(InputState state)
@ -130,7 +130,7 @@ namespace osu.Game.Graphics.UserInterface
Children = new Drawable[]
{
text = new OsuSpriteText
Text = new OsuSpriteText
{
Margin = new MarginPadding { Top = 5, Bottom = 5 },
Origin = Anchor.BottomLeft,

View File

@ -41,6 +41,8 @@ namespace osu.Game
private DialogOverlay dialogOverlay;
private DirectOverlay direct;
private Intro intro
{
get
@ -70,6 +72,8 @@ namespace osu.Game
public void ToggleSettings() => settings.ToggleVisibility();
public void ToggleDirect() => direct.ToggleVisibility();
[BackgroundDependencyLoader]
private void load()
{
@ -82,7 +86,7 @@ namespace osu.Game
if (args?.Length > 0)
{
var paths = args.Where(a => !a.StartsWith(@"-"));
Task.Run(() => BeatmapDatabase.Import(paths));
Task.Run(() => BeatmapDatabase.Import(paths.ToArray()));
}
Dependencies.Cache(this);
@ -160,6 +164,7 @@ namespace osu.Game
});
//overlay elements
LoadComponentAsync(direct = new DirectOverlay { Depth = -1 }, mainContent.Add);
LoadComponentAsync(chat = new ChatOverlay { Depth = -1 }, mainContent.Add);
LoadComponentAsync(settings = new SettingsOverlay { Depth = -1 }, overlayContent.Add);
LoadComponentAsync(musicController = new MusicController
@ -249,6 +254,9 @@ namespace osu.Game
case Key.O:
settings.ToggleVisibility();
return true;
case Key.D:
direct.ToggleVisibility();
return true;
}
}
@ -280,6 +288,7 @@ namespace osu.Game
Toolbar.State = Visibility.Hidden;
musicController.State = Visibility.Hidden;
chat.State = Visibility.Hidden;
direct.State = Visibility.Hidden;
}
else
{

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 System.Linq;
using OpenTK;
using OpenTK.Graphics;
using osu.Framework.Allocation;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Textures;
using osu.Framework.Localisation;
using osu.Game.Database;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
namespace osu.Game.Overlays.Direct
{
public class DirectGridPanel : DirectPanel
{
private const float horizontal_padding = 10;
private const float vertical_padding = 5;
private FillFlowContainer bottomPanel;
public DirectGridPanel(BeatmapSetInfo beatmap) : base(beatmap)
{
Height = 140 + vertical_padding; //full height of all the elements plus vertical padding (autosize uses the image)
CornerRadius = 4;
Masking = true;
EdgeEffect = new EdgeEffect
{
Type = EdgeEffectType.Shadow,
Offset = new Vector2(0f, 1f),
Radius = 3f,
Colour = Color4.Black.Opacity(0.25f),
};
}
protected override void LoadComplete()
{
base.LoadComplete();
FadeInFromZero(200, EasingTypes.Out);
bottomPanel.LayoutDuration = 200;
bottomPanel.LayoutEasing = EasingTypes.Out;
bottomPanel.Origin = Anchor.BottomLeft;
}
[BackgroundDependencyLoader]
private void load(OsuColour colours, LocalisationEngine localisation, TextureStore textures)
{
Children = new[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Color4.Black,
},
GetBackground(textures),
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Color4.Black.Opacity(0.5f),
},
bottomPanel = new FillFlowContainer
{
Anchor = Anchor.BottomLeft,
Origin = Anchor.TopLeft,
Direction = FillDirection.Vertical,
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Spacing = new Vector2(0f, vertical_padding),
Children = new Drawable[]
{
new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Padding = new MarginPadding { Left = horizontal_padding, Right = horizontal_padding },
Direction = FillDirection.Vertical,
Children = new[]
{
new OsuSpriteText
{
Text = localisation.GetUnicodePreference(SetInfo.Metadata.TitleUnicode, SetInfo.Metadata.Title),
TextSize = 18,
Font = @"Exo2.0-BoldItalic",
},
new OsuSpriteText
{
Text = localisation.GetUnicodePreference(SetInfo.Metadata.ArtistUnicode, SetInfo.Metadata.Artist),
Font = @"Exo2.0-BoldItalic",
},
},
},
new Container
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
},
new FillFlowContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Direction = FillDirection.Vertical,
Padding = new MarginPadding
{
Top = vertical_padding,
Bottom = vertical_padding,
Left = horizontal_padding,
Right = horizontal_padding,
},
Children = new Drawable[]
{
new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal,
Children = new[]
{
new OsuSpriteText
{
Text = "mapped by ",
TextSize = 14,
Shadow = false,
Colour = colours.Gray5,
},
new OsuSpriteText
{
Text = SetInfo.Metadata.Author,
TextSize = 14,
Font = @"Exo2.0-SemiBoldItalic",
Shadow = false,
Colour = colours.BlueDark,
},
},
},
new Container
{
AutoSizeAxes = Axes.X,
Height = 14,
Children = new[]
{
new OsuSpriteText
{
Text = $"from {SetInfo.Metadata.Source}",
TextSize = 14,
Shadow = false,
Colour = colours.Gray5,
Alpha = string.IsNullOrEmpty(SetInfo.Metadata.Source) ? 0f : 1f,
},
},
},
new FillFlowContainer
{
AutoSizeAxes = Axes.X,
Height = 20,
Margin = new MarginPadding { Top = vertical_padding, Bottom = vertical_padding },
Children = GetDifficultyIcons(),
},
},
},
},
},
},
},
new FillFlowContainer
{
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Vertical,
Margin = new MarginPadding { Top = vertical_padding, Right = vertical_padding },
Children = new[]
{
new Statistic(FontAwesome.fa_play_circle, SetInfo.Beatmaps.FirstOrDefault()?.OnlineInfo.PlayCount ?? 0)
{
Margin = new MarginPadding { Right = 1 },
},
new Statistic(FontAwesome.fa_heart, SetInfo.Beatmaps.FirstOrDefault()?.OnlineInfo.FavouriteCount ?? 0),
},
},
};
}
}
}

View File

@ -0,0 +1,197 @@
// 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 OpenTK.Graphics;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Colour;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Database;
using osu.Framework.Allocation;
using osu.Framework.Localisation;
using osu.Framework.Graphics.Textures;
using System.Linq;
using osu.Framework.Input;
namespace osu.Game.Overlays.Direct
{
public class DirectListPanel : DirectPanel
{
private const float horizontal_padding = 10;
private const float vertical_padding = 5;
private const float height = 70;
public DirectListPanel(BeatmapSetInfo beatmap) : base(beatmap)
{
RelativeSizeAxes = Axes.X;
Height = height;
CornerRadius = 5;
Masking = true;
EdgeEffect = new EdgeEffect
{
Type = EdgeEffectType.Shadow,
Offset = new Vector2(0f, 1f),
Radius = 3f,
Colour = Color4.Black.Opacity(0.25f),
};
}
protected override void LoadComplete()
{
base.LoadComplete();
FadeInFromZero(200, EasingTypes.Out);
}
[BackgroundDependencyLoader]
private void load(LocalisationEngine localisation, TextureStore textures)
{
Children = new[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Color4.Black,
},
GetBackground(textures),
new Box
{
RelativeSizeAxes = Axes.Both,
ColourInfo = ColourInfo.GradientHorizontal(Color4.Black.Opacity(0.25f), Color4.Black.Opacity(0.75f)),
},
new Container
{
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding { Top = vertical_padding, Bottom = vertical_padding, Left = horizontal_padding, Right = vertical_padding },
Children = new Drawable[]
{
new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Vertical,
Children = new Drawable[]
{
new OsuSpriteText
{
Current = localisation.GetUnicodePreference(SetInfo.Metadata.TitleUnicode, SetInfo.Metadata.Title),
TextSize = 18,
Font = @"Exo2.0-BoldItalic",
},
new OsuSpriteText
{
Current = localisation.GetUnicodePreference(SetInfo.Metadata.ArtistUnicode, SetInfo.Metadata.Artist),
Font = @"Exo2.0-BoldItalic",
},
new FillFlowContainer
{
AutoSizeAxes = Axes.X,
Height = 20,
Margin = new MarginPadding { Top = vertical_padding, Bottom = vertical_padding },
Children = GetDifficultyIcons(),
},
},
},
new FillFlowContainer
{
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Vertical,
Margin = new MarginPadding { Right = height - vertical_padding * 2 + vertical_padding },
Children = new Drawable[]
{
new Statistic(FontAwesome.fa_play_circle, SetInfo.Beatmaps.FirstOrDefault()?.OnlineInfo.PlayCount ?? 0)
{
Margin = new MarginPadding { Right = 1 },
},
new Statistic(FontAwesome.fa_heart, SetInfo.Beatmaps.FirstOrDefault()?.OnlineInfo.FavouriteCount ?? 0),
new FillFlowContainer
{
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal,
Children = new[]
{
new OsuSpriteText
{
Text = "mapped by ",
TextSize = 14,
},
new OsuSpriteText
{
Text = SetInfo.Metadata.Author,
TextSize = 14,
Font = @"Exo2.0-SemiBoldItalic",
},
},
},
new OsuSpriteText
{
Text = $"from {SetInfo.Metadata.Source}",
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
TextSize = 14,
Alpha = string.IsNullOrEmpty(SetInfo.Metadata.Source) ? 0f : 1f,
},
},
},
new DownloadButton
{
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
Size = new Vector2(height - vertical_padding * 2),
},
},
},
};
}
private class DownloadButton : ClickableContainer
{
private readonly TextAwesome icon;
public DownloadButton()
{
Children = new Drawable[]
{
icon = new TextAwesome
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
UseFullGlyphHeight = false,
TextSize = 30,
Icon = FontAwesome.fa_osu_chevron_down_o,
},
};
}
protected override bool OnMouseDown(InputState state, MouseDownEventArgs args)
{
icon.ScaleTo(0.9f, 1000, EasingTypes.Out);
return base.OnMouseDown(state, args);
}
protected override bool OnMouseUp(InputState state, MouseUpEventArgs args)
{
icon.ScaleTo(1f, 500, EasingTypes.OutElastic);
return base.OnMouseUp(state, args);
}
protected override bool OnHover(InputState state)
{
icon.ScaleTo(1.1f, 500, EasingTypes.OutElastic);
return base.OnHover(state);
}
protected override void OnHoverLost(InputState state)
{
icon.ScaleTo(1f, 500, EasingTypes.OutElastic);
}
}
}
}

View File

@ -0,0 +1,88 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Collections.Generic;
using System.Linq;
using OpenTK;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Textures;
using osu.Game.Beatmaps.Drawables;
using osu.Game.Database;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
namespace osu.Game.Overlays.Direct
{
public abstract class DirectPanel : Container
{
protected readonly BeatmapSetInfo SetInfo;
protected DirectPanel(BeatmapSetInfo setInfo)
{
SetInfo = setInfo;
}
protected IEnumerable<DifficultyIcon> GetDifficultyIcons()
{
var icons = new List<DifficultyIcon>();
foreach (var b in SetInfo.Beatmaps)
icons.Add(new DifficultyIcon(b));
return icons;
}
protected Drawable GetBackground(TextureStore textures)
{
return new AsyncLoadWrapper(new BeatmapBackgroundSprite(new OnlineWorkingBeatmap(SetInfo.Beatmaps.FirstOrDefault(), textures, null))
{
FillMode = FillMode.Fill,
OnLoadComplete = d => d.FadeInFromZero(400, EasingTypes.Out),
}) { RelativeSizeAxes = Axes.Both };
}
public class Statistic : FillFlowContainer
{
private readonly SpriteText text;
private int value;
public int Value
{
get { return value; }
set
{
this.value = value;
text.Text = Value.ToString(@"N0");
}
}
public Statistic(FontAwesome icon, int value = 0)
{
Anchor = Anchor.TopRight;
Origin = Anchor.TopRight;
AutoSizeAxes = Axes.Both;
Direction = FillDirection.Horizontal;
Spacing = new Vector2(5f, 0f);
Children = new Drawable[]
{
text = new OsuSpriteText
{
Font = @"Exo2.0-SemiBoldItalic",
},
new TextAwesome
{
Icon = icon,
Shadow = true,
TextSize = 14,
Margin = new MarginPadding { Top = 1 },
},
};
Value = value;
}
}
}
}

View File

@ -0,0 +1,233 @@
// 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 OpenTK.Graphics;
using osu.Framework.Allocation;
using osu.Framework.Configuration;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Game.Database;
using osu.Game.Graphics;
using osu.Game.Graphics.UserInterface;
namespace osu.Game.Overlays.Direct
{
public class FilterControl : Container
{
public static readonly float HEIGHT = 35 + 32 + 30 + padding * 2; // search + mode toggle buttons + sort tabs + padding
private const float padding = 10;
private readonly Box tabStrip;
private readonly FillFlowContainer<RulesetToggleButton> modeButtons;
public readonly SearchTextBox Search;
public readonly SortTabControl SortTabs;
public readonly OsuEnumDropdown<RankStatus> RankStatusDropdown;
public readonly Bindable<DirectOverlay.PanelDisplayStyle> DisplayStyle = new Bindable<DirectOverlay.PanelDisplayStyle>();
protected override bool InternalContains(Vector2 screenSpacePos) => base.InternalContains(screenSpacePos) || RankStatusDropdown.Contains(screenSpacePos);
public FilterControl()
{
RelativeSizeAxes = Axes.X;
Height = HEIGHT;
DisplayStyle.Value = DirectOverlay.PanelDisplayStyle.Grid;
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = OsuColour.FromHex(@"384552"),
Alpha = 0.9f,
},
tabStrip = new Box
{
Anchor = Anchor.BottomLeft,
Origin = Anchor.TopLeft,
RelativeSizeAxes = Axes.X,
Height = 1,
},
new FillFlowContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Padding = new MarginPadding { Left = DirectOverlay.WIDTH_PADDING, Right = DirectOverlay.WIDTH_PADDING },
Children = new Drawable[]
{
Search = new DirectSearchTextBox
{
RelativeSizeAxes = Axes.X,
Margin = new MarginPadding { Top = padding },
},
modeButtons = new FillFlowContainer<RulesetToggleButton>
{
AutoSizeAxes = Axes.Both,
Spacing = new Vector2(padding, 0f),
Margin = new MarginPadding { Top = padding },
},
SortTabs = new SortTabControl
{
RelativeSizeAxes = Axes.X,
},
},
},
new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
Spacing = new Vector2(10f, 0f),
Direction = FillDirection.Horizontal,
Margin = new MarginPadding { Top = HEIGHT - SlimEnumDropdown<DirectTab>.HEIGHT - padding, Right = DirectOverlay.WIDTH_PADDING },
Children = new Drawable[]
{
new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Spacing = new Vector2(5f, 0f),
Direction = FillDirection.Horizontal,
Children = new[]
{
new DisplayStyleToggleButton(FontAwesome.fa_th_large, DirectOverlay.PanelDisplayStyle.Grid, DisplayStyle),
new DisplayStyleToggleButton(FontAwesome.fa_list_ul, DirectOverlay.PanelDisplayStyle.List, DisplayStyle),
},
},
RankStatusDropdown = new SlimEnumDropdown<RankStatus>
{
RelativeSizeAxes = Axes.None,
Width = 160f,
},
},
},
};
RankStatusDropdown.Current.Value = RankStatus.RankedApproved;
SortTabs.Current.Value = SortCriteria.Title;
SortTabs.Current.TriggerChange();
}
[BackgroundDependencyLoader(true)]
private void load(OsuGame game, RulesetDatabase rulesets, OsuColour colours)
{
tabStrip.Colour = colours.Yellow;
RankStatusDropdown.AccentColour = colours.BlueDark;
var b = new Bindable<RulesetInfo>(); //backup bindable incase the game is null
foreach (var r in rulesets.AllRulesets)
{
modeButtons.Add(new RulesetToggleButton(game?.Ruleset ?? b, r));
}
}
private class DirectSearchTextBox : SearchTextBox
{
protected override Color4 BackgroundUnfocused => backgroundColour;
protected override Color4 BackgroundFocused => backgroundColour;
private Color4 backgroundColour;
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
backgroundColour = colours.Gray2.Opacity(0.9f);
}
}
private class RulesetToggleButton : ClickableContainer
{
private readonly TextAwesome icon;
private RulesetInfo ruleset;
public RulesetInfo Ruleset
{
get { return ruleset; }
set
{
ruleset = value;
icon.Icon = Ruleset.CreateInstance().Icon;
}
}
private readonly Bindable<RulesetInfo> bindable;
private void Bindable_ValueChanged(RulesetInfo obj)
{
icon.FadeTo(Ruleset.ID == obj?.ID ? 1f : 0.5f, 100);
}
public RulesetToggleButton(Bindable<RulesetInfo> bindable, RulesetInfo ruleset)
{
this.bindable = bindable;
AutoSizeAxes = Axes.Both;
Children = new[]
{
icon = new TextAwesome
{
Origin = Anchor.TopLeft,
Anchor = Anchor.TopLeft,
TextSize = 32,
}
};
Ruleset = ruleset;
bindable.ValueChanged += Bindable_ValueChanged;
Bindable_ValueChanged(bindable.Value);
Action = () => bindable.Value = Ruleset;
}
protected override void Dispose(bool isDisposing)
{
if (bindable != null)
bindable.ValueChanged -= Bindable_ValueChanged;
base.Dispose(isDisposing);
}
}
private class DisplayStyleToggleButton : ClickableContainer
{
private readonly TextAwesome icon;
private readonly DirectOverlay.PanelDisplayStyle style;
private readonly Bindable<DirectOverlay.PanelDisplayStyle> bindable;
public DisplayStyleToggleButton(FontAwesome icon, DirectOverlay.PanelDisplayStyle style, Bindable<DirectOverlay.PanelDisplayStyle> bindable)
{
this.bindable = bindable;
this.style = style;
Size = new Vector2(SlimEnumDropdown<DirectTab>.HEIGHT);
Children = new Drawable[]
{
this.icon = new TextAwesome
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Icon = icon,
TextSize = 18,
UseFullGlyphHeight = false,
Alpha = 0.5f,
},
};
bindable.ValueChanged += Bindable_ValueChanged;
Bindable_ValueChanged(bindable.Value);
Action = () => bindable.Value = this.style;
}
private void Bindable_ValueChanged(DirectOverlay.PanelDisplayStyle style)
{
icon.FadeTo(style == this.style ? 1.0f : 0.5f, 100);
}
protected override void Dispose(bool isDisposing)
{
bindable.ValueChanged -= Bindable_ValueChanged;
}
}
}
}

View File

@ -0,0 +1,124 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.ComponentModel;
using OpenTK;
using OpenTK.Graphics;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.UserInterface;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
using Container = osu.Framework.Graphics.Containers.Container;
namespace osu.Game.Overlays.Direct
{
public class Header : Container
{
public static readonly float HEIGHT = 90;
private readonly Box tabStrip;
public readonly OsuTabControl<DirectTab> Tabs;
public Header()
{
Height = HEIGHT;
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = OsuColour.FromHex(@"252f3a"),
},
new Container
{
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding { Left = DirectOverlay.WIDTH_PADDING, Right = DirectOverlay.WIDTH_PADDING },
Children = new Drawable[]
{
new FillFlowContainer
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.BottomLeft,
Position = new Vector2(-35f, 5f),
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal,
Spacing = new Vector2(10f, 0f),
Children = new Drawable[]
{
new TextAwesome
{
TextSize = 25,
Icon = FontAwesome.fa_osu_chevron_down_o,
},
new OsuSpriteText
{
TextSize = 25,
Text = @"osu!direct",
},
},
},
tabStrip = new Box
{
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
Width = 282, //todo: make this actually match the tab control's width instead of hardcoding
Height = 1,
},
Tabs = new DirectTabControl
{
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
RelativeSizeAxes = Axes.X,
},
},
},
};
Tabs.Current.Value = DirectTab.Search;
Tabs.Current.TriggerChange();
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
tabStrip.Colour = colours.Green;
}
private class DirectTabControl : OsuTabControl<DirectTab>
{
protected override TabItem<DirectTab> CreateTabItem(DirectTab value) => new DirectTabItem(value);
public DirectTabControl()
{
Height = 25;
AccentColour = Color4.White;
}
private class DirectTabItem : OsuTabItem
{
public DirectTabItem(DirectTab value) : base(value)
{
Text.TextSize = 15;
}
}
}
}
public enum DirectTab
{
Search,
[Description("Newest Maps")]
NewestMaps,
[Description("Top Rated")]
TopRated,
[Description("Most Played")]
MostPlayed
}
}

View File

@ -0,0 +1,43 @@
// 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.Graphics;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.UserInterface;
using osu.Game.Graphics.UserInterface;
namespace osu.Game.Overlays.Direct
{
public class SlimEnumDropdown<T> : OsuEnumDropdown<T>
{
public const float HEIGHT = 25;
protected override DropdownHeader CreateHeader() => new SlimDropdownHeader { AccentColour = AccentColour };
protected override Menu CreateMenu() => new SlimMenu();
private class SlimDropdownHeader : OsuDropdownHeader
{
public SlimDropdownHeader()
{
Height = HEIGHT;
Icon.TextSize = 16;
Foreground.Padding = new MarginPadding { Top = 4, Bottom = 4, Left = 8, Right = 4 };
}
protected override void LoadComplete()
{
base.LoadComplete();
BackgroundColour = Color4.Black.Opacity(0.25f);
}
}
private class SlimMenu : OsuMenu
{
public SlimMenu()
{
Background.Colour = Color4.Black.Opacity(0.25f);
}
}
}
}

View File

@ -0,0 +1,117 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using OpenTK;
using OpenTK.Graphics;
using osu.Framework.Allocation;
using osu.Framework.Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.Input;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
namespace osu.Game.Overlays.Direct
{
public class SortTabControl : OsuTabControl<SortCriteria>
{
protected override TabItem<SortCriteria> CreateTabItem(SortCriteria value) => new SortTabItem(value);
public SortTabControl()
{
Height = 30;
}
private class SortTabItem : TabItem<SortCriteria>
{
private const float transition_duration = 100;
private readonly Box box;
public override bool Active
{
get { return base.Active; }
set
{
if (Active == value) return;
if (value)
slideActive();
else
slideInactive();
base.Active = value;
}
}
public SortTabItem(SortCriteria value) : base(value)
{
AutoSizeAxes = Axes.X;
RelativeSizeAxes = Axes.Y;
Children = new Drawable[]
{
new OsuSpriteText
{
Margin = new MarginPadding { Top = 8, Bottom = 8 },
Origin = Anchor.BottomLeft,
Anchor = Anchor.BottomLeft,
Text = (value as Enum).GetDescription() ?? value.ToString(),
TextSize = 14,
Font = @"Exo2.0-Bold",
},
box = new Box
{
RelativeSizeAxes = Axes.X,
Height = 5,
Scale = new Vector2(1f, 0f),
Colour = Color4.White,
Origin = Anchor.BottomLeft,
Anchor = Anchor.BottomLeft,
},
};
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
box.Colour = colours.Yellow;
}
protected override bool OnHover(InputState state)
{
if (!Active)
slideActive();
return true;
}
protected override void OnHoverLost(InputState state)
{
if (!Active)
slideInactive();
}
private void slideActive()
{
box.ScaleTo(new Vector2(1f), transition_duration);
}
private void slideInactive()
{
box.ScaleTo(new Vector2(1f, 0f), transition_duration);
}
}
}
public enum SortCriteria
{
Title,
Artist,
Creator,
Difficulty,
Ranked,
Rating,
}
}

View File

@ -0,0 +1,230 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Collections.Generic;
using System.Linq;
using OpenTK;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Input;
using osu.Game.Database;
using osu.Game.Graphics;
using osu.Game.Graphics.Backgrounds;
using osu.Game.Graphics.Sprites;
using osu.Game.Overlays.Direct;
using Container = osu.Framework.Graphics.Containers.Container;
namespace osu.Game.Overlays
{
public class DirectOverlay : WaveOverlayContainer
{
public static readonly int WIDTH_PADDING = 80;
private const float panel_padding = 10f;
private readonly FilterControl filter;
private readonly FillFlowContainer resultCountsContainer;
private readonly OsuSpriteText resultCountsText;
private readonly FillFlowContainer<DirectPanel> panels;
private IEnumerable<BeatmapSetInfo> beatmapSets;
public IEnumerable<BeatmapSetInfo> BeatmapSets
{
get { return beatmapSets; }
set
{
if (beatmapSets?.Equals(value) ?? false) return;
beatmapSets = value;
recreatePanels(filter.DisplayStyle.Value);
}
}
private ResultCounts resultAmounts;
public ResultCounts ResultAmounts
{
get { return resultAmounts; }
set
{
if (value == ResultAmounts) return;
resultAmounts = value;
updateResultCounts();
}
}
public DirectOverlay()
{
RelativeSizeAxes = Axes.Both;
// osu!direct colours are not part of the standard palette
FirstWaveColour = OsuColour.FromHex(@"19b0e2");
SecondWaveColour = OsuColour.FromHex(@"2280a2");
ThirdWaveColour = OsuColour.FromHex(@"005774");
FourthWaveColour = OsuColour.FromHex(@"003a4e");
Header header;
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = OsuColour.FromHex(@"485e74"),
},
new Container
{
RelativeSizeAxes = Axes.Both,
Masking = true,
Children = new[]
{
new Triangles
{
RelativeSizeAxes = Axes.Both,
TriangleScale = 5,
ColourLight = OsuColour.FromHex(@"465b71"),
ColourDark = OsuColour.FromHex(@"3f5265"),
},
},
},
new Container
{
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding { Top = Header.HEIGHT + FilterControl.HEIGHT },
Children = new[]
{
new ScrollContainer
{
RelativeSizeAxes = Axes.Both,
ScrollDraggerVisible = false,
Children = new Drawable[]
{
new FillFlowContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Direction = FillDirection.Vertical,
Children = new Drawable[]
{
resultCountsContainer = new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal,
Margin = new MarginPadding { Left = WIDTH_PADDING, Top = 6 },
Children = new Drawable[]
{
new OsuSpriteText
{
Text = "Found ",
TextSize = 15,
},
resultCountsText = new OsuSpriteText
{
TextSize = 15,
Font = @"Exo2.0-Bold",
},
}
},
panels = new FillFlowContainer<DirectPanel>
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Padding = new MarginPadding { Top = panel_padding, Bottom = panel_padding, Left = WIDTH_PADDING, Right = WIDTH_PADDING },
Spacing = new Vector2(panel_padding),
},
},
},
},
},
},
},
filter = new FilterControl
{
RelativeSizeAxes = Axes.X,
Margin = new MarginPadding { Top = Header.HEIGHT },
},
header = new Header
{
RelativeSizeAxes = Axes.X,
},
};
header.Tabs.Current.ValueChanged += tab => { if (tab != DirectTab.Search) filter.Search.Current.Value = string.Empty; };
filter.Search.Exit = Hide;
filter.Search.Current.ValueChanged += text => { if (text != string.Empty) header.Tabs.Current.Value = DirectTab.Search; };
filter.DisplayStyle.ValueChanged += recreatePanels;
updateResultCounts();
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
resultCountsContainer.Colour = colours.Yellow;
}
private void updateResultCounts()
{
resultCountsContainer.FadeTo(ResultAmounts == null ? 0f : 1f, 200, EasingTypes.Out);
if (ResultAmounts == null) return;
resultCountsText.Text = pluralize("Artist", ResultAmounts.Artists) + ", " +
pluralize("Song", ResultAmounts.Songs) + ", " +
pluralize("Tag", ResultAmounts.Tags);
}
private string pluralize(string prefix, int value)
{
return $@"{value} {prefix}" + (value == 1 ? string.Empty : @"s");
}
private void recreatePanels(PanelDisplayStyle displayStyle)
{
if (BeatmapSets == null) return;
panels.Children = BeatmapSets.Select(b => displayStyle == PanelDisplayStyle.Grid ? (DirectPanel)new DirectGridPanel(b) { Width = 400 } : new DirectListPanel(b));
}
protected override bool OnFocus(InputState state)
{
filter.Search.TriggerFocus();
return false;
}
protected override void PopIn()
{
base.PopIn();
filter.Search.HoldFocus = true;
}
protected override void PopOut()
{
base.PopOut();
filter.Search.HoldFocus = false;
}
public class ResultCounts
{
public readonly int Artists;
public readonly int Songs;
public readonly int Tags;
public ResultCounts(int artists, int songs, int tags)
{
Artists = artists;
Songs = songs;
Tags = tags;
}
}
public enum PanelDisplayStyle
{
Grid,
List,
}
}
}

View File

@ -12,6 +12,7 @@ using osu.Game.Graphics.UserInterface;
using osu.Game.Online.API;
using OpenTK;
using osu.Framework.Input;
using osu.Game.Users;
namespace osu.Game.Overlays.Settings.Sections.General
{
@ -73,9 +74,9 @@ namespace osu.Game.Overlays.Settings.Sections.General
case APIState.Online:
Children = new Drawable[]
{
new OsuSpriteText
new UserPanel(api.LocalUser.Value)
{
Text = $"Connected as {api.Username}!",
RelativeSizeAxes = Axes.X,
},
new OsuButton
{

View File

@ -4,6 +4,7 @@
using osu.Framework.Allocation;
using osu.Framework.Configuration;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
namespace osu.Game.Overlays.Settings.Sections.Graphics
{
@ -11,11 +12,12 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
{
protected override string Header => "Layout";
private SettingsSlider<double> letterboxPositionX;
private SettingsSlider<double> letterboxPositionY;
private FillFlowContainer letterboxSettings;
private Bindable<bool> letterboxing;
private const int transition_duration = 400;
[BackgroundDependencyLoader]
private void load(FrameworkConfigManager config)
{
@ -33,34 +35,40 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
LabelText = "Letterboxing",
Bindable = letterboxing,
},
letterboxPositionX = new SettingsSlider<double>
letterboxSettings = new FillFlowContainer
{
LabelText = "Horizontal position",
Bindable = config.GetBindable<double>(FrameworkSetting.LetterboxPositionX)
},
letterboxPositionY = new SettingsSlider<double>
{
LabelText = "Vertical position",
Bindable = config.GetBindable<double>(FrameworkSetting.LetterboxPositionY)
Direction = FillDirection.Vertical,
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
AutoSizeDuration = transition_duration,
AutoSizeEasing = EasingTypes.OutQuint,
Masking = true,
Children = new Drawable[]
{
new SettingsSlider<double>
{
LabelText = "Horizontal position",
Bindable = config.GetBindable<double>(FrameworkSetting.LetterboxPositionX)
},
new SettingsSlider<double>
{
LabelText = "Vertical position",
Bindable = config.GetBindable<double>(FrameworkSetting.LetterboxPositionY)
},
}
},
};
letterboxing.ValueChanged += visibilityChanged;
letterboxing.TriggerChange();
}
letterboxing.ValueChanged += isVisible =>
{
letterboxSettings.ClearTransforms();
letterboxSettings.AutoSizeAxes = isVisible ? Axes.Y : Axes.None;
private void visibilityChanged(bool newVisibility)
{
if (newVisibility)
{
letterboxPositionX.Show();
letterboxPositionY.Show();
}
else
{
letterboxPositionX.Hide();
letterboxPositionY.Hide();
}
if(!isVisible)
letterboxSettings.ResizeHeightTo(0, transition_duration, EasingTypes.OutQuint);
};
letterboxing.TriggerChange();
}
}
}

View File

@ -1,32 +1,17 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using System.Reflection;
using System.ComponentModel;
using System.Collections.Generic;
using osu.Framework.Graphics;
using osu.Game.Graphics.UserInterface;
namespace osu.Game.Overlays.Settings
{
public class SettingsEnumDropdown<T> : SettingsDropdown<T>
{
public SettingsEnumDropdown()
protected override Drawable CreateControl() => new OsuEnumDropdown<T>
{
if (!typeof(T).IsEnum)
throw new InvalidOperationException("SettingsDropdown only supports enums as the generic type argument");
List<KeyValuePair<string, T>> items = new List<KeyValuePair<string, T>>();
foreach(var val in (T[])Enum.GetValues(typeof(T)))
{
var field = typeof(T).GetField(Enum.GetName(typeof(T), val));
items.Add(
new KeyValuePair<string, T>(
field.GetCustomAttribute<DescriptionAttribute>()?.Description ?? Enum.GetName(typeof(T), val),
val
)
);
}
Items = items;
}
Margin = new MarginPadding { Top = 5 },
RelativeSizeAxes = Axes.X,
};
}
}

View File

@ -1,34 +1,16 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
using OpenTK.Graphics;
namespace osu.Game.Overlays.Settings
{
public class SettingsHeader : Container
{
public SearchTextBox SearchTextBox;
private Box background;
private readonly Func<float> currentScrollOffset;
public Action Exit;
/// <param name="currentScrollOffset">A reference to the current scroll position of the ScrollContainer we are contained within.</param>
public SettingsHeader(Func<float> currentScrollOffset)
{
this.currentScrollOffset = currentScrollOffset;
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
@ -37,11 +19,6 @@ namespace osu.Game.Overlays.Settings
Children = new Drawable[]
{
background = new Box
{
Colour = Color4.Black,
RelativeSizeAxes = Axes.Both,
},
new FillFlowContainer
{
AutoSizeAxes = Axes.Y,
@ -53,7 +30,8 @@ namespace osu.Game.Overlays.Settings
{
Text = "settings",
TextSize = 40,
Margin = new MarginPadding {
Margin = new MarginPadding
{
Left = SettingsOverlay.CONTENT_MARGINS,
Top = Toolbar.Toolbar.TOOLTIP_HEIGHT
},
@ -63,45 +41,15 @@ namespace osu.Game.Overlays.Settings
Colour = colours.Pink,
Text = "Change the way osu! behaves",
TextSize = 18,
Margin = new MarginPadding {
Margin = new MarginPadding
{
Left = SettingsOverlay.CONTENT_MARGINS,
Bottom = 30
},
},
SearchTextBox = new SearchTextBox
{
RelativeSizeAxes = Axes.X,
Origin = Anchor.TopCentre,
Anchor = Anchor.TopCentre,
Width = 0.95f,
Margin = new MarginPadding {
Top = 20,
Bottom = 20
},
Exit = () => Exit(),
},
}
}
};
}
protected override void UpdateAfterChildren()
{
base.UpdateAfterChildren();
// the point at which we will start anchoring to the top.
float anchorOffset = SearchTextBox.Y;
float scrollPosition = currentScrollOffset();
// we want to anchor the search field to the top of the screen when scrolling.
Margin = new MarginPadding { Top = Math.Max(0, scrollPosition - anchorOffset) };
// we don't want the header to scroll when scrolling beyond the upper extent.
Y = Math.Min(0, scrollPosition);
// we get darker as scroll progresses
background.Alpha = Math.Min(1, scrollPosition / anchorOffset) * 0.5f;
}
}
}

View File

@ -21,7 +21,7 @@ namespace osu.Game.Overlays.Settings
private readonly Box backgroundBox;
private readonly Box selectionIndicator;
private readonly Container text;
public Action Action;
public Action<SettingsSection> Action;
private SettingsSection section;
public SettingsSection Section
@ -75,6 +75,7 @@ namespace osu.Game.Overlays.Settings
{
Width = Sidebar.DEFAULT_WIDTH,
RelativeSizeAxes = Axes.Y,
Colour = OsuColour.Gray(0.6f),
Children = new[]
{
headerText = new OsuSpriteText
@ -110,7 +111,7 @@ namespace osu.Game.Overlays.Settings
protected override bool OnClick(InputState state)
{
Action?.Invoke();
Action?.Invoke(section);
backgroundBox.FlashColour(Color4.White, 400);
return true;
}

View File

@ -7,10 +7,11 @@ using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Game.Overlays.Settings;
using System;
using osu.Game.Overlays.Settings.Sections;
using osu.Framework.Input;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.UserInterface;
using osu.Game.Overlays.Settings;
using osu.Game.Overlays.Settings.Sections;
namespace osu.Game.Overlays
{
@ -26,18 +27,13 @@ namespace osu.Game.Overlays
private const float sidebar_padding = 10;
private ScrollContainer scrollContainer;
private Sidebar sidebar;
private SidebarButton[] sidebarButtons;
private SettingsSection[] sections;
private SidebarButton selectedSidebarButton;
private SettingsHeader header;
private SettingsSectionsContainer sectionsContainer;
private SettingsFooter footer;
private SearchContainer searchContainer;
private float lastKnownScroll;
private SearchTextBox searchTextBox;
public SettingsOverlay()
{
@ -48,7 +44,7 @@ namespace osu.Game.Overlays
[BackgroundDependencyLoader(permitNulls: true)]
private void load(OsuGame game)
{
sections = new SettingsSection[]
var sections = new SettingsSection[]
{
new GeneralSection(),
new GraphicsSection(),
@ -68,27 +64,27 @@ namespace osu.Game.Overlays
Colour = Color4.Black,
Alpha = 0.6f,
},
scrollContainer = new ScrollContainer
sectionsContainer = new SettingsSectionsContainer
{
ScrollDraggerVisible = false,
RelativeSizeAxes = Axes.Y,
Width = width,
Margin = new MarginPadding { Left = SIDEBAR_WIDTH },
Children = new Drawable[]
ExpandableHeader = new SettingsHeader(),
FixedHeader = searchTextBox = new SearchTextBox
{
searchContainer = new SearchContainer
RelativeSizeAxes = Axes.X,
Origin = Anchor.TopCentre,
Anchor = Anchor.TopCentre,
Width = 0.95f,
Margin = new MarginPadding
{
AutoSizeAxes = Axes.Y,
RelativeSizeAxes = Axes.X,
Direction = FillDirection.Vertical,
Children = sections,
Top = 20,
Bottom = 20
},
footer = new SettingsFooter(),
header = new SettingsHeader(() => scrollContainer.Current)
{
Exit = Hide,
},
}
Exit = Hide,
},
Sections = sections,
Footer = new SettingsFooter()
},
sidebar = new Sidebar
{
@ -96,84 +92,89 @@ namespace osu.Game.Overlays
Children = sidebarButtons = sections.Select(section =>
new SidebarButton
{
Selected = sections[0] == section,
Section = section,
Action = () => scrollContainer.ScrollIntoView(section),
Action = sectionsContainer.ScrollContainer.ScrollIntoView,
}
).ToArray()
}
};
header.SearchTextBox.Current.ValueChanged += newValue => searchContainer.SearchTerm = newValue;
selectedSidebarButton = sidebarButtons[0];
selectedSidebarButton.Selected = true;
scrollContainer.Padding = new MarginPadding { Top = game?.Toolbar.DrawHeight ?? 0 };
}
protected override void UpdateAfterChildren()
{
base.UpdateAfterChildren();
//we need to update these manually because we can't put the SettingsHeader inside the SearchContainer (due to its anchoring).
searchContainer.Y = header.DrawHeight;
footer.Y = searchContainer.Y + searchContainer.DrawHeight;
}
protected override void Update()
{
base.Update();
float currentScroll = scrollContainer.Current;
if (currentScroll != lastKnownScroll)
sectionsContainer.SelectedSection.ValueChanged += section =>
{
lastKnownScroll = currentScroll;
selectedSidebarButton.Selected = false;
selectedSidebarButton = sidebarButtons.Single(b => b.Section == section);
selectedSidebarButton.Selected = true;
};
SettingsSection bestCandidate = null;
float bestDistance = float.MaxValue;
searchTextBox.Current.ValueChanged += newValue => sectionsContainer.SearchContainer.SearchTerm = newValue;
foreach (SettingsSection section in sections)
{
float distance = Math.Abs(scrollContainer.GetChildPosInContent(section) - currentScroll);
if (distance < bestDistance)
{
bestDistance = distance;
bestCandidate = section;
}
}
var previous = sidebarButtons.SingleOrDefault(sb => sb.Selected);
var next = sidebarButtons.SingleOrDefault(sb => sb.Section == bestCandidate);
if (previous != null) previous.Selected = false;
if (next != null) next.Selected = true;
}
sectionsContainer.Padding = new MarginPadding { Top = game?.Toolbar.DrawHeight ?? 0 };
}
protected override void PopIn()
{
base.PopIn();
scrollContainer.MoveToX(0, TRANSITION_LENGTH, EasingTypes.OutQuint);
sectionsContainer.MoveToX(0, TRANSITION_LENGTH, EasingTypes.OutQuint);
sidebar.MoveToX(0, TRANSITION_LENGTH, EasingTypes.OutQuint);
FadeTo(1, TRANSITION_LENGTH / 2);
header.SearchTextBox.HoldFocus = true;
searchTextBox.HoldFocus = true;
}
protected override void PopOut()
{
base.PopOut();
scrollContainer.MoveToX(-width, TRANSITION_LENGTH, EasingTypes.OutQuint);
sectionsContainer.MoveToX(-width, TRANSITION_LENGTH, EasingTypes.OutQuint);
sidebar.MoveToX(-SIDEBAR_WIDTH, TRANSITION_LENGTH, EasingTypes.OutQuint);
FadeTo(0, TRANSITION_LENGTH / 2);
header.SearchTextBox.HoldFocus = false;
header.SearchTextBox.TriggerFocusLost();
searchTextBox.HoldFocus = false;
searchTextBox.TriggerFocusLost();
}
protected override bool OnFocus(InputState state)
{
header.SearchTextBox.TriggerFocus(state);
searchTextBox.TriggerFocus(state);
return false;
}
private class SettingsSectionsContainer : SectionsContainer
{
public SearchContainer SearchContainer;
private readonly Box headerBackground;
protected override Container<Drawable> CreateScrollContentContainer()
=> SearchContainer = new SearchContainer
{
AutoSizeAxes = Axes.Y,
RelativeSizeAxes = Axes.X,
Direction = FillDirection.Vertical,
};
public SettingsSectionsContainer()
{
ScrollContainer.ScrollDraggerVisible = false;
Add(headerBackground = new Box
{
Colour = Color4.Black,
RelativeSizeAxes = Axes.X
});
}
protected override void UpdateAfterChildren()
{
base.UpdateAfterChildren();
// no null check because the usage of this class is strict
headerBackground.Height = ExpandableHeader.LayoutSize.Y + FixedHeader.LayoutSize.Y;
headerBackground.Y = ExpandableHeader.Y;
headerBackground.Alpha = -ExpandableHeader.Y / ExpandableHeader.LayoutSize.Y * 0.5f;
}
}
}
}

View File

@ -45,7 +45,7 @@ namespace osu.Game.Rulesets.Beatmaps
return new Beatmap<T>
{
BeatmapInfo = original.BeatmapInfo,
TimingInfo = original.TimingInfo,
ControlPointInfo = original.ControlPointInfo,
HitObjects = original.HitObjects.SelectMany(h => convert(h, original)).ToList()
};
}

View File

@ -2,7 +2,7 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Audio;
using osu.Game.Beatmaps.Timing;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Database;
using osu.Game.Rulesets.Objects.Types;
@ -33,31 +33,28 @@ namespace osu.Game.Rulesets.Objects
/// <summary>
/// Applies default values to this HitObject.
/// </summary>
/// <param name="controlPointInfo">The control points.</param>
/// <param name="difficulty">The difficulty settings to use.</param>
/// <param name="timing">The timing settings to use.</param>
public virtual void ApplyDefaults(TimingInfo timing, BeatmapDifficulty difficulty)
public virtual void ApplyDefaults(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty)
{
ControlPoint overridePoint;
ControlPoint timingPoint = timing.TimingPointAt(StartTime, out overridePoint);
ControlPoint samplePoint = overridePoint ?? timingPoint;
SoundControlPoint soundPoint = controlPointInfo.SoundPointAt(StartTime);
// Initialize first sample
Samples.ForEach(s => initializeSampleInfo(s, samplePoint));
Samples.ForEach(s => initializeSampleInfo(s, soundPoint));
// Initialize any repeat samples
var repeatData = this as IHasRepeats;
repeatData?.RepeatSamples?.ForEach(r => r.ForEach(s => initializeSampleInfo(s, samplePoint)));
repeatData?.RepeatSamples?.ForEach(r => r.ForEach(s => initializeSampleInfo(s, soundPoint)));
}
private void initializeSampleInfo(SampleInfo sample, ControlPoint controlPoint)
private void initializeSampleInfo(SampleInfo sample, SoundControlPoint soundPoint)
{
if (sample.Volume == 0)
sample.Volume = controlPoint?.SampleVolume ?? 0;
sample.Volume = soundPoint?.SampleVolume ?? 0;
// If the bank is not assigned a name, assign it from the control point
if (string.IsNullOrEmpty(sample.Bank))
sample.Bank = controlPoint?.SampleBank ?? @"normal";
sample.Bank = soundPoint?.SampleBank ?? @"normal";
}
}
}

View File

@ -143,7 +143,7 @@ namespace osu.Game.Rulesets.UI
// Apply defaults
foreach (var h in Beatmap.HitObjects)
h.ApplyDefaults(Beatmap.TimingInfo, Beatmap.BeatmapInfo.Difficulty);
h.ApplyDefaults(Beatmap.ControlPointInfo, Beatmap.BeatmapInfo.Difficulty);
// Post-process the beatmap
processor.PostProcess(Beatmap);

View File

@ -8,7 +8,10 @@ using osu.Framework.Audio.Track;
using osu.Framework.Configuration;
using osu.Framework.Screens;
using osu.Framework.Graphics;
using osu.Framework.MathUtils;
using osu.Game.Beatmaps.IO;
using osu.Game.Configuration;
using osu.Game.Database;
using osu.Game.Graphics.Containers;
using osu.Game.Screens.Backgrounds;
using OpenTK.Graphics;
@ -19,6 +22,8 @@ namespace osu.Game.Screens.Menu
{
private readonly OsuLogo logo;
public const string MENU_MUSIC_BEATMAP_HASH = "21c1271b91234385978b5418881fdd88";
/// <summary>
/// Whether we have loaded the menu previously.
/// </summary>
@ -27,7 +32,6 @@ namespace osu.Game.Screens.Menu
private MainMenu mainMenu;
private SampleChannel welcome;
private SampleChannel seeya;
private Track bgm;
internal override bool HasLocalCursorDisplayed => true;
@ -60,15 +64,49 @@ namespace osu.Game.Screens.Menu
private Bindable<bool> menuVoice;
private Bindable<bool> menuMusic;
private Track track;
[BackgroundDependencyLoader]
private void load(AudioManager audio, OsuConfigManager config)
private void load(AudioManager audio, OsuConfigManager config, BeatmapDatabase beatmaps, Framework.Game game)
{
menuVoice = config.GetBindable<bool>(OsuSetting.MenuVoice);
menuMusic = config.GetBindable<bool>(OsuSetting.MenuMusic);
bgm = audio.Track.Get(@"circles");
bgm.Looping = true;
var trackManager = audio.Track;
BeatmapSetInfo setInfo = null;
if (!menuMusic)
{
var query = beatmaps.Query<BeatmapSetInfo>().Where(b => !b.DeletePending);
int count = query.Count();
if (count > 0)
setInfo = query.ElementAt(RNG.Next(0, count - 1));
}
if (setInfo == null)
{
var query = beatmaps.Query<BeatmapSetInfo>().Where(b => b.Hash == MENU_MUSIC_BEATMAP_HASH);
setInfo = query.FirstOrDefault();
if (setInfo == null)
{
// we need to import the default menu background beatmap
beatmaps.Import(new OszArchiveReader(game.Resources.GetStream(@"Tracks/circles.osz")));
setInfo = query.First();
setInfo.DeletePending = true;
beatmaps.Update(setInfo, false);
}
}
beatmaps.GetChildren(setInfo);
Beatmap = beatmaps.GetWorkingBeatmap(setInfo.Beatmaps[0]);
track = Beatmap.Track;
trackManager.SetExclusive(track);
welcome = audio.Sample.Get(@"welcome");
seeya = audio.Sample.Get(@"seeya");
@ -83,8 +121,7 @@ namespace osu.Game.Screens.Menu
Scheduler.AddDelayed(delegate
{
if (menuMusic)
bgm.Start();
track.Start();
LoadComponentAsync(mainMenu = new MainMenu());

View File

@ -1,18 +1,12 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Threading.Tasks;
using OpenTK;
using OpenTK.Input;
using osu.Framework.Allocation;
using osu.Framework.Audio.Track;
using osu.Framework.Configuration;
using osu.Framework.Graphics;
using osu.Framework.Input;
using osu.Framework.MathUtils;
using osu.Framework.Screens;
using osu.Game.Configuration;
using osu.Game.Database;
using osu.Game.Graphics.Containers;
using osu.Game.Screens.Backgrounds;
using osu.Game.Screens.Charts;
@ -54,37 +48,20 @@ namespace osu.Game.Screens.Menu
OnSolo = delegate { Push(consumeSongSelect()); },
OnMulti = delegate { Push(new Lobby()); },
OnExit = delegate { Exit(); },
}
},
new MenuSideFlashes(),
}
}
};
}
private Bindable<bool> menuMusic;
private TrackManager trackManager;
[BackgroundDependencyLoader]
private void load(OsuGame game, OsuConfigManager config, BeatmapDatabase beatmaps)
private void load(OsuGame game)
{
menuMusic = config.GetBindable<bool>(OsuSetting.MenuMusic);
LoadComponentAsync(background);
if (!menuMusic)
{
trackManager = game.Audio.Track;
var query = beatmaps.Query<BeatmapSetInfo>().Where(b => !b.DeletePending);
int count = query.Count();
if (count > 0)
{
var beatmap = query.ElementAt(RNG.Next(0, count - 1));
beatmaps.GetChildren(beatmap);
Beatmap = beatmaps.GetWorkingBeatmap(beatmap.Beatmaps[0]);
}
}
buttons.OnSettings = game.ToggleSettings;
buttons.OnDirect = game.ToggleDirect;
preloadSongSelect();
}
@ -108,14 +85,13 @@ namespace osu.Game.Screens.Menu
buttons.FadeInFromZero(500);
if (last is Intro && Beatmap != null)
{
Task.Run(() =>
if (!Beatmap.Track.IsRunning)
{
trackManager.SetExclusive(Beatmap.Track);
Beatmap.Track.Seek(Beatmap.Metadata.PreviewTime);
if (Beatmap.Metadata.PreviewTime == -1)
Beatmap.Track.Seek(Beatmap.Track.Length * 0.4f);
Beatmap.Track.Start();
});
}
}
}

View File

@ -0,0 +1,96 @@
// 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.Graphics;
using osu.Framework.Allocation;
using osu.Framework.Audio.Track;
using osu.Framework.Configuration;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Colour;
using osu.Framework.Graphics.Sprites;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using System;
namespace osu.Game.Screens.Menu
{
public class MenuSideFlashes : BeatSyncedContainer
{
public override bool HandleInput => false;
private readonly Bindable<WorkingBeatmap> beatmap = new Bindable<WorkingBeatmap>();
private readonly Box leftBox;
private readonly Box rightBox;
private const float amplitude_dead_zone = 0.25f;
private const float alpha_multiplier = (1 - amplitude_dead_zone) / 0.55f;
private const float kiai_multiplier = (1 - amplitude_dead_zone * 0.95f) / 0.8f;
private const int box_max_alpha = 200;
private const double box_fade_in_time = 65;
private const int box_width = 200;
public MenuSideFlashes()
{
RelativeSizeAxes = Axes.Both;
Anchor = Anchor.Centre;
Origin = Anchor.Centre;
Children = new Drawable[]
{
leftBox = new Box
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
RelativeSizeAxes = Axes.Y,
Width = box_width,
Alpha = 0,
BlendingMode = BlendingMode.Additive,
},
rightBox = new Box
{
Anchor = Anchor.CentreRight,
Origin = Anchor.CentreRight,
RelativeSizeAxes = Axes.Y,
Width = box_width,
Alpha = 0,
BlendingMode = BlendingMode.Additive,
}
};
}
[BackgroundDependencyLoader]
private void load(OsuGameBase game, OsuColour colours)
{
beatmap.BindTo(game.Beatmap);
// linear colour looks better in this case, so let's use it for now.
Color4 gradientDark = colours.Blue.Opacity(0).ToLinear();
Color4 gradientLight = colours.Blue.Opacity(0.3f).ToLinear();
leftBox.ColourInfo = ColourInfo.GradientHorizontal(gradientLight, gradientDark);
rightBox.ColourInfo = ColourInfo.GradientHorizontal(gradientDark, gradientLight);
}
protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, TrackAmplitudes amplitudes)
{
if (beatIndex < 0)
return;
if (effectPoint.KiaiMode ? beatIndex % 2 == 0 : beatIndex % (int)timingPoint.TimeSignature == 0)
flash(leftBox, timingPoint.BeatLength, effectPoint.KiaiMode, amplitudes);
if (effectPoint.KiaiMode ? beatIndex % 2 == 1 : beatIndex % (int)timingPoint.TimeSignature == 0)
flash(rightBox, timingPoint.BeatLength, effectPoint.KiaiMode, amplitudes);
}
private void flash(Drawable d, double beatLength, bool kiai, TrackAmplitudes amplitudes)
{
d.FadeTo(Math.Max(0, ((d.Equals(leftBox) ? amplitudes.LeftChannel : amplitudes.RightChannel) - amplitude_dead_zone) / (kiai ? kiai_multiplier : alpha_multiplier)), box_fade_in_time);
using (d.BeginDelayedSequence(box_fade_in_time))
d.FadeOut(beatLength, EasingTypes.In);
}
}
}

View File

@ -5,13 +5,17 @@ using System;
using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Audio.Sample;
using osu.Framework.Audio.Track;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Textures;
using osu.Framework.Input;
using osu.Framework.MathUtils;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Graphics;
using osu.Game.Graphics.Backgrounds;
using osu.Game.Graphics.Containers;
using OpenTK;
using OpenTK.Graphics;
@ -20,19 +24,23 @@ namespace osu.Game.Screens.Menu
/// <summary>
/// osu! logo and its attachments (pulsing, visualiser etc.)
/// </summary>
public class OsuLogo : Container
public class OsuLogo : BeatSyncedContainer
{
public readonly Color4 OsuPink = OsuColour.FromHex(@"e967a1");
private readonly Sprite logo;
private readonly CircularContainer logoContainer;
private readonly Container logoBounceContainer;
private readonly Container logoBeatContainer;
private readonly Container logoAmplitudeContainer;
private readonly Container logoHoverContainer;
private SampleChannel sampleClick;
private readonly Container colourAndTriangles;
private readonly Triangles triangles;
public Action Action;
public float SizeForFlow => logo == null ? 0 : logo.DrawSize.X * logo.Scale.X * logoBounceContainer.Scale.X * logoHoverContainer.Scale.X * 0.74f;
@ -67,8 +75,12 @@ namespace osu.Game.Screens.Menu
private const float default_size = 480;
private const double beat_in_time = 60;
public OsuLogo()
{
EarlyActivationMilliseconds = beat_in_time;
Size = new Vector2(default_size);
Anchor = Anchor.Centre;
@ -78,67 +90,16 @@ namespace osu.Game.Screens.Menu
Children = new Drawable[]
{
logoBounceContainer = new Container
logoHoverContainer = new Container
{
AutoSizeAxes = Axes.Both,
Children = new Drawable[]
{
logoHoverContainer = new Container
logoBounceContainer = new Container
{
AutoSizeAxes = Axes.Both,
Children = new Drawable[]
{
new BufferedContainer
{
AutoSizeAxes = Axes.Both,
Children = new Drawable[]
{
logoContainer = new CircularContainer
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both,
Scale = new Vector2(0.88f),
Masking = true,
Children = new Drawable[]
{
colourAndTriangles = new Container
{
RelativeSizeAxes = Axes.Both,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = OsuPink,
},
new Triangles
{
TriangleScale = 4,
ColourLight = OsuColour.FromHex(@"ff7db7"),
ColourDark = OsuColour.FromHex(@"de5b95"),
RelativeSizeAxes = Axes.Both,
},
}
},
flashLayer = new Box
{
RelativeSizeAxes = Axes.Both,
BlendingMode = BlendingMode.Additive,
Colour = Color4.White,
Alpha = 0,
},
},
},
logo = new Sprite
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
},
}
},
rippleContainer = new Container
{
Anchor = Anchor.Centre,
@ -151,36 +112,101 @@ namespace osu.Game.Screens.Menu
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
BlendingMode = BlendingMode.Additive,
Alpha = 0.15f
Alpha = 0
}
}
},
impactContainer = new CircularContainer
logoAmplitudeContainer = new Container
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Alpha = 0,
BorderColour = Color4.White,
RelativeSizeAxes = Axes.Both,
BorderThickness = 10,
Masking = true,
AutoSizeAxes = Axes.Both,
Children = new Drawable[]
{
new Box
logoBeatContainer = new Container
{
RelativeSizeAxes = Axes.Both,
AlwaysPresent = true,
Alpha = 0,
AutoSizeAxes = Axes.Both,
Children = new Drawable[]
{
new BufferedContainer
{
AutoSizeAxes = Axes.Both,
Children = new Drawable[]
{
logoContainer = new CircularContainer
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both,
Scale = new Vector2(0.88f),
Masking = true,
Children = new Drawable[]
{
colourAndTriangles = new Container
{
RelativeSizeAxes = Axes.Both,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = OsuPink,
},
triangles = new Triangles
{
TriangleScale = 4,
ColourLight = OsuColour.FromHex(@"ff7db7"),
ColourDark = OsuColour.FromHex(@"de5b95"),
RelativeSizeAxes = Axes.Both,
},
}
},
flashLayer = new Box
{
RelativeSizeAxes = Axes.Both,
BlendingMode = BlendingMode.Additive,
Colour = Color4.White,
Alpha = 0,
},
},
},
logo = new Sprite
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
},
}
},
impactContainer = new CircularContainer
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Alpha = 0,
BorderColour = Color4.White,
RelativeSizeAxes = Axes.Both,
BorderThickness = 10,
Masking = true,
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
AlwaysPresent = true,
Alpha = 0,
}
}
},
new MenuVisualisation
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both,
BlendingMode = BlendingMode.Additive,
Alpha = 0.2f,
}
}
}
}
},
new MenuVisualisation
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both,
BlendingMode = BlendingMode.Additive,
Alpha = 0.2f,
}
}
}
@ -197,13 +223,56 @@ namespace osu.Game.Screens.Menu
ripple.Texture = textures.Get(@"Menu/logo");
}
protected override void LoadComplete()
{
base.LoadComplete();
private int lastBeatIndex;
ripple.ScaleTo(ripple.Scale * 1.1f, 500);
ripple.FadeOut(500);
ripple.Loop(300);
protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, TrackAmplitudes amplitudes)
{
base.OnNewBeat(beatIndex, timingPoint, effectPoint, amplitudes);
lastBeatIndex = beatIndex;
var beatLength = timingPoint.BeatLength;
float amplitudeAdjust = Math.Min(1, 0.4f + amplitudes.Maximum);
if (beatIndex < 0) return;
logoBeatContainer.ScaleTo(1 - 0.02f * amplitudeAdjust, beat_in_time, EasingTypes.Out);
using (logoBeatContainer.BeginDelayedSequence(beat_in_time))
logoBeatContainer.ScaleTo(1, beatLength * 2, EasingTypes.OutQuint);
ripple.ClearTransforms();
ripple.ScaleTo(logoAmplitudeContainer.Scale);
ripple.Alpha = 0.15f * amplitudeAdjust;
ripple.ScaleTo(logoAmplitudeContainer.Scale * (1 + 0.04f * amplitudeAdjust), beatLength, EasingTypes.OutQuint);
ripple.FadeOut(beatLength, EasingTypes.OutQuint);
if (effectPoint.KiaiMode && flashLayer.Alpha < 0.4f)
{
flashLayer.ClearTransforms();
flashLayer.FadeTo(0.2f * amplitudeAdjust, beat_in_time, EasingTypes.Out);
using (flashLayer.BeginDelayedSequence(beat_in_time))
flashLayer.FadeOut(beatLength);
}
}
protected override void Update()
{
base.Update();
const float scale_adjust_cutoff = 0.4f;
const float velocity_adjust_cutoff = 0.98f;
var maxAmplitude = lastBeatIndex >= 0 ? Beatmap.Value?.Track?.CurrentAmplitudes.Maximum ?? 0 : 0;
logoAmplitudeContainer.ScaleTo(1 - Math.Max(0, maxAmplitude - scale_adjust_cutoff) * 0.04f, 75, EasingTypes.OutQuint);
if (maxAmplitude > velocity_adjust_cutoff)
triangles.Velocity = 1 + Math.Max(0, maxAmplitude - velocity_adjust_cutoff) * 50;
else
triangles.Velocity = (float)Interpolation.Damp(triangles.Velocity, 1, 0.995f, Time.Elapsed);
}
protected override bool OnMouseDown(InputState state, MouseDownEventArgs args)
@ -254,4 +323,4 @@ namespace osu.Game.Screens.Menu
impactContainer.ScaleTo(1.12f, 250);
}
}
}
}

View File

@ -2,7 +2,6 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Configuration;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
@ -12,6 +11,7 @@ using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.UI;
using OpenTK;
using osu.Framework.Input;
using osu.Game.Graphics.Containers;
namespace osu.Game.Screens.Play.HUD
{
@ -27,7 +27,7 @@ namespace osu.Game.Screens.Play.HUD
{
Children = new Drawable[]
{
iconsContainer = new IconFlow
iconsContainer = new ReverseDepthFillFlowContainer<ModIcon>
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
@ -93,12 +93,5 @@ namespace osu.Game.Screens.Play.HUD
contract();
base.OnHoverLost(state);
}
private class IconFlow : FillFlowContainer<ModIcon>
{
// just reverses the depth of flow contents.
protected override IComparer<Drawable> DepthComparer => new ReverseCreationOrderDepthComparer();
protected override IEnumerable<ModIcon> FlowingChildren => base.FlowingChildren.Reverse();
}
}
}

View File

@ -30,7 +30,6 @@ namespace osu.Game.Screens.Play
public readonly SongProgress Progress;
public readonly ModDisplay ModDisplay;
private Bindable<bool> showKeyCounter;
private Bindable<bool> showHud;
private static bool hasShownNotificationOnce;
@ -67,24 +66,8 @@ namespace osu.Game.Screens.Play
[BackgroundDependencyLoader(true)]
private void load(OsuConfigManager config, NotificationManager notificationManager)
{
showKeyCounter = config.GetBindable<bool>(OsuSetting.KeyOverlay);
showKeyCounter.ValueChanged += keyCounterVisibility =>
{
if (keyCounterVisibility)
KeyCounter.FadeIn(duration);
else
KeyCounter.FadeOut(duration);
};
showKeyCounter.TriggerChange();
showHud = config.GetBindable<bool>(OsuSetting.ShowInterface);
showHud.ValueChanged += hudVisibility =>
{
if (hudVisibility)
content.FadeIn(duration);
else
content.FadeOut(duration);
};
showHud.ValueChanged += hudVisibility => content.FadeTo(hudVisibility ? 1 : 0, duration);
showHud.TriggerChange();
if (!showHud && !hasShownNotificationOnce)

View File

@ -6,11 +6,18 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using OpenTK.Graphics;
using osu.Framework.Input;
using osu.Framework.Configuration;
using osu.Framework.Allocation;
using osu.Game.Configuration;
namespace osu.Game.Screens.Play
{
public class KeyCounterCollection : FillFlowContainer<KeyCounter>
{
private const int duration = 100;
private Bindable<bool> showKeyCounter;
public KeyCounterCollection()
{
AlwaysReceiveInput = true;
@ -34,6 +41,14 @@ namespace osu.Game.Screens.Play
counter.ResetCount();
}
[BackgroundDependencyLoader]
private void load(OsuConfigManager config)
{
showKeyCounter = config.GetBindable<bool>(OsuSetting.KeyOverlay);
showKeyCounter.ValueChanged += keyCounterVisibility => FadeTo(keyCounterVisibility ? 1 : 0, duration);
showKeyCounter.TriggerChange();
}
//further: change default values here and in KeyCounter if needed, instead of passing them in every constructor
private bool isCounting;
public bool IsCounting

View File

@ -123,7 +123,7 @@ namespace osu.Game.Screens.Play
decoupledClock = new DecoupleableInterpolatingFramedClock { IsCoupled = false };
var firstObjectTime = HitRenderer.Objects.First().StartTime;
decoupledClock.Seek(Math.Min(0, firstObjectTime - Math.Max(Beatmap.Beatmap.TimingInfo.BeatLengthAt(firstObjectTime) * 4, Beatmap.BeatmapInfo.AudioLeadIn)));
decoupledClock.Seek(Math.Min(0, firstObjectTime - Math.Max(Beatmap.Beatmap.ControlPointInfo.TimingPointAt(firstObjectTime).BeatLength * 4, Beatmap.BeatmapInfo.AudioLeadIn)));
decoupledClock.ProcessFrame();
offsetClock = new FramedOffsetClock(decoupledClock);

View File

@ -170,8 +170,8 @@ namespace osu.Game.Screens.Select
List<BeatmapGroup> visibleGroups = groups.Where(selectGroup => selectGroup.State != BeatmapGroupState.Hidden).ToList();
if (visibleGroups.Count < 1)
return;
BeatmapGroup group = visibleGroups[RNG.Next(visibleGroups.Count)];
BeatmapGroup group = visibleGroups[RNG.Next(visibleGroups.Count)];
BeatmapPanel panel = group.BeatmapPanels[RNG.Next(group.BeatmapPanels.Count)];
selectGroup(group, panel);

View File

@ -238,12 +238,12 @@ namespace osu.Game.Screens.Select
private string getBPMRange(Beatmap beatmap)
{
double bpmMax = beatmap.TimingInfo.BPMMaximum;
double bpmMin = beatmap.TimingInfo.BPMMinimum;
double bpmMax = beatmap.ControlPointInfo.BPMMaximum;
double bpmMin = beatmap.ControlPointInfo.BPMMinimum;
if (Precision.AlmostEquals(bpmMin, bpmMax)) return Math.Round(bpmMin) + "bpm";
return Math.Round(bpmMin) + "-" + Math.Round(bpmMax) + "bpm (mostly " + Math.Round(beatmap.TimingInfo.BPMMode) + "bpm)";
return Math.Round(bpmMin) + "-" + Math.Round(bpmMax) + "bpm (mostly " + Math.Round(beatmap.ControlPointInfo.BPMMode) + "bpm)";
}
public class InfoLabel : Container

View File

@ -2,8 +2,6 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
@ -12,6 +10,7 @@ using osu.Game.Graphics;
using OpenTK;
using OpenTK.Graphics;
using OpenTK.Input;
using osu.Game.Graphics.Containers;
namespace osu.Game.Screens.Select.Options
{
@ -71,7 +70,7 @@ namespace osu.Game.Screens.Select.Options
Scale = new Vector2(1, 0),
Colour = Color4.Black.Opacity(0.5f),
},
buttonsContainer = new ButtonFlow
buttonsContainer = new ReverseDepthFillFlowContainer<BeatmapOptionsButton>
{
Height = height,
RelativePositionAxes = Axes.X,
@ -109,16 +108,5 @@ namespace osu.Game.Screens.Select.Options
HotKey = hotkey
});
}
private class ButtonFlow : FillFlowContainer<BeatmapOptionsButton>
{
protected override IComparer<Drawable> DepthComparer => new ReverseCreationOrderDepthComparer();
protected override IEnumerable<BeatmapOptionsButton> FlowingChildren => base.FlowingChildren.Reverse();
public ButtonFlow()
{
Direction = FillDirection.Horizontal;
}
}
}
}

View File

@ -316,11 +316,12 @@ namespace osu.Game.Screens.Select
/// </summary>
private void selectionChanged(BeatmapInfo beatmap)
{
bool beatmapSetChange = false;
selectionChangedDebounce?.Cancel();
if (beatmap.Equals(Beatmap?.BeatmapInfo))
return;
bool beatmapSetChange = false;
if (beatmap.BeatmapSetInfoID == selectionChangeNoBounce?.BeatmapSetInfoID)
sampleChangeDifficulty.Play();
else
@ -331,7 +332,6 @@ namespace osu.Game.Screens.Select
selectionChangeNoBounce = beatmap;
selectionChangedDebounce?.Cancel();
selectionChangedDebounce = Scheduler.AddDelayed(delegate
{
Beatmap = database.GetWorkingBeatmap(beatmap, Beatmap);

209
osu.Game/Users/UserPanel.cs Normal file
View File

@ -0,0 +1,209 @@
// 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 OpenTK.Graphics;
using osu.Framework.Allocation;
using osu.Framework.Configuration;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Textures;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
namespace osu.Game.Users
{
public class UserPanel : Container
{
private const float height = 100;
private const float content_padding = 10;
private const float status_height = 30;
private OsuColour colours;
private readonly Container statusBar;
private readonly Box statusBg;
private readonly OsuSpriteText statusMessage;
public readonly Bindable<UserStatus> Status = new Bindable<UserStatus>();
public UserPanel(User user)
{
Height = height - status_height;
Masking = true;
CornerRadius = 5;
EdgeEffect = new EdgeEffect
{
Type = EdgeEffectType.Shadow,
Colour = Color4.Black.Opacity(0.25f),
Radius = 4,
};
Children = new Drawable[]
{
new AsyncLoadWrapper(new CoverBackgroundSprite(user)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
FillMode = FillMode.Fill,
OnLoadComplete = d => d.FadeInFromZero(200),
}) { RelativeSizeAxes = Axes.Both },
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Color4.Black.Opacity(0.7f),
},
new Container
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Padding = new MarginPadding { Top = content_padding, Left = content_padding, Right = content_padding },
Children = new Drawable[]
{
new UpdateableAvatar
{
Size = new Vector2(height - status_height - content_padding * 2),
User = user,
Masking = true,
CornerRadius = 5,
EdgeEffect = new EdgeEffect
{
Type = EdgeEffectType.Shadow,
Colour = Color4.Black.Opacity(0.25f),
Radius = 4,
},
},
new Container
{
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding { Left = height - status_height - content_padding },
Children = new Drawable[]
{
new OsuSpriteText
{
Text = user.Username,
TextSize = 18,
Font = @"Exo2.0-SemiBoldItalic",
},
new FillFlowContainer
{
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
AutoSizeAxes = Axes.X,
Height = 20f,
Direction = FillDirection.Horizontal,
Spacing = new Vector2(5f, 0f),
Children = new Drawable[]
{
new DrawableFlag(user.Country?.FlagName ?? @"__")
{
Width = 30f,
RelativeSizeAxes = Axes.Y,
},
new Container
{
Width = 40f,
RelativeSizeAxes = Axes.Y,
},
new CircularContainer
{
Width = 20f,
RelativeSizeAxes = Axes.Y,
},
},
},
},
},
},
},
statusBar = new Container
{
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
RelativeSizeAxes = Axes.X,
Alpha = 0f,
Children = new Drawable[]
{
statusBg = new Box
{
RelativeSizeAxes = Axes.Both,
Alpha = 0.5f,
},
new FillFlowContainer
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
AutoSizeAxes = Axes.Both,
Spacing = new Vector2(5f, 0f),
Children = new[]
{
new TextAwesome
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
Icon = FontAwesome.fa_circle_o,
Shadow = true,
TextSize = 14,
},
statusMessage = new OsuSpriteText
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
Font = @"Exo2.0-Semibold",
},
},
},
},
},
};
Status.ValueChanged += displayStatus;
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
this.colours = colours;
}
private void displayStatus(UserStatus status)
{
const float transition_duration = 500;
if (status == null)
{
statusBar.ResizeHeightTo(0f, transition_duration, EasingTypes.OutQuint);
statusBar.FadeOut(transition_duration, EasingTypes.OutQuint);
ResizeHeightTo(height - status_height, transition_duration, EasingTypes.OutQuint);
}
else
{
statusBar.ResizeHeightTo(status_height, transition_duration, EasingTypes.OutQuint);
statusBar.FadeIn(transition_duration, EasingTypes.OutQuint);
ResizeHeightTo(height, transition_duration, EasingTypes.OutQuint);
statusBg.FadeColour(status.GetAppropriateColour(colours), 500, EasingTypes.OutQuint);
statusMessage.Text = status.Message;
}
}
private class CoverBackgroundSprite : Sprite
{
private readonly User user;
public CoverBackgroundSprite(User user)
{
this.user = user;
}
[BackgroundDependencyLoader]
private void load(TextureStore textures)
{
if (!string.IsNullOrEmpty(user.CoverUrl))
Texture = textures.Get(user.CoverUrl);
}
}
}
}

View File

@ -0,0 +1,61 @@
// 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.Graphics;
using osu.Game.Graphics;
namespace osu.Game.Users
{
public abstract class UserStatus
{
public abstract string Message { get; }
public abstract Color4 GetAppropriateColour(OsuColour colours);
}
public abstract class UserStatusAvailable : UserStatus
{
public override Color4 GetAppropriateColour(OsuColour colours) => colours.BlueDarker;
}
public abstract class UserStatusBusy : UserStatus
{
public override Color4 GetAppropriateColour(OsuColour colours) => colours.YellowDark;
}
public class UserStatusOffline : UserStatus
{
public override string Message => @"Offline";
public override Color4 GetAppropriateColour(OsuColour colours) => colours.Gray7;
}
public class UserStatusOnline : UserStatusAvailable
{
public override string Message => @"Online";
}
public class UserStatusSpectating : UserStatusAvailable
{
public override string Message => @"Spectating a game";
}
public class UserStatusInLobby : UserStatusAvailable
{
public override string Message => @"in Multiplayer Lobby";
}
public class UserStatusSoloGame : UserStatusBusy
{
public override string Message => @"Solo Game";
}
public class UserStatusMultiplayerGame: UserStatusBusy
{
public override string Message => @"Multiplaying";
}
public class UserStatusModding : UserStatus
{
public override string Message => @"Modding a map";
public override Color4 GetAppropriateColour(OsuColour colours) => colours.PurpleDark;
}
}

View File

@ -81,15 +81,21 @@
<Compile Include="Overlays\Music\PlaylistItem.cs" />
<Compile Include="Overlays\Music\PlaylistList.cs" />
<Compile Include="Overlays\OnScreenDisplay.cs" />
<Compile Include="Graphics\Containers\SectionsContainer.cs" />
<Compile Include="Overlays\Settings\SettingsHeader.cs" />
<Compile Include="Overlays\Settings\Sections\Audio\MainMenuSettings.cs" />
<Compile Include="Overlays\Toolbar\ToolbarChatButton.cs" />
<Compile Include="Rulesets\Beatmaps\BeatmapConverter.cs" />
<Compile Include="Rulesets\Beatmaps\BeatmapProcessor.cs" />
<Compile Include="Beatmaps\ControlPoints\ControlPoint.cs" />
<Compile Include="Beatmaps\ControlPoints\ControlPointInfo.cs" />
<Compile Include="Beatmaps\ControlPoints\DifficultyControlPoint.cs" />
<Compile Include="Beatmaps\ControlPoints\EffectControlPoint.cs" />
<Compile Include="Beatmaps\ControlPoints\SoundControlPoint.cs" />
<Compile Include="Beatmaps\ControlPoints\TimingControlPoint.cs" />
<Compile Include="Beatmaps\Legacy\LegacyBeatmap.cs" />
<Compile Include="Beatmaps\Timing\BreakPeriod.cs" />
<Compile Include="Beatmaps\Timing\TimeSignatures.cs" />
<Compile Include="Beatmaps\Timing\TimingInfo.cs" />
<Compile Include="Database\BeatmapMetrics.cs" />
<Compile Include="Database\Database.cs" />
<Compile Include="Database\RulesetInfo.cs" />
@ -183,6 +189,7 @@
<Compile Include="Database\RulesetDatabase.cs" />
<Compile Include="Rulesets\Scoring\Score.cs" />
<Compile Include="Rulesets\Scoring\ScoreProcessor.cs" />
<Compile Include="Screens\Menu\MenuSideFlashes.cs" />
<Compile Include="Screens\Play\HUD\HealthDisplay.cs" />
<Compile Include="Screens\Play\HUDOverlay.cs" />
<Compile Include="Screens\Play\HUD\StandardHealthDisplay.cs" />
@ -201,7 +208,6 @@
<Compile Include="Beatmaps\Drawables\Panel.cs" />
<Compile Include="Rulesets\Objects\Drawables\DrawableHitObject.cs" />
<Compile Include="Rulesets\Objects\HitObject.cs" />
<Compile Include="Beatmaps\Timing\ControlPoint.cs" />
<Compile Include="Configuration\OsuConfigManager.cs" />
<Compile Include="Overlays\Notifications\IHasCompletionTarget.cs" />
<Compile Include="Overlays\Notifications\Notification.cs" />
@ -292,6 +298,7 @@
<Compile Include="Graphics\UserInterface\RollingCounter.cs" />
<Compile Include="Graphics\UserInterface\Volume\VolumeControlReceptor.cs" />
<Compile Include="Graphics\Backgrounds\Background.cs" />
<Compile Include="Graphics\Containers\BeatSyncedContainer.cs" />
<Compile Include="Graphics\Containers\ParallaxContainer.cs" />
<Compile Include="Graphics\Cursor\MenuCursor.cs" />
<Compile Include="Graphics\Processing\RatioAdjust.cs" />
@ -428,6 +435,21 @@
<Compile Include="Screens\Multiplayer\DrawableRoom.cs" />
<Compile Include="Online\Multiplayer\Room.cs" />
<Compile Include="Online\Multiplayer\RoomStatus.cs" />
<Compile Include="Users\UserPanel.cs" />
<Compile Include="Users\UserStatus.cs" />
<Compile Include="Overlays\DirectOverlay.cs" />
<Compile Include="Overlays\Direct\FilterControl.cs" />
<Compile Include="Overlays\Direct\Header.cs" />
<Compile Include="Overlays\Direct\SortTabControl.cs" />
<Compile Include="Graphics\UserInterface\OsuEnumDropdown.cs" />
<Compile Include="Overlays\Direct\DirectPanel.cs" />
<Compile Include="Overlays\Direct\DirectGridPanel.cs" />
<Compile Include="Overlays\Direct\DirectListPanel.cs" />
<Compile Include="Database\OnlineWorkingBeatmap.cs" />
<Compile Include="Database\BeatmapOnlineInfo.cs" />
<Compile Include="Overlays\Direct\SlimEnumDropdown.cs" />
<Compile Include="Graphics\Containers\ReverseDepthFillFlowContainer.cs" />
<Compile Include="Database\RankStatus.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\osu-framework\osu.Framework\osu.Framework.csproj">