1
0
mirror of https://github.com/ppy/osu.git synced 2025-01-30 08:52:58 +08:00

Merge branch 'master' into sections

This commit is contained in:
Huo Yaoyuan 2017-05-22 12:52:27 +08:00
commit d0ff9e6576
36 changed files with 732 additions and 193 deletions

@ -1 +1 @@
Subproject commit d00a7df902074d0b3f1479904b7f322db9d39c1f Subproject commit 42e26d49b9046fcb96c123b0dfb48e06d741e162

View File

@ -11,6 +11,7 @@ using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Mania.Beatmaps.Patterns; using osu.Game.Rulesets.Mania.Beatmaps.Patterns;
using osu.Game.Rulesets.Mania.MathUtils; using osu.Game.Rulesets.Mania.MathUtils;
using osu.Game.Database; using osu.Game.Database;
using osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy;
namespace osu.Game.Rulesets.Mania.Beatmaps namespace osu.Game.Rulesets.Mania.Beatmaps
{ {
@ -83,28 +84,24 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
// Following lines currently commented out to appease resharper // Following lines currently commented out to appease resharper
//Patterns.PatternGenerator conversion = null; Patterns.PatternGenerator conversion = null;
if (distanceData != null) if (distanceData != null)
{ conversion = new DistanceObjectPatternGenerator(random, original, beatmap, lastPattern);
// Slider
}
else if (endTimeData != null) else if (endTimeData != null)
{ conversion = new EndTimeObjectPatternGenerator(random, original, beatmap);
// Spinner
}
else if (positionData != null) else if (positionData != null)
{ {
// Circle // Circle
} }
//if (conversion == null) if (conversion == null)
return null; return null;
//Pattern newPattern = conversion.Generate(); Pattern newPattern = conversion.Generate();
//lastPattern = newPattern; lastPattern = newPattern;
//return newPattern.HitObjects; return newPattern.HitObjects;
} }
/// <summary> /// <summary>

View File

@ -0,0 +1,490 @@
// 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 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;
namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
{
/// <summary>
/// A pattern generator for IHasDistance hit objects.
/// </summary>
internal class DistanceObjectPatternGenerator : PatternGenerator
{
/// <summary>
/// Base osu! slider scoring distance.
/// </summary>
private const float osu_base_scoring_distance = 100;
private readonly double endTime;
private readonly double segmentDuration;
private readonly int repeatCount;
private PatternType convertType;
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)
convertType = PatternType.LowProbability;
var distanceData = hitObject as IHasDistance;
var repeatsData = hitObject as IHasRepeats;
repeatCount = repeatsData?.RepeatCount ?? 1;
double speedAdjustment = beatmap.TimingInfo.SpeedMultiplierAt(hitObject.StartTime);
double speedAdjustedBeatLength = beatmap.TimingInfo.BeatLengthAt(hitObject.StartTime) * speedAdjustment;
// 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;
// The duration of the osu! hit object
double osuDuration = distance / osuVelocity;
endTime = hitObject.StartTime + osuDuration;
segmentDuration = (endTime - HitObject.StartTime) / repeatCount;
}
public override Pattern Generate()
{
if (repeatCount > 1)
{
if (segmentDuration <= 90)
return generateRandomHoldNotes(HitObject.StartTime, 1);
if (segmentDuration <= 120)
{
convertType |= PatternType.ForceNotStack;
return generateRandomNotes(HitObject.StartTime, repeatCount + 1);
}
if (segmentDuration <= 160)
return generateStair(HitObject.StartTime);
if (segmentDuration <= 200 && ConversionDifficulty > 3)
return generateRandomMultipleNotes(HitObject.StartTime);
double duration = endTime - HitObject.StartTime;
if (duration >= 4000)
return generateNRandomNotes(HitObject.StartTime, 0.23, 0, 0);
if (segmentDuration > 400 && repeatCount < AvailableColumns - 1 - RandomStart)
return generateTiledHoldNotes(HitObject.StartTime);
return generateHoldAndNormalNotes(HitObject.StartTime);
}
if (segmentDuration <= 110)
{
if (PreviousPattern.ColumnWithObjects < AvailableColumns)
convertType |= PatternType.ForceNotStack;
else
convertType &= ~PatternType.ForceNotStack;
return generateRandomNotes(HitObject.StartTime, segmentDuration < 80 ? 1 : 2);
}
if (ConversionDifficulty > 6.5)
{
if ((convertType & PatternType.LowProbability) > 0)
return generateNRandomNotes(HitObject.StartTime, 0.78, 0.3, 0);
return generateNRandomNotes(HitObject.StartTime, 0.85, 0.36, 0.03);
}
if (ConversionDifficulty > 4)
{
if ((convertType & PatternType.LowProbability) > 0)
return generateNRandomNotes(HitObject.StartTime, 0.43, 0.08, 0);
return generateNRandomNotes(HitObject.StartTime, 0.56, 0.18, 0);
}
if (ConversionDifficulty > 2.5)
{
if ((convertType & PatternType.LowProbability) > 0)
return generateNRandomNotes(HitObject.StartTime, 0.3, 0, 0);
return generateNRandomNotes(HitObject.StartTime, 0.37, 0.08, 0);
}
if ((convertType & PatternType.LowProbability) > 0)
return generateNRandomNotes(HitObject.StartTime, 0.17, 0, 0);
return generateNRandomNotes(HitObject.StartTime, 0.27, 0, 0);
}
/// <summary>
/// Generates random hold notes that start at an span the same amount of rows.
/// </summary>
/// <param name="startTime">Start time of each hold note.</param>
/// <param name="noteCount">Number of hold notes.</param>
/// <returns>The <see cref="Pattern"/> containing the hit objects.</returns>
private Pattern generateRandomHoldNotes(double startTime, int noteCount)
{
// - - - -
// ■ - ■ ■
// □ - □ □
// ■ - ■ ■
var pattern = new Pattern();
int usableColumns = AvailableColumns - RandomStart - PreviousPattern.ColumnWithObjects;
int nextColumn = Random.Next(RandomStart, AvailableColumns);
for (int i = 0; i < Math.Min(usableColumns, noteCount); i++)
{
while (pattern.ColumnHasObject(nextColumn) || PreviousPattern.ColumnHasObject(nextColumn)) //find available column
nextColumn = Random.Next(RandomStart, AvailableColumns);
addToPattern(pattern, nextColumn, startTime, endTime);
}
// This is can't be combined with the above loop due to RNG
for (int i = 0; i < noteCount - usableColumns; i++)
{
while (pattern.ColumnHasObject(nextColumn))
nextColumn = Random.Next(RandomStart, AvailableColumns);
addToPattern(pattern, nextColumn, startTime, endTime);
}
return pattern;
}
/// <summary>
/// Generates random notes, with one note per row and no stacking.
/// </summary>
/// <param name="startTime">The start time.</param>
/// <param name="noteCount">The number of notes.</param>
/// <returns>The <see cref="Pattern"/> containing the hit objects.</returns>
private Pattern generateRandomNotes(double startTime, int noteCount)
{
// - - - -
// x - - -
// - - x -
// - - - x
// x - - -
var pattern = new Pattern();
int nextColumn = GetColumn((HitObject as IHasXPosition)?.X ?? 0, true);
if ((convertType & PatternType.ForceNotStack) > 0 && PreviousPattern.ColumnWithObjects < AvailableColumns)
{
while (PreviousPattern.ColumnHasObject(nextColumn))
nextColumn = Random.Next(RandomStart, AvailableColumns);
}
int lastColumn = nextColumn;
for (int i = 0; i < noteCount; i++)
{
addToPattern(pattern, nextColumn, startTime, startTime);
while (nextColumn == lastColumn)
nextColumn = Random.Next(RandomStart, AvailableColumns);
lastColumn = nextColumn;
startTime += segmentDuration;
}
return pattern;
}
/// <summary>
/// Generates a stair of notes, with one note per row.
/// </summary>
/// <param name="startTime">The start time.</param>
/// <returns>The <see cref="Pattern"/> containing the hit objects.</returns>
private Pattern generateStair(double startTime)
{
// - - - -
// x - - -
// - x - -
// - - x -
// - - - x
// - - x -
// - x - -
// x - - -
var pattern = new Pattern();
int column = GetColumn((HitObject as IHasXPosition)?.X ?? 0, true);
bool increasing = Random.NextDouble() > 0.5;
for (int i = 0; i <= repeatCount; i++)
{
addToPattern(pattern, column, startTime, startTime);
startTime += segmentDuration;
// Check if we're at the borders of the stage, and invert the pattern if so
if (increasing)
{
if (column >= AvailableColumns - 1)
{
increasing = false;
column--;
}
else
column++;
}
else
{
if (column <= RandomStart)
{
increasing = true;
column++;
}
else
column--;
}
}
return pattern;
}
/// <summary>
/// Generates random notes with 1-2 notes per row and no stacking.
/// </summary>
/// <param name="startTime">The start time.</param>
/// <returns>The <see cref="Pattern"/> containing the hit objects.</returns>
private Pattern generateRandomMultipleNotes(double startTime)
{
// - - - -
// x - - -
// - x x -
// - - - x
// x - x -
var pattern = new Pattern();
bool legacy = AvailableColumns >= 4 && AvailableColumns <= 8;
int interval = Random.Next(1, AvailableColumns - (legacy ? 1 : 0));
int nextColumn = GetColumn((HitObject as IHasXPosition)?.X ?? 0, true);
for (int i = 0; i <= repeatCount; i++)
{
addToPattern(pattern, nextColumn, startTime, startTime);
nextColumn += interval;
if (nextColumn >= AvailableColumns - RandomStart)
nextColumn = nextColumn - AvailableColumns - RandomStart + (legacy ? 1 : 0);
nextColumn += RandomStart;
// If we're in 2K, let's not add many consecutive doubles
if (AvailableColumns > 2)
addToPattern(pattern, nextColumn, startTime, startTime);
nextColumn = Random.Next(RandomStart, AvailableColumns);
startTime += segmentDuration;
}
return pattern;
}
/// <summary>
/// Generates random hold notes. The amount of hold notes generated is determined by probabilities.
/// </summary>
/// <param name="startTime">The hold note start time.</param>
/// <param name="p2">The probability required for 2 hold notes to be generated.</param>
/// <param name="p3">The probability required for 3 hold notes to be generated.</param>
/// <param name="p4">The probability required for 4 hold notes to be generated.</param>
/// <returns>The <see cref="Pattern"/> containing the hit objects.</returns>
private Pattern generateNRandomNotes(double startTime, double p2, double p3, double p4)
{
// - - - -
// ■ - ■ ■
// □ - □ □
// ■ - ■ ■
switch (AvailableColumns)
{
case 2:
p2 = 0;
p3 = 0;
p4 = 0;
break;
case 3:
p2 = Math.Max(p2, 0.1);
p3 = 0;
p4 = 0;
break;
case 4:
p2 = Math.Max(p2, 0.3);
p3 = Math.Max(p3, 0.04);
p4 = 0;
break;
case 5:
p2 = Math.Max(p2, 0.34);
p3 = Math.Max(p3, 0.1);
p4 = Math.Max(p4, 0.03);
break;
}
Func<SampleInfo, bool> isDoubleSample = sample => sample.Name == SampleInfo.HIT_CLAP && sample.Name == SampleInfo.HIT_FINISH;
bool canGenerateTwoNotes = (convertType & PatternType.LowProbability) == 0;
canGenerateTwoNotes &= HitObject.Samples.Any(isDoubleSample) || sampleInfoListAt(HitObject.StartTime).Any(isDoubleSample);
if (canGenerateTwoNotes)
p2 = 1;
return generateRandomHoldNotes(startTime, GetRandomNoteCount(p2, p3, p4));
}
/// <summary>
/// Generates tiled hold notes. You can think of this as a stair of hold notes.
/// </summary>
/// <param name="startTime">The first hold note start time.</param>
/// <returns>The <see cref="Pattern"/> containing the hit objects.</returns>
private Pattern generateTiledHoldNotes(double startTime)
{
// - - - -
// ■ ■ ■ ■
// □ □ □ □
// □ □ □ □
// □ □ □ ■
// □ □ ■ -
// □ ■ - -
// ■ - - -
var pattern = new Pattern();
int columnRepeat = Math.Min(repeatCount, AvailableColumns);
int nextColumn = GetColumn((HitObject as IHasXPosition)?.X ?? 0, true);
if ((convertType & PatternType.ForceNotStack) > 0 && PreviousPattern.ColumnWithObjects < AvailableColumns)
{
while (PreviousPattern.ColumnHasObject(nextColumn))
nextColumn = Random.Next(RandomStart, AvailableColumns);
}
for (int i = 0; i < columnRepeat; i++)
{
while (pattern.ColumnHasObject(nextColumn))
nextColumn = Random.Next(RandomStart, AvailableColumns);
addToPattern(pattern, nextColumn, startTime, endTime);
startTime += segmentDuration;
}
return pattern;
}
/// <summary>
/// Generates a hold note alongside normal notes.
/// </summary>
/// <param name="startTime">The start time of notes.</param>
/// <returns>The <see cref="Pattern"/> containing the hit objects.</returns>
private Pattern generateHoldAndNormalNotes(double startTime)
{
// - - - -
// ■ x x -
// ■ - x x
// ■ x - x
// ■ - x x
var pattern = new Pattern();
int holdColumn = GetColumn((HitObject as IHasXPosition)?.X ?? 0, true);
if ((convertType & PatternType.ForceNotStack) > 0 && PreviousPattern.ColumnWithObjects < AvailableColumns)
{
while (PreviousPattern.ColumnHasObject(holdColumn))
holdColumn = Random.Next(RandomStart, AvailableColumns);
}
// Create the hold note
addToPattern(pattern, holdColumn, startTime, endTime);
int noteCount = 1;
if (ConversionDifficulty > 6.5)
noteCount = GetRandomNoteCount(0.63, 0);
else if (ConversionDifficulty > 4)
noteCount = GetRandomNoteCount(AvailableColumns < 6 ? 0.12 : 0.45, 0);
else if (ConversionDifficulty > 2.5)
noteCount = GetRandomNoteCount(AvailableColumns < 6 ? 0 : 0.24, 0);
noteCount = Math.Min(AvailableColumns - 1, noteCount);
bool ignoreHead = !sampleInfoListAt(startTime).Any(s => s.Name == SampleInfo.HIT_WHISTLE || s.Name == SampleInfo.HIT_FINISH || s.Name == SampleInfo.HIT_CLAP);
int nextColumn = Random.Next(RandomStart, AvailableColumns);
var rowPattern = new Pattern();
for (int i = 0; i <= repeatCount; i++)
{
if (!(ignoreHead && startTime == HitObject.StartTime))
{
for (int j = 0; j < noteCount; j++)
{
while (rowPattern.ColumnHasObject(nextColumn) || nextColumn == holdColumn)
nextColumn = Random.Next(RandomStart, AvailableColumns);
addToPattern(rowPattern, nextColumn, startTime, startTime);
}
}
pattern.Add(rowPattern);
rowPattern.Clear();
startTime += segmentDuration;
}
return pattern;
}
/// <summary>
/// Retrieves the sample info list at a point in time.
/// </summary>
/// <param name="time">The time to retrieve the sample info list from.</param>
/// <returns></returns>
private SampleInfoList sampleInfoListAt(double time)
{
var curveData = HitObject as IHasCurve;
if (curveData == null)
return HitObject.Samples;
double segmentTime = (endTime - HitObject.StartTime) / repeatCount;
int index = (int)(segmentTime == 0 ? 0 : (time - HitObject.StartTime) / segmentTime);
return curveData.RepeatSamples[index];
}
/// <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>
/// <param name="startTime">The start time of the note.</param>
/// <param name="endTime">The end time of the note (set to <paramref name="startTime"/> for a non-hold note).</param>
private void addToPattern(Pattern pattern, int column, double startTime, double endTime)
{
ManiaHitObject newObject;
if (startTime == endTime)
{
newObject = new Note
{
StartTime = startTime,
Samples = sampleInfoListAt(startTime),
Column = column
};
}
else
{
newObject = new HoldNote
{
StartTime = startTime,
Samples = sampleInfoListAt(startTime),
EndSamples = sampleInfoListAt(endTime),
Column = column,
Duration = endTime - startTime
};
}
pattern.Add(newObject);
}
}
}

View File

@ -0,0 +1,98 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Mania.MathUtils;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
using System.Linq;
using osu.Game.Audio;
using osu.Game.Rulesets.Mania.Objects;
namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
{
internal class EndTimeObjectPatternGenerator : PatternGenerator
{
private readonly double endTime;
public EndTimeObjectPatternGenerator(FastRandom random, HitObject hitObject, Beatmap beatmap)
: base(random, hitObject, beatmap, new Pattern())
{
var endtimeData = HitObject as IHasEndTime;
endTime = endtimeData?.EndTime ?? 0;
}
public override Pattern Generate()
{
var pattern = new Pattern();
bool generateHold = endTime - HitObject.StartTime >= 100;
if (AvailableColumns == 8)
{
if (HitObject.Samples.Any(s => s.Name == SampleInfo.HIT_FINISH) && endTime - HitObject.StartTime < 1000)
addToPattern(pattern, 0, generateHold);
else
addToPattern(pattern, getNextRandomColumn(RandomStart), generateHold);
}
else if (AvailableColumns > 0)
addToPattern(pattern, getNextRandomColumn(0), generateHold);
return pattern;
}
/// <summary>
/// Picks a random column after a column.
/// </summary>
/// <param name="start">The starting column.</param>
/// <returns>A random column after <paramref name="start"/>.</returns>
private int getNextRandomColumn(int start)
{
int nextColumn = Random.Next(start, AvailableColumns);
while (PreviousPattern.ColumnHasObject(nextColumn))
nextColumn = Random.Next(start, AvailableColumns);
return nextColumn;
}
/// <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>
/// <param name="holdNote">Whether to add a hold note.</param>
private void addToPattern(Pattern pattern, int column, bool holdNote)
{
ManiaHitObject newObject;
if (holdNote)
{
newObject = new HoldNote
{
StartTime = HitObject.StartTime,
EndSamples = HitObject.Samples,
Column = column,
Duration = endTime - HitObject.StartTime
};
newObject.Samples.Add(new SampleInfo
{
Name = SampleInfo.HIT_NORMAL
});
}
else
{
newObject = new Note
{
StartTime = HitObject.StartTime,
Samples = HitObject.Samples,
Column = column
};
}
pattern.Add(newObject);
}
}
}

View File

@ -90,7 +90,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
HitObject firstObject = Beatmap.HitObjects.FirstOrDefault(); HitObject firstObject = Beatmap.HitObjects.FirstOrDefault();
double drainTime = (lastObject?.StartTime ?? 0) - (firstObject?.StartTime ?? 0); double drainTime = (lastObject?.StartTime ?? 0) - (firstObject?.StartTime ?? 0);
drainTime -= Beatmap.EventInfo.TotalBreakTime; drainTime -= Beatmap.TotalBreakTime;
if (drainTime == 0) if (drainTime == 0)
drainTime = 10000; drainTime = 10000;

View File

@ -15,51 +15,51 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
/// <summary> /// <summary>
/// Keep the same as last row. /// Keep the same as last row.
/// </summary> /// </summary>
ForceStack = 1, ForceStack = 1 << 0,
/// <summary> /// <summary>
/// Keep different from last row. /// Keep different from last row.
/// </summary> /// </summary>
ForceNotStack = 2, ForceNotStack = 1 << 1,
/// <summary> /// <summary>
/// Keep as single note at its original position. /// Keep as single note at its original position.
/// </summary> /// </summary>
KeepSingle = 4, KeepSingle = 1 << 2,
/// <summary> /// <summary>
/// Use a lower random value. /// Use a lower random value.
/// </summary> /// </summary>
LowProbability = 8, LowProbability = 1 << 3,
/// <summary> /// <summary>
/// Reserved. /// Reserved.
/// </summary> /// </summary>
Alternate = 16, Alternate = 1 << 4,
/// <summary> /// <summary>
/// Ignore the repeat count. /// Ignore the repeat count.
/// </summary> /// </summary>
ForceSigSlider = 32, ForceSigSlider = 1 << 5,
/// <summary> /// <summary>
/// Convert slider to circle. /// Convert slider to circle.
/// </summary> /// </summary>
ForceNotSlider = 64, ForceNotSlider = 1 << 6,
/// <summary> /// <summary>
/// Notes gathered together. /// Notes gathered together.
/// </summary> /// </summary>
Gathered = 128, Gathered = 1 << 7,
Mirror = 256, Mirror = 1 << 8,
/// <summary> /// <summary>
/// Change 0 -> 6. /// Change 0 -> 6.
/// </summary> /// </summary>
Reverse = 512, Reverse = 1 << 9,
/// <summary> /// <summary>
/// 1 -> 5 -> 1 -> 5 like reverse. /// 1 -> 5 -> 1 -> 5 like reverse.
/// </summary> /// </summary>
Cycle = 1024, Cycle = 1 << 10,
/// <summary> /// <summary>
/// Next note will be at column + 1. /// Next note will be at column + 1.
/// </summary> /// </summary>
Stair = 2048, Stair = 1 << 11,
/// <summary> /// <summary>
/// Next note will be at column - 1. /// Next note will be at column - 1.
/// </summary> /// </summary>
ReverseStair = 4096 ReverseStair = 1 << 12
} }
} }

View File

@ -3,7 +3,6 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Objects;
namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns
@ -21,16 +20,16 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns
public IEnumerable<ManiaHitObject> HitObjects => hitObjects; public IEnumerable<ManiaHitObject> HitObjects => hitObjects;
/// <summary> /// <summary>
/// Whether this pattern already contains a hit object in a code. /// Check whether a column of this patterns contains a hit object.
/// </summary> /// </summary>
/// <param name="column">The column index.</param> /// <param name="column">The column index.</param>
/// <returns>Whether this pattern already contains a hit object in <paramref name="column"/></returns> /// <returns>Whether the column with index <paramref name="column"/> contains a hit object.</returns>
public bool IsFilled(int column) => hitObjects.Exists(h => h.Column == column); public bool ColumnHasObject(int column) => hitObjects.Exists(h => h.Column == column);
/// <summary> /// <summary>
/// Amount of columns taken up by hit objects in this pattern. /// Amount of columns taken up by hit objects in this pattern.
/// </summary> /// </summary>
public int ColumnsFilled => HitObjects.GroupBy(h => h.Column).Count(); public int ColumnWithObjects => HitObjects.GroupBy(h => h.Column).Count();
/// <summary> /// <summary>
/// Adds a hit object to this pattern. /// Adds a hit object to this pattern.
@ -42,10 +41,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns
/// Copies hit object from another pattern to this one. /// Copies hit object from another pattern to this one.
/// </summary> /// </summary>
/// <param name="other">The other pattern.</param> /// <param name="other">The other pattern.</param>
public void Add(Pattern other) public void Add(Pattern other) => hitObjects.AddRange(other.HitObjects);
{
other.HitObjects.ForEach(Add);
}
/// <summary> /// <summary>
/// Clears this pattern, removing all hit objects. /// Clears this pattern, removing all hit objects.

View File

@ -1,6 +1,7 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Audio;
using osu.Game.Beatmaps.Timing; using osu.Game.Beatmaps.Timing;
using osu.Game.Database; using osu.Game.Database;
using osu.Game.Rulesets.Mania.Judgements; using osu.Game.Rulesets.Mania.Judgements;
@ -22,6 +23,11 @@ namespace osu.Game.Rulesets.Mania.Objects
public double Duration { get; set; } public double Duration { get; set; }
public double EndTime => StartTime + Duration; public double EndTime => StartTime + Duration;
/// <summary>
/// The samples to be played when this hold note is released.
/// </summary>
public SampleInfoList EndSamples = new SampleInfoList();
/// <summary> /// <summary>
/// The key-release hit windows for this hold note. /// The key-release hit windows for this hold note.
/// </summary> /// </summary>

View File

@ -9,11 +9,5 @@ namespace osu.Game.Rulesets.Mania.Objects
public abstract class ManiaHitObject : HitObject, IHasColumn public abstract class ManiaHitObject : HitObject, IHasColumn
{ {
public int Column { get; set; } public int Column { get; set; }
/// <summary>
/// The number of other <see cref="ManiaHitObject"/> that start at
/// the same time as this hit object.
/// </summary>
public int Siblings { get; set; }
} }
} }

View File

@ -51,7 +51,7 @@ namespace osu.Game.Rulesets.Mania.Timing
var controlPoint = drawableControlPoints.LastOrDefault(t => t.CanContain(drawable)) ?? drawableControlPoints.FirstOrDefault(); var controlPoint = drawableControlPoints.LastOrDefault(t => t.CanContain(drawable)) ?? drawableControlPoints.FirstOrDefault();
if (controlPoint == null) if (controlPoint == null)
throw new Exception("Could not find suitable timing section to add object to."); throw new InvalidOperationException("Could not find suitable timing section to add object to.");
controlPoint.Add(drawable); controlPoint.Add(drawable);
} }

View File

@ -34,7 +34,7 @@ namespace osu.Game.Rulesets.Mania.UI
ControlPoint firstTimingChange = Beatmap.TimingInfo.ControlPoints.FirstOrDefault(t => t.TimingChange); ControlPoint firstTimingChange = Beatmap.TimingInfo.ControlPoints.FirstOrDefault(t => t.TimingChange);
if (firstTimingChange == null) if (firstTimingChange == null)
throw new Exception("The Beatmap contains no timing points!"); throw new InvalidOperationException("The Beatmap contains no timing points!");
// Generate the timing points, making non-timing changes use the previous timing change // Generate the timing points, making non-timing changes use the previous timing change
var timingChanges = Beatmap.TimingInfo.ControlPoints.Select(c => var timingChanges = Beatmap.TimingInfo.ControlPoints.Select(c =>

View File

@ -47,6 +47,8 @@
<Reference Include="System.Xml" /> <Reference Include="System.Xml" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="Beatmaps\Patterns\Legacy\EndTimeObjectPatternGenerator.cs" />
<Compile Include="Beatmaps\Patterns\Legacy\DistanceObjectPatternGenerator.cs" />
<Compile Include="Beatmaps\Patterns\Legacy\PatternGenerator.cs" /> <Compile Include="Beatmaps\Patterns\Legacy\PatternGenerator.cs" />
<Compile Include="Beatmaps\Patterns\PatternGenerator.cs" /> <Compile Include="Beatmaps\Patterns\PatternGenerator.cs" />
<Compile Include="Beatmaps\Patterns\Legacy\PatternType.cs" /> <Compile Include="Beatmaps\Patterns\Legacy\PatternType.cs" />

View File

@ -30,6 +30,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
private readonly TextAwesome symbol; private readonly TextAwesome symbol;
private readonly Color4 baseColour = OsuColour.FromHex(@"002c3c");
private readonly Color4 fillColour = OsuColour.FromHex(@"005b7c");
private Color4 normalColour; private Color4 normalColour;
private Color4 completeColour; private Color4 completeColour;
@ -154,13 +157,13 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(OsuColour colours) private void load(OsuColour colours)
{ {
normalColour = colours.SpinnerBase; normalColour = baseColour;
background.AccentColour = normalColour; background.AccentColour = normalColour;
completeColour = colours.YellowLight.Opacity(0.6f); completeColour = colours.YellowLight.Opacity(0.75f);
disc.AccentColour = colours.SpinnerFill; disc.AccentColour = fillColour;
circle.Colour = colours.BlueDark; circle.Colour = colours.BlueDark;
glow.Colour = colours.BlueDark; glow.Colour = colours.BlueDark;
} }

View File

@ -2,7 +2,6 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using OpenTK.Graphics; using OpenTK.Graphics;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Sprites;
@ -28,9 +27,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
EdgeEffect = new EdgeEffect EdgeEffect = new EdgeEffect
{ {
Hollow = true,
Type = EdgeEffectType.Glow, Type = EdgeEffectType.Glow,
Radius = 14, Radius = 40,
Colour = value.Opacity(0.3f), Colour = value,
}; };
} }
} }

View File

@ -127,7 +127,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
if (Complete && updateCompleteTick()) if (Complete && updateCompleteTick())
{ {
background.Flush(flushType: typeof(TransformAlpha)); background.Flush(flushType: typeof(TransformAlpha));
background.FadeTo(tracking_alpha + 0.4f, 60, EasingTypes.OutExpo); background.FadeTo(tracking_alpha + 0.2f, 60, EasingTypes.OutExpo);
background.Delay(60); background.Delay(60);
background.FadeTo(tracking_alpha, 250, EasingTypes.OutQuint); background.FadeTo(tracking_alpha, 250, EasingTypes.OutQuint);
} }

View File

@ -2,12 +2,10 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System; using System;
using osu.Framework.Allocation;
using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Sprites;
using osu.Game.Graphics;
using OpenTK; using OpenTK;
using OpenTK.Graphics; using OpenTK.Graphics;
@ -15,24 +13,12 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
{ {
public class SpinnerTicks : Container public class SpinnerTicks : Container
{ {
private Color4 glowColour;
public SpinnerTicks() public SpinnerTicks()
{ {
Origin = Anchor.Centre; Origin = Anchor.Centre;
Anchor = Anchor.Centre; Anchor = Anchor.Centre;
RelativeSizeAxes = Axes.Both; RelativeSizeAxes = Axes.Both;
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
glowColour = colours.BlueDarker.Opacity(0.4f);
layout();
}
private void layout()
{
const int count = 18; const int count = 18;
for (int i = 0; i < count; i++) for (int i = 0; i < count; i++)
@ -44,8 +30,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
EdgeEffect = new EdgeEffect EdgeEffect = new EdgeEffect
{ {
Type = EdgeEffectType.Glow, Type = EdgeEffectType.Glow,
Radius = 20, Radius = 10,
Colour = glowColour, Colour = Color4.Gray.Opacity(0.2f),
}, },
RelativePositionAxes = Axes.Both, RelativePositionAxes = Axes.Both,
Masking = true, Masking = true,

View File

@ -2,11 +2,11 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using OpenTK.Graphics; using OpenTK.Graphics;
using osu.Game.Beatmaps.Events;
using osu.Game.Beatmaps.Timing; using osu.Game.Beatmaps.Timing;
using osu.Game.Database; using osu.Game.Database;
using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
namespace osu.Game.Beatmaps namespace osu.Game.Beatmaps
{ {
@ -18,7 +18,7 @@ namespace osu.Game.Beatmaps
{ {
public BeatmapInfo BeatmapInfo; public BeatmapInfo BeatmapInfo;
public TimingInfo TimingInfo = new TimingInfo(); public TimingInfo TimingInfo = new TimingInfo();
public EventInfo EventInfo = new EventInfo(); public List<BreakPeriod> Breaks = new List<BreakPeriod>();
public readonly List<Color4> ComboColors = new List<Color4> public readonly List<Color4> ComboColors = new List<Color4>
{ {
new Color4(17, 136, 170, 255), new Color4(17, 136, 170, 255),
@ -34,6 +34,11 @@ namespace osu.Game.Beatmaps
/// </summary> /// </summary>
public List<T> HitObjects; public List<T> HitObjects;
/// <summary>
/// Total amount of break time in the beatmap.
/// </summary>
public double TotalBreakTime => Breaks.Sum(b => b.Duration);
/// <summary> /// <summary>
/// Constructs a new beatmap. /// Constructs a new beatmap.
/// </summary> /// </summary>
@ -42,7 +47,7 @@ namespace osu.Game.Beatmaps
{ {
BeatmapInfo = original?.BeatmapInfo ?? BeatmapInfo; BeatmapInfo = original?.BeatmapInfo ?? BeatmapInfo;
TimingInfo = original?.TimingInfo ?? TimingInfo; TimingInfo = original?.TimingInfo ?? TimingInfo;
EventInfo = original?.EventInfo ?? EventInfo; Breaks = original?.Breaks ?? Breaks;
ComboColors = original?.ComboColors ?? ComboColors; ComboColors = original?.ComboColors ?? ComboColors;
} }
} }

View File

@ -1,13 +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.Events
{
public class BackgroundEvent : Event
{
/// <summary>
/// The file name.
/// </summary>
public string Filename;
}
}

View File

@ -1,13 +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.Events
{
public abstract class Event
{
/// <summary>
/// The event start time.
/// </summary>
public double StartTime;
}
}

View File

@ -1,33 +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.Events
{
public class EventInfo
{
/// <summary>
/// All the background events.
/// </summary>
public readonly List<BackgroundEvent> Backgrounds = new List<BackgroundEvent>();
/// <summary>
/// All the break events.
/// </summary>
public readonly List<BreakEvent> Breaks = new List<BreakEvent>();
/// <summary>
/// Total duration of all breaks.
/// </summary>
public double TotalBreakTime => Breaks.Sum(b => b.Duration);
/// <summary>
/// Retrieves the active background at a time.
/// </summary>
/// <param name="time">The time to retrieve the background at.</param>
/// <returns>The background.</returns>
public BackgroundEvent BackgroundAt(double time) => Backgrounds.FirstOrDefault(b => b.StartTime <= time);
}
}

View File

@ -5,7 +5,6 @@ using System;
using System.Globalization; using System.Globalization;
using System.IO; using System.IO;
using OpenTK.Graphics; using OpenTK.Graphics;
using osu.Game.Beatmaps.Events;
using osu.Game.Beatmaps.Timing; using osu.Game.Beatmaps.Timing;
using osu.Game.Beatmaps.Legacy; using osu.Game.Beatmaps.Legacy;
using osu.Game.Rulesets.Objects.Legacy; using osu.Game.Rulesets.Objects.Legacy;
@ -217,18 +216,12 @@ namespace osu.Game.Beatmaps.Formats
case EventType.Background: case EventType.Background:
string filename = split[2].Trim('"'); string filename = split[2].Trim('"');
beatmap.EventInfo.Backgrounds.Add(new BackgroundEvent
{
StartTime = double.Parse(split[1], NumberFormatInfo.InvariantInfo),
Filename = filename
});
if (type == EventType.Background) if (type == EventType.Background)
beatmap.BeatmapInfo.Metadata.BackgroundFile = filename; beatmap.BeatmapInfo.Metadata.BackgroundFile = filename;
break; break;
case EventType.Break: case EventType.Break:
var breakEvent = new BreakEvent var breakEvent = new BreakPeriod
{ {
StartTime = double.Parse(split[1], NumberFormatInfo.InvariantInfo), StartTime = double.Parse(split[1], NumberFormatInfo.InvariantInfo),
EndTime = double.Parse(split[2], NumberFormatInfo.InvariantInfo) EndTime = double.Parse(split[2], NumberFormatInfo.InvariantInfo)
@ -237,7 +230,7 @@ namespace osu.Game.Beatmaps.Formats
if (!breakEvent.HasEffect) if (!breakEvent.HasEffect)
return; return;
beatmap.EventInfo.Breaks.Add(breakEvent); beatmap.Breaks.Add(breakEvent);
break; break;
} }
} }

View File

@ -1,27 +1,32 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
namespace osu.Game.Beatmaps.Events namespace osu.Game.Beatmaps.Timing
{ {
public class BreakEvent : Event public class BreakPeriod
{ {
/// <summary> /// <summary>
/// The minimum duration required for a break to have any effect. /// The minimum duration required for a break to have any effect.
/// </summary> /// </summary>
private const double min_break_duration = 650; private const double min_break_duration = 650;
/// <summary>
/// The break start time.
/// </summary>
public double StartTime;
/// <summary> /// <summary>
/// The break end time. /// The break end time.
/// </summary> /// </summary>
public double EndTime; public double EndTime;
/// <summary> /// <summary>
/// The duration of the break. /// The break duration.
/// </summary> /// </summary>
public double Duration => EndTime - StartTime; public double Duration => EndTime - StartTime;
/// <summary> /// <summary>
/// Whether the break has any effect. Breaks that are too short are culled before they reach the EventInfo. /// Whether the break has any effect. Breaks that are too short are culled before they are added to the beatmap.
/// </summary> /// </summary>
public bool HasEffect => Duration >= min_break_duration; public bool HasEffect => Duration >= min_break_duration;
} }

View File

@ -87,8 +87,5 @@ namespace osu.Game.Graphics
public readonly Color4 RedDarker = FromHex(@"870000"); public readonly Color4 RedDarker = FromHex(@"870000");
public readonly Color4 ChatBlue = FromHex(@"17292e"); public readonly Color4 ChatBlue = FromHex(@"17292e");
public readonly Color4 SpinnerBase = FromHex(@"002c3c");
public readonly Color4 SpinnerFill = FromHex(@"005b7c");
} }
} }

View File

@ -288,7 +288,7 @@ namespace osu.Game.Online.API
{ {
APIRequest req; APIRequest req;
while (oldQueue.TryDequeue(out req)) while (oldQueue.TryDequeue(out req))
req.Fail(new Exception(@"Disconnected from server")); req.Fail(new WebException(@"Disconnected from server"));
} }
} }

View File

@ -2,6 +2,7 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using osu.Framework.IO.Network; using osu.Framework.IO.Network;
using osu.Game.Online.Chat; using osu.Game.Online.Chat;
@ -20,10 +21,7 @@ namespace osu.Game.Online.API.Requests
protected override WebRequest CreateWebRequest() protected override WebRequest CreateWebRequest()
{ {
string channelString = string.Empty; string channelString = string.Join(",", channels.Select(x => x.Id));
foreach (Channel c in channels)
channelString += c.Id + ",";
channelString = channelString.TrimEnd(',');
var req = base.CreateWebRequest(); var req = base.CreateWebRequest();
req.AddParameter(@"channels", channelString); req.AddParameter(@"channels", channelString);

View File

@ -23,7 +23,7 @@ namespace osu.Game.Online.Chat
[JsonProperty(@"channel_id")] [JsonProperty(@"channel_id")]
public int Id; public int Id;
public readonly SortedList<Message> Messages = new SortedList<Message>((m1, m2) => m1.Id.CompareTo(m2.Id)); public readonly SortedList<Message> Messages = new SortedList<Message>(Comparer<Message>.Default);
//internal bool Joined; //internal bool Joined;

View File

@ -8,7 +8,7 @@ using osu.Game.Users;
namespace osu.Game.Online.Chat namespace osu.Game.Online.Chat
{ {
public class Message public class Message : IComparable<Message>, IEquatable<Message>
{ {
[JsonProperty(@"message_id")] [JsonProperty(@"message_id")]
public readonly long Id; public readonly long Id;
@ -42,17 +42,11 @@ namespace osu.Game.Online.Chat
Id = id; Id = id;
} }
public override bool Equals(object obj) public int CompareTo(Message other) => Id.CompareTo(other.Id);
{
var objMessage = obj as Message;
return Id == objMessage?.Id; public bool Equals(Message other) => Id == other?.Id;
}
public override int GetHashCode() public override int GetHashCode() => Id.GetHashCode();
{
return Id.GetHashCode();
}
} }
public enum TargetType public enum TargetType

View File

@ -13,7 +13,7 @@ namespace osu.Game.Overlays.Chat
public class DrawableChannel : Container public class DrawableChannel : Container
{ {
public readonly Channel Channel; public readonly Channel Channel;
private readonly FillFlowContainer flow; private readonly FillFlowContainer<ChatLine> flow;
private readonly ScrollContainer scroll; private readonly ScrollContainer scroll;
public DrawableChannel(Channel channel) public DrawableChannel(Channel channel)
@ -29,7 +29,7 @@ namespace osu.Game.Overlays.Chat
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Children = new Drawable[] Children = new Drawable[]
{ {
flow = new FillFlowContainer flow = new FillFlowContainer<ChatLine>
{ {
Direction = FillDirection.Vertical, Direction = FillDirection.Vertical,
RelativeSizeAxes = Axes.X, RelativeSizeAxes = Axes.X,
@ -63,19 +63,18 @@ namespace osu.Game.Overlays.Chat
var displayMessages = newMessages.Skip(Math.Max(0, newMessages.Count() - Channel.MAX_HISTORY)); var displayMessages = newMessages.Skip(Math.Max(0, newMessages.Count() - Channel.MAX_HISTORY));
//up to last Channel.MAX_HISTORY messages
flow.Add(displayMessages.Select(m => new ChatLine(m)));
if (scroll.IsScrolledToEnd(10) || !flow.Children.Any()) if (scroll.IsScrolledToEnd(10) || !flow.Children.Any())
scrollToEnd(); scrollToEnd();
//up to last Channel.MAX_HISTORY messages var staleMessages = flow.Children.Where(c => c.LifetimeEnd == double.MaxValue).ToArray();
foreach (Message m in displayMessages) int count = staleMessages.Length - Channel.MAX_HISTORY;
{
var d = new ChatLine(m);
flow.Add(d);
}
while (flow.Children.Count(c => c.LifetimeEnd == double.MaxValue) > Channel.MAX_HISTORY) for (int i = 0; i < count; i++)
{ {
var d = flow.Children.First(c => c.LifetimeEnd == double.MaxValue); var d = staleMessages[i];
if (!scroll.IsScrolledToEnd(10)) if (!scroll.IsScrolledToEnd(10))
scroll.OffsetScrollPosition(-d.DrawHeight); scroll.OffsetScrollPosition(-d.DrawHeight);
d.Expire(); d.Expire();

View File

@ -229,6 +229,7 @@ namespace osu.Game.Overlays
Scheduler.Add(delegate Scheduler.Add(delegate
{ {
loading.FadeOut(100); loading.FadeOut(100);
loading.Expire();
addChannel(channels.Find(c => c.Name == @"#lazer")); addChannel(channels.Find(c => c.Name == @"#lazer"));
addChannel(channels.Find(c => c.Name == @"#osu")); addChannel(channels.Find(c => c.Name == @"#osu"));
@ -320,11 +321,8 @@ namespace osu.Game.Overlays
fetchReq = new GetMessagesRequest(careChannels, lastMessageId); fetchReq = new GetMessagesRequest(careChannels, lastMessageId);
fetchReq.Success += delegate (List<Message> messages) fetchReq.Success += delegate (List<Message> messages)
{ {
var ids = messages.Where(m => m.TargetType == TargetType.Channel).Select(m => m.TargetId).Distinct(); foreach (var group in messages.Where(m => m.TargetType == TargetType.Channel).GroupBy(m => m.TargetId))
careChannels.Find(c => c.Id == group.Key)?.AddNewMessages(group.ToArray());
//batch messages per channel.
foreach (var id in ids)
careChannels.Find(c => c.Id == id)?.AddNewMessages(messages.Where(m => m.TargetId == id).ToArray());
lastMessageId = messages.LastOrDefault()?.Id ?? lastMessageId; lastMessageId = messages.LastOrDefault()?.Id ?? lastMessageId;

View File

@ -57,12 +57,17 @@ namespace osu.Game.Screens
beatmap.Value = localMap; beatmap.Value = localMap;
} }
beatmap.ValueChanged += OnBeatmapChanged;
if (osuGame != null) if (osuGame != null)
ruleset.BindTo(osuGame.Ruleset); ruleset.BindTo(osuGame.Ruleset);
} }
protected override void LoadComplete()
{
base.LoadComplete();
beatmap.ValueChanged += OnBeatmapChanged;
}
/// <summary> /// <summary>
/// The global Beatmap was changed. /// The global Beatmap was changed.
/// </summary> /// </summary>

View File

@ -150,7 +150,7 @@ namespace osu.Game.Screens.Play
FramedClock = offsetClock, FramedClock = offsetClock,
OnRetry = Restart, OnRetry = Restart,
OnQuit = Exit, OnQuit = Exit,
CheckCanPause = () => ValidForResume && !HasFailed, CheckCanPause = () => ValidForResume && !HasFailed && !HitRenderer.HasReplayLoaded,
Retries = RestartCount, Retries = RestartCount,
OnPause = () => { OnPause = () => {
hudOverlay.KeyCounter.IsCounting = pauseContainer.IsPaused; hudOverlay.KeyCounter.IsCounting = pauseContainer.IsPaused;

View File

@ -155,7 +155,11 @@ namespace osu.Game.Screens.Select
index = (index + direction + groups.Count) % groups.Count; index = (index + direction + groups.Count) % groups.Count;
if (groups[index].State != BeatmapGroupState.Hidden) if (groups[index].State != BeatmapGroupState.Hidden)
{ {
SelectBeatmap(groups[index].BeatmapPanels.First().Beatmap); if (skipDifficulties)
SelectBeatmap(groups[index].SelectedPanel != null ? groups[index].SelectedPanel.Beatmap : groups[index].BeatmapPanels.First().Beatmap);
else
SelectBeatmap(direction == 1 ? groups[index].BeatmapPanels.First().Beatmap : groups[index].BeatmapPanels.Last().Beatmap);
return; return;
} }
} while (index != startIndex); } while (index != startIndex);
@ -167,10 +171,8 @@ namespace osu.Game.Screens.Select
if (visibleGroups.Count < 1) if (visibleGroups.Count < 1)
return; return;
BeatmapGroup group = visibleGroups[RNG.Next(visibleGroups.Count)]; BeatmapGroup group = visibleGroups[RNG.Next(visibleGroups.Count)];
BeatmapPanel panel = group?.BeatmapPanels.First();
if (panel == null) BeatmapPanel panel = group.BeatmapPanels[RNG.Next(group.BeatmapPanels.Count)];
return;
selectGroup(group, panel); selectGroup(group, panel);
} }
@ -409,7 +411,14 @@ namespace osu.Game.Screens.Select
int firstIndex = yPositions.BinarySearch(Current - Panel.MAX_HEIGHT); int firstIndex = yPositions.BinarySearch(Current - Panel.MAX_HEIGHT);
if (firstIndex < 0) firstIndex = ~firstIndex; if (firstIndex < 0) firstIndex = ~firstIndex;
int lastIndex = yPositions.BinarySearch(Current + drawHeight); int lastIndex = yPositions.BinarySearch(Current + drawHeight);
if (lastIndex < 0) lastIndex = ~lastIndex; if (lastIndex < 0)
{
lastIndex = ~lastIndex;
// Add the first panel of the last visible beatmap group to preload its data.
if (lastIndex != 0 && panels[lastIndex - 1] is BeatmapSetHeader)
lastIndex++;
}
// Add those panels within the previously found index range that should be displayed. // Add those panels within the previously found index range that should be displayed.
for (int i = firstIndex; i < lastIndex; ++i) for (int i = firstIndex; i < lastIndex; ++i)

View File

@ -9,6 +9,7 @@ using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Sprites;
using osu.Framework.Input;
using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterface;
using osu.Game.Screens.Menu; using osu.Game.Screens.Menu;
@ -68,8 +69,6 @@ namespace osu.Game.Screens.Select
public Footer() public Footer()
{ {
AlwaysReceiveInput = true;
RelativeSizeAxes = Axes.X; RelativeSizeAxes = Axes.X;
Height = HEIGHT; Height = HEIGHT;
Anchor = Anchor.BottomCentre; Anchor = Anchor.BottomCentre;
@ -124,5 +123,13 @@ namespace osu.Game.Screens.Select
updateModeLight(); updateModeLight();
} }
protected override bool InternalContains(Vector2 screenSpacePos) => base.InternalContains(screenSpacePos) || StartButton.Contains(screenSpacePos);
protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) => true;
protected override bool OnClick(InputState state) => true;
protected override bool OnDragStart(InputState state) => true;
} }
} }

View File

@ -57,14 +57,23 @@ namespace osu.Game.Screens.Select
{ {
beatmap?.Mods.BindTo(modSelect.SelectedMods); beatmap?.Mods.BindTo(modSelect.SelectedMods);
if (Beatmap?.Track != null)
Beatmap.Track.Looping = false;
beatmapDetails.Beatmap = beatmap; beatmapDetails.Beatmap = beatmap;
if (beatmap?.Track != null)
beatmap.Track.Looping = true;
base.OnBeatmapChanged(beatmap); base.OnBeatmapChanged(beatmap);
} }
protected override void OnResuming(Screen last) protected override void OnResuming(Screen last)
{ {
player = null; player = null;
Beatmap.Track.Looping = true;
base.OnResuming(last); base.OnResuming(last);
} }
@ -83,13 +92,21 @@ namespace osu.Game.Screens.Select
return true; return true;
} }
return base.OnExiting(next); if (base.OnExiting(next))
return true;
if (Beatmap?.Track != null)
Beatmap.Track.Looping = false;
return false;
} }
protected override void OnSelected() protected override void OnSelected()
{ {
if (player != null) return; if (player != null) return;
Beatmap.Track.Looping = false;
LoadComponentAsync(player = new PlayerLoader(new Player LoadComponentAsync(player = new PlayerLoader(new Player
{ {
Beatmap = Beatmap, //eagerly set this so it's present before push. Beatmap = Beatmap, //eagerly set this so it's present before push.

View File

@ -229,6 +229,8 @@ namespace osu.Game.Screens.Select
changeBackground(Beatmap); changeBackground(Beatmap);
selectionChangeNoBounce = Beatmap?.BeatmapInfo;
Content.FadeInFromZero(250); Content.FadeInFromZero(250);
beatmapInfoWedge.State = Visibility.Visible; beatmapInfoWedge.State = Visibility.Visible;

View File

@ -74,10 +74,6 @@
<Compile Include="Audio\SampleInfoList.cs" /> <Compile Include="Audio\SampleInfoList.cs" />
<Compile Include="Beatmaps\Drawables\BeatmapBackgroundSprite.cs" /> <Compile Include="Beatmaps\Drawables\BeatmapBackgroundSprite.cs" />
<Compile Include="Beatmaps\DifficultyCalculator.cs" /> <Compile Include="Beatmaps\DifficultyCalculator.cs" />
<Compile Include="Beatmaps\Events\BackgroundEvent.cs" />
<Compile Include="Beatmaps\Events\BreakEvent.cs" />
<Compile Include="Beatmaps\Events\Event.cs" />
<Compile Include="Beatmaps\Events\EventInfo.cs" />
<Compile Include="Online\API\Requests\PostMessageRequest.cs" /> <Compile Include="Online\API\Requests\PostMessageRequest.cs" />
<Compile Include="Online\Chat\ErrorMessage.cs" /> <Compile Include="Online\Chat\ErrorMessage.cs" />
<Compile Include="Overlays\Chat\ChatTabControl.cs" /> <Compile Include="Overlays\Chat\ChatTabControl.cs" />
@ -92,6 +88,7 @@
<Compile Include="Rulesets\Beatmaps\BeatmapConverter.cs" /> <Compile Include="Rulesets\Beatmaps\BeatmapConverter.cs" />
<Compile Include="Rulesets\Beatmaps\BeatmapProcessor.cs" /> <Compile Include="Rulesets\Beatmaps\BeatmapProcessor.cs" />
<Compile Include="Beatmaps\Legacy\LegacyBeatmap.cs" /> <Compile Include="Beatmaps\Legacy\LegacyBeatmap.cs" />
<Compile Include="Beatmaps\Timing\BreakPeriod.cs" />
<Compile Include="Beatmaps\Timing\TimeSignatures.cs" /> <Compile Include="Beatmaps\Timing\TimeSignatures.cs" />
<Compile Include="Beatmaps\Timing\TimingInfo.cs" /> <Compile Include="Beatmaps\Timing\TimingInfo.cs" />
<Compile Include="Database\BeatmapMetrics.cs" /> <Compile Include="Database\BeatmapMetrics.cs" />
@ -431,11 +428,11 @@
<Compile Include="Rulesets\Replays\AutoGenerator.cs" /> <Compile Include="Rulesets\Replays\AutoGenerator.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="$(SolutionDir)\osu-framework\osu.Framework\osu.Framework.csproj"> <ProjectReference Include="..\osu-framework\osu.Framework\osu.Framework.csproj">
<Project>{c76bf5b3-985e-4d39-95fe-97c9c879b83a}</Project> <Project>{c76bf5b3-985e-4d39-95fe-97c9c879b83a}</Project>
<Name>osu.Framework</Name> <Name>osu.Framework</Name>
</ProjectReference> </ProjectReference>
<ProjectReference Include="$(SolutionDir)\osu-resources\osu.Game.Resources\osu.Game.Resources.csproj"> <ProjectReference Include="..\osu-resources\osu.Game.Resources\osu.Game.Resources.csproj">
<Project>{d9a367c9-4c1a-489f-9b05-a0cea2b53b58}</Project> <Project>{d9a367c9-4c1a-489f-9b05-a0cea2b53b58}</Project>
<Name>osu.Game.Resources</Name> <Name>osu.Game.Resources</Name>
</ProjectReference> </ProjectReference>