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

Merge branch 'master' of git://github.com/ppy/osu into channel-selector-tab-item

This commit is contained in:
Jorolf 2017-05-23 17:32:48 +02:00
commit 7ad10009e6
17 changed files with 708 additions and 140 deletions

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

View File

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

View File

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

View File

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

View File

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

View File

@ -51,6 +51,7 @@
<Compile Include="Beatmaps\Patterns\Legacy\DistanceObjectPatternGenerator.cs" />
<Compile Include="Beatmaps\Patterns\Legacy\PatternGenerator.cs" />
<Compile Include="Beatmaps\Patterns\PatternGenerator.cs" />
<Compile Include="Beatmaps\Patterns\Legacy\HitObjectPatternGenerator.cs" />
<Compile Include="Beatmaps\Patterns\Legacy\PatternType.cs" />
<Compile Include="Beatmaps\ManiaBeatmapConverter.cs" />
<Compile Include="Beatmaps\Patterns\Pattern.cs" />

View File

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

View File

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

View File

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

View File

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

View File

@ -82,7 +82,7 @@ namespace osu.Game
if (args?.Length > 0)
{
var paths = args.Where(a => !a.StartsWith(@"-"));
Task.Run(() => BeatmapDatabase.Import(paths));
Task.Run(() => BeatmapDatabase.Import(paths.ToArray()));
}
Dependencies.Cache(this);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -293,6 +293,7 @@
<Compile Include="Graphics\UserInterface\RollingCounter.cs" />
<Compile Include="Graphics\UserInterface\Volume\VolumeControlReceptor.cs" />
<Compile Include="Graphics\Backgrounds\Background.cs" />
<Compile Include="Graphics\Containers\BeatSyncedContainer.cs" />
<Compile Include="Graphics\Containers\ParallaxContainer.cs" />
<Compile Include="Graphics\Cursor\MenuCursor.cs" />
<Compile Include="Graphics\Processing\RatioAdjust.cs" />