mirror of
https://github.com/ppy/osu.git
synced 2025-01-15 08:12:56 +08:00
Merge branch 'master' into osu-direct
This commit is contained in:
commit
0634a3a5c5
@ -1 +1 @@
|
|||||||
Subproject commit d00a7df902074d0b3f1479904b7f322db9d39c1f
|
Subproject commit 42e26d49b9046fcb96c123b0dfb48e06d741e162
|
@ -11,11 +11,18 @@ 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;
|
||||||
|
using OpenTK;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.Beatmaps
|
namespace osu.Game.Rulesets.Mania.Beatmaps
|
||||||
{
|
{
|
||||||
public class ManiaBeatmapConverter : BeatmapConverter<ManiaHitObject>
|
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) };
|
protected override IEnumerable<Type> ValidConversionTypes { get; } = new[] { typeof(IHasXPosition) };
|
||||||
|
|
||||||
private Pattern lastPattern = new Pattern();
|
private Pattern lastPattern = new Pattern();
|
||||||
@ -55,6 +62,26 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
|
|||||||
yield return obj;
|
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>
|
/// <summary>
|
||||||
/// Method that generates hit objects for osu!mania specific beatmaps.
|
/// Method that generates hit objects for osu!mania specific beatmaps.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -83,28 +110,31 @@ 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
|
computeDensity(original.StartTime);
|
||||||
|
|
||||||
|
conversion = new HitObjectPatternGenerator(random, original, beatmap, lastPattern, lastTime, lastPosition, density, lastStair);
|
||||||
|
|
||||||
|
recordNote(original.StartTime, positionData.Position);
|
||||||
}
|
}
|
||||||
|
|
||||||
//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;
|
var stairPatternGenerator = (HitObjectPatternGenerator)conversion;
|
||||||
|
lastStair = stairPatternGenerator.StairType;
|
||||||
|
|
||||||
|
return newPattern.HitObjects;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,407 @@
|
|||||||
|
// 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.Timing;
|
||||||
|
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;
|
||||||
|
|
||||||
|
ControlPoint overridePoint;
|
||||||
|
ControlPoint controlPoint = beatmap.TimingInfo.TimingPointAt(hitObject.StartTime, out overridePoint);
|
||||||
|
|
||||||
|
var positionData = hitObject as IHasPosition;
|
||||||
|
|
||||||
|
float positionSeparation = ((positionData?.Position ?? Vector2.Zero) - previousPosition).Length;
|
||||||
|
double timeSeparation = hitObject.StartTime - previousTime;
|
||||||
|
|
||||||
|
double beatLength = controlPoint.BeatLength;
|
||||||
|
bool kiai = (overridePoint ?? controlPoint).KiaiMode;
|
||||||
|
|
||||||
|
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 >= beatLength / 2.5)
|
||||||
|
{
|
||||||
|
// Low density stream
|
||||||
|
convertType |= PatternType.Reverse | PatternType.LowProbability;
|
||||||
|
}
|
||||||
|
else if (density < beatLength / 2.5 || kiai)
|
||||||
|
{
|
||||||
|
// 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
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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.
|
||||||
|
@ -140,6 +140,26 @@ namespace osu.Game.Rulesets.Mania.Judgements
|
|||||||
Miss = BeatmapDifficulty.DifficultyRange(difficulty, miss_max, miss_mid, miss_min);
|
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>
|
/// <summary>
|
||||||
/// Constructs new hit windows which have been multiplied by a value.
|
/// Constructs new hit windows which have been multiplied by a value.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
21
osu.Game.Rulesets.Mania/Judgements/ManiaHitResult.cs
Normal file
21
osu.Game.Rulesets.Mania/Judgements/ManiaHitResult.cs
Normal 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
|
||||||
|
}
|
||||||
|
}
|
@ -10,5 +10,10 @@ namespace osu.Game.Rulesets.Mania.Judgements
|
|||||||
public override string ResultString => string.Empty;
|
public override string ResultString => string.Empty;
|
||||||
|
|
||||||
public override string MaxResultString => string.Empty;
|
public override string MaxResultString => string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The hit result.
|
||||||
|
/// </summary>
|
||||||
|
public ManiaHitResult ManiaResult;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,8 @@ using osu.Game.Rulesets.Objects.Drawables;
|
|||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces;
|
using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces;
|
||||||
using OpenTK.Graphics;
|
using OpenTK.Graphics;
|
||||||
|
using osu.Framework.Configuration;
|
||||||
|
using OpenTK.Input;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
||||||
{
|
{
|
||||||
@ -14,8 +16,8 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
|||||||
private readonly BodyPiece bodyPiece;
|
private readonly BodyPiece bodyPiece;
|
||||||
private readonly NotePiece tailPiece;
|
private readonly NotePiece tailPiece;
|
||||||
|
|
||||||
public DrawableHoldNote(HoldNote hitObject)
|
public DrawableHoldNote(HoldNote hitObject, Bindable<Key> key = null)
|
||||||
: base(hitObject)
|
: base(hitObject, key)
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both;
|
RelativeSizeAxes = Axes.Both;
|
||||||
Height = (float)HitObject.Duration;
|
Height = (float)HitObject.Duration;
|
||||||
|
@ -2,6 +2,8 @@
|
|||||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||||
|
|
||||||
using OpenTK.Graphics;
|
using OpenTK.Graphics;
|
||||||
|
using OpenTK.Input;
|
||||||
|
using osu.Framework.Configuration;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Game.Rulesets.Mania.Judgements;
|
using osu.Game.Rulesets.Mania.Judgements;
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
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>
|
public abstract class DrawableManiaHitObject<TObject> : DrawableHitObject<ManiaHitObject, ManiaJudgement>
|
||||||
where TObject : ManiaHitObject
|
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;
|
public new TObject HitObject;
|
||||||
|
|
||||||
protected DrawableManiaHitObject(TObject hitObject)
|
protected DrawableManiaHitObject(TObject hitObject, Bindable<Key> key = null)
|
||||||
: base(hitObject)
|
: base(hitObject)
|
||||||
{
|
{
|
||||||
HitObject = hitObject;
|
HitObject = hitObject;
|
||||||
|
|
||||||
|
if (key != null)
|
||||||
|
Key.BindTo(key);
|
||||||
|
|
||||||
RelativePositionAxes = Axes.Y;
|
RelativePositionAxes = Axes.Y;
|
||||||
Y = (float)HitObject.StartTime;
|
Y = (float)HitObject.StartTime;
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,13 @@
|
|||||||
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
|
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
|
||||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||||
|
|
||||||
|
using System;
|
||||||
using OpenTK.Graphics;
|
using OpenTK.Graphics;
|
||||||
|
using OpenTK.Input;
|
||||||
|
using osu.Framework.Configuration;
|
||||||
using osu.Framework.Graphics;
|
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.Mania.Objects.Drawables.Pieces;
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
|
|
||||||
@ -12,8 +17,8 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
|||||||
{
|
{
|
||||||
private readonly NotePiece headPiece;
|
private readonly NotePiece headPiece;
|
||||||
|
|
||||||
public DrawableNote(Note hitObject)
|
public DrawableNote(Note hitObject, Bindable<Key> key = null)
|
||||||
: base(hitObject)
|
: base(hitObject, key)
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both;
|
RelativeSizeAxes = Axes.Both;
|
||||||
Height = 100;
|
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)
|
if (!userTriggered)
|
||||||
Colour = Color4.Green;
|
{
|
||||||
|
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)
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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,10 +23,15 @@ 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>
|
||||||
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(TimingInfo timing, BeatmapDifficulty difficulty)
|
||||||
{
|
{
|
||||||
|
@ -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; }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,7 +15,7 @@ namespace osu.Game.Rulesets.Mania.Objects
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// The key-press hit window for this note.
|
/// The key-press hit window for this note.
|
||||||
/// </summary>
|
/// </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(TimingInfo timing, BeatmapDifficulty difficulty)
|
||||||
{
|
{
|
||||||
|
@ -22,5 +22,12 @@ namespace osu.Game.Rulesets.Mania.Scoring
|
|||||||
protected override void OnNewJudgement(ManiaJudgement judgement)
|
protected override void OnNewJudgement(ManiaJudgement judgement)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void Reset()
|
||||||
|
{
|
||||||
|
base.Reset();
|
||||||
|
|
||||||
|
Health.Value = 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -18,6 +18,8 @@ using osu.Game.Rulesets.Objects.Drawables;
|
|||||||
using osu.Game.Rulesets.Mania.Objects;
|
using osu.Game.Rulesets.Mania.Objects;
|
||||||
using osu.Game.Rulesets.Mania.Judgements;
|
using osu.Game.Rulesets.Mania.Judgements;
|
||||||
using osu.Game.Beatmaps.Timing;
|
using osu.Game.Beatmaps.Timing;
|
||||||
|
using System;
|
||||||
|
using osu.Framework.Configuration;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.UI
|
namespace osu.Game.Rulesets.Mania.UI
|
||||||
{
|
{
|
||||||
@ -33,7 +35,10 @@ namespace osu.Game.Rulesets.Mania.UI
|
|||||||
private const float column_width = 45;
|
private const float column_width = 45;
|
||||||
private const float special_column_width = 70;
|
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 Box background;
|
||||||
private readonly Container hitTargetBar;
|
private readonly Container hitTargetBar;
|
||||||
@ -95,6 +100,12 @@ namespace osu.Game.Rulesets.Mania.UI
|
|||||||
Name = "Hit objects",
|
Name = "Hit objects",
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
},
|
},
|
||||||
|
// For column lighting, we need to capture input events before the notes
|
||||||
|
new InputTarget
|
||||||
|
{
|
||||||
|
KeyDown = onKeyDown,
|
||||||
|
KeyUp = onKeyUp
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
new Container
|
new Container
|
||||||
@ -178,12 +189,9 @@ namespace osu.Game.Rulesets.Mania.UI
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Add(DrawableHitObject<ManiaHitObject, ManiaJudgement> hitObject)
|
public void Add(DrawableHitObject<ManiaHitObject, ManiaJudgement> hitObject) => ControlPointContainer.Add(hitObject);
|
||||||
{
|
|
||||||
ControlPointContainer.Add(hitObject);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override bool OnKeyDown(InputState state, KeyDownEventArgs args)
|
private bool onKeyDown(InputState state, KeyDownEventArgs args)
|
||||||
{
|
{
|
||||||
if (args.Repeat)
|
if (args.Repeat)
|
||||||
return false;
|
return false;
|
||||||
@ -197,7 +205,7 @@ namespace osu.Game.Rulesets.Mania.UI
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override bool OnKeyUp(InputState state, KeyUpEventArgs args)
|
private bool onKeyUp(InputState state, KeyUpEventArgs args)
|
||||||
{
|
{
|
||||||
if (args.Key == Key)
|
if (args.Key == Key)
|
||||||
{
|
{
|
||||||
@ -207,5 +215,24 @@ namespace osu.Game.Rulesets.Mania.UI
|
|||||||
|
|
||||||
return false;
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,8 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using OpenTK;
|
using OpenTK;
|
||||||
|
using OpenTK.Input;
|
||||||
|
using osu.Framework.Configuration;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Beatmaps.Timing;
|
using osu.Game.Beatmaps.Timing;
|
||||||
@ -34,7 +36,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 =>
|
||||||
@ -76,13 +78,19 @@ namespace osu.Game.Rulesets.Mania.UI
|
|||||||
|
|
||||||
protected override DrawableHitObject<ManiaHitObject, ManiaJudgement> GetVisualRepresentation(ManiaHitObject h)
|
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;
|
var holdNote = h as HoldNote;
|
||||||
if (holdNote != null)
|
if (holdNote != null)
|
||||||
return new DrawableHoldNote(holdNote);
|
return new DrawableHoldNote(holdNote, key);
|
||||||
|
|
||||||
var note = h as Note;
|
var note = h as Note;
|
||||||
if (note != null)
|
if (note != null)
|
||||||
return new DrawableNote(note);
|
return new DrawableNote(note, key);
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -55,7 +55,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;
|
private readonly ControlPointContainer barlineContainer;
|
||||||
|
|
||||||
@ -87,7 +88,7 @@ namespace osu.Game.Rulesets.Mania.UI
|
|||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Colour = Color4.Black
|
Colour = Color4.Black
|
||||||
},
|
},
|
||||||
Columns = new FillFlowContainer<Column>
|
columns = new FillFlowContainer<Column>
|
||||||
{
|
{
|
||||||
Name = "Columns",
|
Name = "Columns",
|
||||||
RelativeSizeAxes = Axes.Y,
|
RelativeSizeAxes = Axes.Y,
|
||||||
@ -114,7 +115,7 @@ namespace osu.Game.Rulesets.Mania.UI
|
|||||||
};
|
};
|
||||||
|
|
||||||
for (int i = 0; i < columnCount; i++)
|
for (int i = 0; i < columnCount; i++)
|
||||||
Columns.Add(new Column(timingChanges));
|
columns.Add(new Column(timingChanges));
|
||||||
|
|
||||||
TimeSpan = time_span_default;
|
TimeSpan = time_span_default;
|
||||||
}
|
}
|
||||||
@ -133,17 +134,17 @@ namespace osu.Game.Rulesets.Mania.UI
|
|||||||
// Set the special column + colour + key
|
// Set the special column + colour + key
|
||||||
for (int i = 0; i < columnCount; i++)
|
for (int i = 0; i < columnCount; i++)
|
||||||
{
|
{
|
||||||
Column column = Columns.Children.ElementAt(i);
|
Column column = Columns.ElementAt(i);
|
||||||
column.IsSpecial = isSpecialColumn(i);
|
column.IsSpecial = isSpecialColumn(i);
|
||||||
|
|
||||||
if (!column.IsSpecial)
|
if (!column.IsSpecial)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
column.Key = Key.Space;
|
column.Key.Value = Key.Space;
|
||||||
column.AccentColour = specialColumnColour;
|
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
|
// 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
|
// column colours are mirrored across their centre and special styles mess with this
|
||||||
@ -162,11 +163,11 @@ namespace osu.Game.Rulesets.Mania.UI
|
|||||||
|
|
||||||
int keyOffset = default_keys.Length / 2 - nonSpecialColumns.Count / 2 + i;
|
int keyOffset = default_keys.Length / 2 - nonSpecialColumns.Count / 2 + i;
|
||||||
if (keyOffset >= 0 && keyOffset < default_keys.Length)
|
if (keyOffset >= 0 && keyOffset < default_keys.Length)
|
||||||
column.Key = default_keys[keyOffset];
|
column.Key.Value = default_keys[keyOffset];
|
||||||
else
|
else
|
||||||
// There is no default key defined for this column. Let's set this to Unknown for now
|
// 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
|
// however note that this will be gone after bindings are in place
|
||||||
column.Key = Key.Unknown;
|
column.Key.Value = Key.Unknown;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -189,7 +190,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)
|
protected override bool OnKeyDown(InputState state, KeyDownEventArgs args)
|
||||||
{
|
{
|
||||||
@ -225,7 +226,7 @@ namespace osu.Game.Rulesets.Mania.UI
|
|||||||
timeSpan = MathHelper.Clamp(timeSpan, time_span_min, time_span_max);
|
timeSpan = MathHelper.Clamp(timeSpan, time_span_min, time_span_max);
|
||||||
|
|
||||||
barlineContainer.TimeSpan = value;
|
barlineContainer.TimeSpan = value;
|
||||||
Columns.Children.ForEach(c => c.ControlPointContainer.TimeSpan = value);
|
Columns.ForEach(c => c.ControlPointContainer.TimeSpan = value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -47,13 +47,17 @@
|
|||||||
<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\HitObjectPatternGenerator.cs" />
|
||||||
<Compile Include="Beatmaps\Patterns\Legacy\PatternType.cs" />
|
<Compile Include="Beatmaps\Patterns\Legacy\PatternType.cs" />
|
||||||
<Compile Include="Beatmaps\ManiaBeatmapConverter.cs" />
|
<Compile Include="Beatmaps\ManiaBeatmapConverter.cs" />
|
||||||
<Compile Include="Beatmaps\Patterns\Pattern.cs" />
|
<Compile Include="Beatmaps\Patterns\Pattern.cs" />
|
||||||
<Compile Include="MathUtils\FastRandom.cs" />
|
<Compile Include="MathUtils\FastRandom.cs" />
|
||||||
<Compile Include="Judgements\HitWindows.cs" />
|
<Compile Include="Judgements\HitWindows.cs" />
|
||||||
|
<Compile Include="Judgements\ManiaHitResult.cs" />
|
||||||
<Compile Include="Judgements\ManiaJudgement.cs" />
|
<Compile Include="Judgements\ManiaJudgement.cs" />
|
||||||
<Compile Include="ManiaDifficultyCalculator.cs" />
|
<Compile Include="ManiaDifficultyCalculator.cs" />
|
||||||
<Compile Include="Objects\Drawables\DrawableHoldNote.cs" />
|
<Compile Include="Objects\Drawables\DrawableHoldNote.cs" />
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -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,
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
}
|
}
|
60
osu.Game/Graphics/Containers/BeatSyncedContainer.cs
Normal file
60
osu.Game/Graphics/Containers/BeatSyncedContainer.cs
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
// 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.Configuration;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Beatmaps.Timing;
|
||||||
|
|
||||||
|
namespace osu.Game.Graphics.Containers
|
||||||
|
{
|
||||||
|
public class BeatSyncedContainer : Container
|
||||||
|
{
|
||||||
|
private readonly Bindable<WorkingBeatmap> beatmap = new Bindable<WorkingBeatmap>();
|
||||||
|
|
||||||
|
private int lastBeat;
|
||||||
|
private ControlPoint lastControlPoint;
|
||||||
|
|
||||||
|
protected override void Update()
|
||||||
|
{
|
||||||
|
if (beatmap.Value?.Track == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
double currentTrackTime = beatmap.Value.Track.CurrentTime;
|
||||||
|
ControlPoint overridePoint;
|
||||||
|
ControlPoint controlPoint = beatmap.Value.Beatmap.TimingInfo.TimingPointAt(currentTrackTime, out overridePoint);
|
||||||
|
|
||||||
|
if (controlPoint.BeatLength == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
bool kiai = (overridePoint ?? controlPoint).KiaiMode;
|
||||||
|
int beat = (int)((currentTrackTime - controlPoint.Time) / controlPoint.BeatLength);
|
||||||
|
|
||||||
|
// The beats before the start of the first control point are off by 1, this should do the trick
|
||||||
|
if (currentTrackTime < controlPoint.Time)
|
||||||
|
beat--;
|
||||||
|
|
||||||
|
if (controlPoint == lastControlPoint && beat == lastBeat)
|
||||||
|
return;
|
||||||
|
|
||||||
|
double offsetFromBeat = (controlPoint.Time - currentTrackTime) % controlPoint.BeatLength;
|
||||||
|
|
||||||
|
using (BeginDelayedSequence(offsetFromBeat, true))
|
||||||
|
OnNewBeat(beat, controlPoint.BeatLength, controlPoint.TimeSignature, kiai);
|
||||||
|
|
||||||
|
lastBeat = beat;
|
||||||
|
lastControlPoint = controlPoint;
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(OsuGameBase game)
|
||||||
|
{
|
||||||
|
beatmap.BindTo(game.Beatmap);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void OnNewBeat(int newBeat, double beatLength, TimeSignatures timeSignature, bool kiai)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
171
osu.Game/Graphics/Containers/SectionsContainer.cs
Normal file
171
osu.Game/Graphics/Containers/SectionsContainer.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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);
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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();
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
@ -1,34 +1,16 @@
|
|||||||
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
|
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
|
||||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||||
|
|
||||||
using System;
|
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Sprites;
|
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Graphics.Sprites;
|
using osu.Game.Graphics.Sprites;
|
||||||
using osu.Game.Graphics.UserInterface;
|
|
||||||
using OpenTK.Graphics;
|
|
||||||
|
|
||||||
namespace osu.Game.Overlays.Settings
|
namespace osu.Game.Overlays.Settings
|
||||||
{
|
{
|
||||||
public class SettingsHeader : Container
|
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]
|
[BackgroundDependencyLoader]
|
||||||
private void load(OsuColour colours)
|
private void load(OsuColour colours)
|
||||||
{
|
{
|
||||||
@ -37,11 +19,6 @@ namespace osu.Game.Overlays.Settings
|
|||||||
|
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
background = new Box
|
|
||||||
{
|
|
||||||
Colour = Color4.Black,
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
},
|
|
||||||
new FillFlowContainer
|
new FillFlowContainer
|
||||||
{
|
{
|
||||||
AutoSizeAxes = Axes.Y,
|
AutoSizeAxes = Axes.Y,
|
||||||
@ -53,7 +30,8 @@ namespace osu.Game.Overlays.Settings
|
|||||||
{
|
{
|
||||||
Text = "settings",
|
Text = "settings",
|
||||||
TextSize = 40,
|
TextSize = 40,
|
||||||
Margin = new MarginPadding {
|
Margin = new MarginPadding
|
||||||
|
{
|
||||||
Left = SettingsOverlay.CONTENT_MARGINS,
|
Left = SettingsOverlay.CONTENT_MARGINS,
|
||||||
Top = Toolbar.Toolbar.TOOLTIP_HEIGHT
|
Top = Toolbar.Toolbar.TOOLTIP_HEIGHT
|
||||||
},
|
},
|
||||||
@ -63,45 +41,15 @@ namespace osu.Game.Overlays.Settings
|
|||||||
Colour = colours.Pink,
|
Colour = colours.Pink,
|
||||||
Text = "Change the way osu! behaves",
|
Text = "Change the way osu! behaves",
|
||||||
TextSize = 18,
|
TextSize = 18,
|
||||||
Margin = new MarginPadding {
|
Margin = new MarginPadding
|
||||||
|
{
|
||||||
Left = SettingsOverlay.CONTENT_MARGINS,
|
Left = SettingsOverlay.CONTENT_MARGINS,
|
||||||
Bottom = 30
|
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -21,7 +21,7 @@ namespace osu.Game.Overlays.Settings
|
|||||||
private readonly Box backgroundBox;
|
private readonly Box backgroundBox;
|
||||||
private readonly Box selectionIndicator;
|
private readonly Box selectionIndicator;
|
||||||
private readonly Container text;
|
private readonly Container text;
|
||||||
public Action Action;
|
public Action<SettingsSection> Action;
|
||||||
|
|
||||||
private SettingsSection section;
|
private SettingsSection section;
|
||||||
public SettingsSection Section
|
public SettingsSection Section
|
||||||
@ -75,6 +75,7 @@ namespace osu.Game.Overlays.Settings
|
|||||||
{
|
{
|
||||||
Width = Sidebar.DEFAULT_WIDTH,
|
Width = Sidebar.DEFAULT_WIDTH,
|
||||||
RelativeSizeAxes = Axes.Y,
|
RelativeSizeAxes = Axes.Y,
|
||||||
|
Colour = OsuColour.Gray(0.6f),
|
||||||
Children = new[]
|
Children = new[]
|
||||||
{
|
{
|
||||||
headerText = new OsuSpriteText
|
headerText = new OsuSpriteText
|
||||||
@ -110,7 +111,7 @@ namespace osu.Game.Overlays.Settings
|
|||||||
|
|
||||||
protected override bool OnClick(InputState state)
|
protected override bool OnClick(InputState state)
|
||||||
{
|
{
|
||||||
Action?.Invoke();
|
Action?.Invoke(section);
|
||||||
backgroundBox.FlashColour(Color4.White, 400);
|
backgroundBox.FlashColour(Color4.White, 400);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -7,10 +7,11 @@ using osu.Framework.Allocation;
|
|||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
using osu.Game.Overlays.Settings;
|
|
||||||
using System;
|
|
||||||
using osu.Game.Overlays.Settings.Sections;
|
|
||||||
using osu.Framework.Input;
|
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
|
namespace osu.Game.Overlays
|
||||||
{
|
{
|
||||||
@ -26,18 +27,13 @@ namespace osu.Game.Overlays
|
|||||||
|
|
||||||
private const float sidebar_padding = 10;
|
private const float sidebar_padding = 10;
|
||||||
|
|
||||||
private ScrollContainer scrollContainer;
|
|
||||||
private Sidebar sidebar;
|
private Sidebar sidebar;
|
||||||
private SidebarButton[] sidebarButtons;
|
private SidebarButton[] sidebarButtons;
|
||||||
private SettingsSection[] sections;
|
private SidebarButton selectedSidebarButton;
|
||||||
|
|
||||||
private SettingsHeader header;
|
private SettingsSectionsContainer sectionsContainer;
|
||||||
|
|
||||||
private SettingsFooter footer;
|
private SearchTextBox searchTextBox;
|
||||||
|
|
||||||
private SearchContainer searchContainer;
|
|
||||||
|
|
||||||
private float lastKnownScroll;
|
|
||||||
|
|
||||||
public SettingsOverlay()
|
public SettingsOverlay()
|
||||||
{
|
{
|
||||||
@ -48,7 +44,7 @@ namespace osu.Game.Overlays
|
|||||||
[BackgroundDependencyLoader(permitNulls: true)]
|
[BackgroundDependencyLoader(permitNulls: true)]
|
||||||
private void load(OsuGame game)
|
private void load(OsuGame game)
|
||||||
{
|
{
|
||||||
sections = new SettingsSection[]
|
var sections = new SettingsSection[]
|
||||||
{
|
{
|
||||||
new GeneralSection(),
|
new GeneralSection(),
|
||||||
new GraphicsSection(),
|
new GraphicsSection(),
|
||||||
@ -68,27 +64,27 @@ namespace osu.Game.Overlays
|
|||||||
Colour = Color4.Black,
|
Colour = Color4.Black,
|
||||||
Alpha = 0.6f,
|
Alpha = 0.6f,
|
||||||
},
|
},
|
||||||
scrollContainer = new ScrollContainer
|
sectionsContainer = new SettingsSectionsContainer
|
||||||
{
|
{
|
||||||
ScrollDraggerVisible = false,
|
|
||||||
RelativeSizeAxes = Axes.Y,
|
RelativeSizeAxes = Axes.Y,
|
||||||
Width = width,
|
Width = width,
|
||||||
Margin = new MarginPadding { Left = SIDEBAR_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,
|
Top = 20,
|
||||||
RelativeSizeAxes = Axes.X,
|
Bottom = 20
|
||||||
Direction = FillDirection.Vertical,
|
|
||||||
Children = sections,
|
|
||||||
},
|
},
|
||||||
footer = new SettingsFooter(),
|
Exit = Hide,
|
||||||
header = new SettingsHeader(() => scrollContainer.Current)
|
},
|
||||||
{
|
Sections = sections,
|
||||||
Exit = Hide,
|
Footer = new SettingsFooter()
|
||||||
},
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
sidebar = new Sidebar
|
sidebar = new Sidebar
|
||||||
{
|
{
|
||||||
@ -96,84 +92,89 @@ namespace osu.Game.Overlays
|
|||||||
Children = sidebarButtons = sections.Select(section =>
|
Children = sidebarButtons = sections.Select(section =>
|
||||||
new SidebarButton
|
new SidebarButton
|
||||||
{
|
{
|
||||||
Selected = sections[0] == section,
|
|
||||||
Section = section,
|
Section = section,
|
||||||
Action = () => scrollContainer.ScrollIntoView(section),
|
Action = sectionsContainer.ScrollContainer.ScrollIntoView,
|
||||||
}
|
}
|
||||||
).ToArray()
|
).ToArray()
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
header.SearchTextBox.Current.ValueChanged += newValue => searchContainer.SearchTerm = newValue;
|
selectedSidebarButton = sidebarButtons[0];
|
||||||
|
selectedSidebarButton.Selected = true;
|
||||||
|
|
||||||
scrollContainer.Padding = new MarginPadding { Top = game?.Toolbar.DrawHeight ?? 0 };
|
sectionsContainer.SelectedSection.ValueChanged += section =>
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
{
|
{
|
||||||
lastKnownScroll = currentScroll;
|
selectedSidebarButton.Selected = false;
|
||||||
|
selectedSidebarButton = sidebarButtons.Single(b => b.Section == section);
|
||||||
|
selectedSidebarButton.Selected = true;
|
||||||
|
};
|
||||||
|
|
||||||
SettingsSection bestCandidate = null;
|
searchTextBox.Current.ValueChanged += newValue => sectionsContainer.SearchContainer.SearchTerm = newValue;
|
||||||
float bestDistance = float.MaxValue;
|
|
||||||
|
|
||||||
foreach (SettingsSection section in sections)
|
sectionsContainer.Padding = new MarginPadding { Top = game?.Toolbar.DrawHeight ?? 0 };
|
||||||
{
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void PopIn()
|
protected override void PopIn()
|
||||||
{
|
{
|
||||||
base.PopIn();
|
base.PopIn();
|
||||||
|
|
||||||
scrollContainer.MoveToX(0, TRANSITION_LENGTH, EasingTypes.OutQuint);
|
sectionsContainer.MoveToX(0, TRANSITION_LENGTH, EasingTypes.OutQuint);
|
||||||
sidebar.MoveToX(0, TRANSITION_LENGTH, EasingTypes.OutQuint);
|
sidebar.MoveToX(0, TRANSITION_LENGTH, EasingTypes.OutQuint);
|
||||||
FadeTo(1, TRANSITION_LENGTH / 2);
|
FadeTo(1, TRANSITION_LENGTH / 2);
|
||||||
|
|
||||||
header.SearchTextBox.HoldFocus = true;
|
searchTextBox.HoldFocus = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void PopOut()
|
protected override void PopOut()
|
||||||
{
|
{
|
||||||
base.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);
|
sidebar.MoveToX(-SIDEBAR_WIDTH, TRANSITION_LENGTH, EasingTypes.OutQuint);
|
||||||
FadeTo(0, TRANSITION_LENGTH / 2);
|
FadeTo(0, TRANSITION_LENGTH / 2);
|
||||||
|
|
||||||
header.SearchTextBox.HoldFocus = false;
|
searchTextBox.HoldFocus = false;
|
||||||
header.SearchTextBox.TriggerFocusLost();
|
searchTextBox.TriggerFocusLost();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override bool OnFocus(InputState state)
|
protected override bool OnFocus(InputState state)
|
||||||
{
|
{
|
||||||
header.SearchTextBox.TriggerFocus(state);
|
searchTextBox.TriggerFocus(state);
|
||||||
return false;
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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>
|
||||||
|
@ -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;
|
||||||
|
@ -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)
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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.
|
||||||
|
@ -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;
|
||||||
|
@ -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" />
|
||||||
@ -85,12 +81,14 @@
|
|||||||
<Compile Include="Overlays\Music\PlaylistItem.cs" />
|
<Compile Include="Overlays\Music\PlaylistItem.cs" />
|
||||||
<Compile Include="Overlays\Music\PlaylistList.cs" />
|
<Compile Include="Overlays\Music\PlaylistList.cs" />
|
||||||
<Compile Include="Overlays\OnScreenDisplay.cs" />
|
<Compile Include="Overlays\OnScreenDisplay.cs" />
|
||||||
|
<Compile Include="Graphics\Containers\SectionsContainer.cs" />
|
||||||
<Compile Include="Overlays\Settings\SettingsHeader.cs" />
|
<Compile Include="Overlays\Settings\SettingsHeader.cs" />
|
||||||
<Compile Include="Overlays\Settings\Sections\Audio\MainMenuSettings.cs" />
|
<Compile Include="Overlays\Settings\Sections\Audio\MainMenuSettings.cs" />
|
||||||
<Compile Include="Overlays\Toolbar\ToolbarChatButton.cs" />
|
<Compile Include="Overlays\Toolbar\ToolbarChatButton.cs" />
|
||||||
<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" />
|
||||||
@ -295,6 +293,7 @@
|
|||||||
<Compile Include="Graphics\UserInterface\RollingCounter.cs" />
|
<Compile Include="Graphics\UserInterface\RollingCounter.cs" />
|
||||||
<Compile Include="Graphics\UserInterface\Volume\VolumeControlReceptor.cs" />
|
<Compile Include="Graphics\UserInterface\Volume\VolumeControlReceptor.cs" />
|
||||||
<Compile Include="Graphics\Backgrounds\Background.cs" />
|
<Compile Include="Graphics\Backgrounds\Background.cs" />
|
||||||
|
<Compile Include="Graphics\Containers\BeatSyncedContainer.cs" />
|
||||||
<Compile Include="Graphics\Containers\ParallaxContainer.cs" />
|
<Compile Include="Graphics\Containers\ParallaxContainer.cs" />
|
||||||
<Compile Include="Graphics\Cursor\MenuCursor.cs" />
|
<Compile Include="Graphics\Cursor\MenuCursor.cs" />
|
||||||
<Compile Include="Graphics\Processing\RatioAdjust.cs" />
|
<Compile Include="Graphics\Processing\RatioAdjust.cs" />
|
||||||
@ -442,11 +441,11 @@
|
|||||||
<Compile Include="Graphics\Containers\ReverseDepthFillFlowContainer.cs" />
|
<Compile Include="Graphics\Containers\ReverseDepthFillFlowContainer.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>
|
||||||
|
Loading…
Reference in New Issue
Block a user