mirror of
https://github.com/ppy/osu.git
synced 2025-01-15 16:42:57 +08:00
Merge branch 'master' into multiplayer-room-settings
This commit is contained in:
commit
e6d5bbd886
@ -24,7 +24,9 @@ Clone the repository including submodules
|
||||
Build and run
|
||||
|
||||
- Using Visual Studio 2017, Rider or Visual Studio Code (configurations are included)
|
||||
- From command line using `dotnet run --project osu.Desktop`
|
||||
- From command line using `dotnet run --project osu.Desktop`. When building for non-development purposes, add `-c Release` to gain higher performance.
|
||||
|
||||
Note: If you run from command line under linux, you will need to prefix the output folder to your `LD_LIBRARY_PATH`. See `.vscode/launch.json` for an example
|
||||
|
||||
If you run into issues building you may need to restore nuget packages (commonly via `dotnet restore`). Visual Studio Code users must run `Restore` task from debug tab before attempt to build.
|
||||
|
||||
|
@ -4,7 +4,11 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using osu.Framework;
|
||||
using osu.Framework.Development;
|
||||
using osu.Framework.Logging;
|
||||
using osu.Framework.Platform;
|
||||
using osu.Game.IPC;
|
||||
|
||||
@ -20,6 +24,8 @@ namespace osu.Desktop
|
||||
|
||||
using (DesktopGameHost host = Host.GetSuitableHost(@"osu", true))
|
||||
{
|
||||
host.ExceptionThrown += handleException;
|
||||
|
||||
if (!host.IsPrimaryInstance)
|
||||
{
|
||||
var importer = new ArchiveImportIPCChannel(host);
|
||||
@ -45,5 +51,24 @@ namespace osu.Desktop
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
private static int allowableExceptions = DebugUtils.IsDebugBuild ? 0 : 1;
|
||||
|
||||
/// <summary>
|
||||
/// Allow a maximum of one unhandled exception, per second of execution.
|
||||
/// </summary>
|
||||
/// <param name="arg"></param>
|
||||
/// <returns></returns>
|
||||
private static bool handleException(Exception arg)
|
||||
{
|
||||
bool continueExecution = Interlocked.Decrement(ref allowableExceptions) >= 0;
|
||||
|
||||
Logger.Log($"Unhandled exception has been {(continueExecution ? $"allowed with {allowableExceptions} more allowable exceptions" : "denied")} .");
|
||||
|
||||
// restore the stock of allowable exceptions after a short delay.
|
||||
Task.Delay(1000).ContinueWith(_ => Interlocked.Increment(ref allowableExceptions));
|
||||
|
||||
return continueExecution;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -28,8 +28,8 @@
|
||||
<ItemGroup Label="Package References">
|
||||
<PackageReference Include="System.IO.Packaging" Version="4.5.0" />
|
||||
<PackageReference Include="ppy.squirrel.windows" Version="1.8.0.5" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.1" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="2.1.1" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.2" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="2.1.2" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Label="Resources">
|
||||
<EmbeddedResource Include="lazer.ico" />
|
||||
|
@ -41,6 +41,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
|
||||
RepeatCount = curveData.RepeatCount,
|
||||
X = (positionData?.X ?? 0) / CatchPlayfield.BASE_WIDTH,
|
||||
NewCombo = comboData?.NewCombo ?? false,
|
||||
ComboOffset = comboData?.ComboOffset ?? 0,
|
||||
LegacyLastTickOffset = legacyOffset?.LegacyLastTickOffset ?? 0
|
||||
};
|
||||
}
|
||||
@ -51,7 +52,8 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
|
||||
StartTime = obj.StartTime,
|
||||
Samples = obj.Samples,
|
||||
Duration = endTime.Duration,
|
||||
NewCombo = comboData?.NewCombo ?? false
|
||||
NewCombo = comboData?.NewCombo ?? false,
|
||||
ComboOffset = comboData?.ComboOffset ?? 0,
|
||||
};
|
||||
}
|
||||
else
|
||||
@ -61,6 +63,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
|
||||
StartTime = obj.StartTime,
|
||||
Samples = obj.Samples,
|
||||
NewCombo = comboData?.NewCombo ?? false,
|
||||
ComboOffset = comboData?.ComboOffset ?? 0,
|
||||
X = (positionData?.X ?? 0) / CatchPlayfield.BASE_WIDTH
|
||||
};
|
||||
}
|
||||
|
@ -20,6 +20,8 @@ namespace osu.Game.Rulesets.Catch.Objects
|
||||
|
||||
public virtual bool NewCombo { get; set; }
|
||||
|
||||
public int ComboOffset { get; set; }
|
||||
|
||||
public int IndexInCurrentCombo { get; set; }
|
||||
|
||||
public int ComboIndex { get; set; }
|
||||
|
@ -407,9 +407,7 @@ namespace osu.Game.Rulesets.Catch.UI
|
||||
/// </summary>
|
||||
public void Explode()
|
||||
{
|
||||
var fruit = caughtFruit.ToArray();
|
||||
|
||||
foreach (var f in fruit)
|
||||
foreach (var f in caughtFruit.ToArray())
|
||||
Explode(f);
|
||||
}
|
||||
|
||||
@ -422,15 +420,15 @@ namespace osu.Game.Rulesets.Catch.UI
|
||||
fruit.Anchor = Anchor.TopLeft;
|
||||
fruit.Position = caughtFruit.ToSpaceOfOtherDrawable(fruit.DrawPosition, ExplodingFruitTarget);
|
||||
|
||||
caughtFruit.Remove(fruit);
|
||||
if (!caughtFruit.Remove(fruit))
|
||||
// we may have already been removed by a previous operation (due to the weird OnLoadComplete scheduling).
|
||||
// this avoids a crash on potentially attempting to Add a fruit to ExplodingFruitTarget twice.
|
||||
return;
|
||||
|
||||
ExplodingFruitTarget.Add(fruit);
|
||||
}
|
||||
|
||||
fruit.MoveToY(fruit.Y - 50, 250, Easing.OutSine)
|
||||
.Then()
|
||||
.MoveToY(fruit.Y + 50, 500, Easing.InSine);
|
||||
|
||||
fruit.MoveToY(fruit.Y - 50, 250, Easing.OutSine).Then().MoveToY(fruit.Y + 50, 500, Easing.InSine);
|
||||
fruit.MoveToX(fruit.X + originalX * 6, 1000);
|
||||
fruit.FadeOut(750);
|
||||
|
||||
|
@ -173,19 +173,18 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
||||
var pattern = new Pattern();
|
||||
|
||||
int usableColumns = TotalColumns - RandomStart - PreviousPattern.ColumnWithObjects;
|
||||
int nextColumn = Random.Next(RandomStart, TotalColumns);
|
||||
int nextColumn = GetRandomColumn();
|
||||
for (int i = 0; i < Math.Min(usableColumns, noteCount); i++)
|
||||
{
|
||||
while (pattern.ColumnHasObject(nextColumn) || PreviousPattern.ColumnHasObject(nextColumn)) //find available column
|
||||
nextColumn = Random.Next(RandomStart, TotalColumns);
|
||||
// Find available column
|
||||
nextColumn = FindAvailableColumn(nextColumn, pattern, PreviousPattern);
|
||||
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, TotalColumns);
|
||||
nextColumn = FindAvailableColumn(nextColumn, pattern);
|
||||
addToPattern(pattern, nextColumn, startTime, EndTime);
|
||||
}
|
||||
|
||||
@ -210,18 +209,13 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
||||
|
||||
int nextColumn = GetColumn((HitObject as IHasXPosition)?.X ?? 0, true);
|
||||
if (convertType.HasFlag(PatternType.ForceNotStack) && PreviousPattern.ColumnWithObjects < TotalColumns)
|
||||
{
|
||||
while (PreviousPattern.ColumnHasObject(nextColumn))
|
||||
nextColumn = Random.Next(RandomStart, TotalColumns);
|
||||
}
|
||||
nextColumn = FindAvailableColumn(nextColumn, PreviousPattern);
|
||||
|
||||
int lastColumn = nextColumn;
|
||||
for (int i = 0; i < noteCount; i++)
|
||||
{
|
||||
addToPattern(pattern, nextColumn, startTime, startTime);
|
||||
while (nextColumn == lastColumn)
|
||||
nextColumn = Random.Next(RandomStart, TotalColumns);
|
||||
|
||||
nextColumn = FindAvailableColumn(nextColumn, validation: c => c != lastColumn);
|
||||
lastColumn = nextColumn;
|
||||
startTime += SegmentDuration;
|
||||
}
|
||||
@ -313,7 +307,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
||||
if (TotalColumns > 2)
|
||||
addToPattern(pattern, nextColumn, startTime, startTime);
|
||||
|
||||
nextColumn = Random.Next(RandomStart, TotalColumns);
|
||||
nextColumn = GetRandomColumn();
|
||||
startTime += SegmentDuration;
|
||||
}
|
||||
|
||||
@ -392,16 +386,11 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
||||
|
||||
int nextColumn = GetColumn((HitObject as IHasXPosition)?.X ?? 0, true);
|
||||
if (convertType.HasFlag(PatternType.ForceNotStack) && PreviousPattern.ColumnWithObjects < TotalColumns)
|
||||
{
|
||||
while (PreviousPattern.ColumnHasObject(nextColumn))
|
||||
nextColumn = Random.Next(RandomStart, TotalColumns);
|
||||
}
|
||||
nextColumn = FindAvailableColumn(nextColumn, PreviousPattern);
|
||||
|
||||
for (int i = 0; i < columnRepeat; i++)
|
||||
{
|
||||
while (pattern.ColumnHasObject(nextColumn))
|
||||
nextColumn = Random.Next(RandomStart, TotalColumns);
|
||||
|
||||
nextColumn = FindAvailableColumn(nextColumn, pattern);
|
||||
addToPattern(pattern, nextColumn, startTime, EndTime);
|
||||
startTime += SegmentDuration;
|
||||
}
|
||||
@ -426,15 +415,12 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
||||
|
||||
int holdColumn = GetColumn((HitObject as IHasXPosition)?.X ?? 0, true);
|
||||
if (convertType.HasFlag(PatternType.ForceNotStack) && PreviousPattern.ColumnWithObjects < TotalColumns)
|
||||
{
|
||||
while (PreviousPattern.ColumnHasObject(holdColumn))
|
||||
holdColumn = Random.Next(RandomStart, TotalColumns);
|
||||
}
|
||||
holdColumn = FindAvailableColumn(holdColumn, PreviousPattern);
|
||||
|
||||
// Create the hold note
|
||||
addToPattern(pattern, holdColumn, startTime, EndTime);
|
||||
|
||||
int nextColumn = Random.Next(RandomStart, TotalColumns);
|
||||
int nextColumn = GetRandomColumn();
|
||||
int noteCount;
|
||||
if (ConversionDifficulty > 6.5)
|
||||
noteCount = GetRandomNoteCount(0.63, 0);
|
||||
@ -455,8 +441,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
||||
{
|
||||
for (int j = 0; j < noteCount; j++)
|
||||
{
|
||||
while (rowPattern.ColumnHasObject(nextColumn) || nextColumn == holdColumn)
|
||||
nextColumn = Random.Next(RandomStart, TotalColumns);
|
||||
nextColumn = FindAvailableColumn(nextColumn, validation: c => c != holdColumn, patterns: rowPattern);
|
||||
addToPattern(rowPattern, nextColumn, startTime, startTime);
|
||||
}
|
||||
}
|
||||
|
@ -39,32 +39,17 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
||||
addToPattern(pattern, 0, generateHold);
|
||||
break;
|
||||
case 8:
|
||||
addToPattern(pattern, getNextRandomColumn(RandomStart), generateHold);
|
||||
addToPattern(pattern, FindAvailableColumn(GetRandomColumn(), PreviousPattern), generateHold);
|
||||
break;
|
||||
default:
|
||||
if (TotalColumns > 0)
|
||||
addToPattern(pattern, getNextRandomColumn(0), generateHold);
|
||||
addToPattern(pattern, GetRandomColumn(), generateHold);
|
||||
break;
|
||||
}
|
||||
|
||||
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, TotalColumns);
|
||||
|
||||
while (PreviousPattern.ColumnHasObject(nextColumn))
|
||||
nextColumn = Random.Next(start, TotalColumns);
|
||||
|
||||
return nextColumn;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructs and adds a note to a pattern.
|
||||
/// </summary>
|
||||
|
@ -25,9 +25,6 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
||||
PatternType lastStair, IBeatmap originalBeatmap)
|
||||
: base(random, hitObject, beatmap, previousPattern, originalBeatmap)
|
||||
{
|
||||
if (previousTime > hitObject.StartTime) throw new ArgumentOutOfRangeException(nameof(previousTime));
|
||||
if (density < 0) throw new ArgumentOutOfRangeException(nameof(density));
|
||||
|
||||
StairType = lastStair;
|
||||
|
||||
TimingControlPoint timingPoint = beatmap.ControlPointInfo.TimingPointAt(hitObject.StartTime);
|
||||
@ -234,22 +231,27 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
||||
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.HasFlag(PatternType.Gathered))
|
||||
{
|
||||
nextColumn++;
|
||||
if (nextColumn == TotalColumns)
|
||||
nextColumn = RandomStart;
|
||||
}
|
||||
else
|
||||
nextColumn = Random.Next(RandomStart, TotalColumns);
|
||||
}
|
||||
nextColumn = allowStacking
|
||||
? FindAvailableColumn(nextColumn, nextColumn: getNextColumn, patterns: pattern)
|
||||
: FindAvailableColumn(nextColumn, nextColumn: getNextColumn, patterns: new[] { pattern, PreviousPattern });
|
||||
|
||||
addToPattern(pattern, nextColumn);
|
||||
}
|
||||
|
||||
return pattern;
|
||||
|
||||
int getNextColumn(int last)
|
||||
{
|
||||
if (convertType.HasFlag(PatternType.Gathered))
|
||||
{
|
||||
last++;
|
||||
if (last == TotalColumns)
|
||||
last = RandomStart;
|
||||
}
|
||||
else
|
||||
last = GetRandomColumn();
|
||||
return last;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -286,17 +288,19 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
||||
/// <returns>The <see cref="Pattern"/> containing the hit objects.</returns>
|
||||
private Pattern generateRandomPatternWithMirrored(double centreProbability, double p2, double p3)
|
||||
{
|
||||
if (convertType.HasFlag(PatternType.ForceNotStack))
|
||||
return generateRandomPattern(1 / 2f + p2 / 2, p2, (p2 + p3) / 2, p3);
|
||||
|
||||
var pattern = new Pattern();
|
||||
|
||||
bool addToCentre;
|
||||
int noteCount = getRandomNoteCountMirrored(centreProbability, p2, p3, out addToCentre);
|
||||
|
||||
int columnLimit = (TotalColumns % 2 == 0 ? TotalColumns : TotalColumns - 1) / 2;
|
||||
int nextColumn = Random.Next(RandomStart, columnLimit);
|
||||
int nextColumn = GetRandomColumn(upperBound: columnLimit);
|
||||
for (int i = 0; i < noteCount; i++)
|
||||
{
|
||||
while (pattern.ColumnHasObject(nextColumn))
|
||||
nextColumn = Random.Next(RandomStart, columnLimit);
|
||||
nextColumn = FindAvailableColumn(nextColumn, upperBound: columnLimit, patterns: pattern);
|
||||
|
||||
// Add normal note
|
||||
addToPattern(pattern, nextColumn);
|
||||
@ -368,9 +372,6 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
||||
{
|
||||
addToCentre = false;
|
||||
|
||||
if (convertType.HasFlag(PatternType.ForceNotStack))
|
||||
return getRandomNoteCount(1 / 2f + p2 / 2, p2, (p2 + p3) / 2, p3);
|
||||
|
||||
switch (TotalColumns)
|
||||
{
|
||||
case 2:
|
||||
|
@ -3,6 +3,7 @@
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using JetBrains.Annotations;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Mania.MathUtils;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
@ -90,6 +91,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
||||
}
|
||||
|
||||
private double? conversionDifficulty;
|
||||
|
||||
/// <summary>
|
||||
/// A difficulty factor used for various conversion methods from osu!stable.
|
||||
/// </summary>
|
||||
@ -116,5 +118,82 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
||||
return conversionDifficulty.Value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds a new column in which a <see cref="HitObject"/> can be placed.
|
||||
/// This uses <see cref="GetRandomColumn"/> to pick the next candidate column.
|
||||
/// </summary>
|
||||
/// <param name="initialColumn">The initial column to test. This may be returned if it is already a valid column.</param>
|
||||
/// <param name="patterns">A list of patterns for which the validity of a column should be checked against.
|
||||
/// A column is not a valid candidate if a <see cref="HitObject"/> occupies the same column in any of the patterns.</param>
|
||||
/// <returns>A column for which there are no <see cref="HitObject"/>s in any of <paramref name="patterns"/> occupying the same column.</returns>
|
||||
/// <exception cref="NotEnoughColumnsException">If there are no valid candidate columns.</exception>
|
||||
protected int FindAvailableColumn(int initialColumn, params Pattern[] patterns)
|
||||
=> FindAvailableColumn(initialColumn, null, patterns: patterns);
|
||||
|
||||
/// <summary>
|
||||
/// Finds a new column in which a <see cref="HitObject"/> can be placed.
|
||||
/// </summary>
|
||||
/// <param name="initialColumn">The initial column to test. This may be returned if it is already a valid column.</param>
|
||||
/// <param name="nextColumn">A function to retrieve the next column. If null, a randomisation scheme will be used.</param>
|
||||
/// <param name="validation">A function to perform additional validation checks to determine if a column is a valid candidate for a <see cref="HitObject"/>.</param>
|
||||
/// <param name="lowerBound">The minimum column index. If null, <see cref="RandomStart"/> is used.</param>
|
||||
/// <param name="upperBound">The maximum column index. If null, <see cref="PatternGenerator.TotalColumns"/> is used.</param>
|
||||
/// <param name="patterns">A list of patterns for which the validity of a column should be checked against.
|
||||
/// A column is not a valid candidate if a <see cref="HitObject"/> occupies the same column in any of the patterns.</param>
|
||||
/// <returns>A column which has passed the <paramref name="validation"/> check and for which there are no
|
||||
/// <see cref="HitObject"/>s in any of <paramref name="patterns"/> occupying the same column.</returns>
|
||||
/// <exception cref="NotEnoughColumnsException">If there are no valid candidate columns.</exception>
|
||||
protected int FindAvailableColumn(int initialColumn, int? lowerBound = null, int? upperBound = null, Func<int, int> nextColumn = null, [InstantHandle] Func<int, bool> validation = null,
|
||||
params Pattern[] patterns)
|
||||
{
|
||||
lowerBound = lowerBound ?? RandomStart;
|
||||
upperBound = upperBound ?? TotalColumns;
|
||||
nextColumn = nextColumn ?? (_ => GetRandomColumn(lowerBound, upperBound));
|
||||
|
||||
// Check for the initial column
|
||||
if (isValid(initialColumn))
|
||||
return initialColumn;
|
||||
|
||||
// Ensure that we have at least one free column, so that an endless loop is avoided
|
||||
bool hasValidColumns = false;
|
||||
for (int i = lowerBound.Value; i < upperBound.Value; i++)
|
||||
{
|
||||
hasValidColumns = isValid(i);
|
||||
if (hasValidColumns)
|
||||
break;
|
||||
}
|
||||
|
||||
if (!hasValidColumns)
|
||||
throw new NotEnoughColumnsException();
|
||||
|
||||
// Iterate until a valid column is found. This is a random iteration in the default case.
|
||||
do
|
||||
{
|
||||
initialColumn = nextColumn(initialColumn);
|
||||
} while (!isValid(initialColumn));
|
||||
|
||||
return initialColumn;
|
||||
|
||||
bool isValid(int column) => validation?.Invoke(column) != false && !patterns.Any(p => p.ColumnHasObject(column));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a random column index in the range [<paramref name="lowerBound"/>, <paramref name="upperBound"/>).
|
||||
/// </summary>
|
||||
/// <param name="lowerBound">The minimum column index. If null, <see cref="RandomStart"/> is used.</param>
|
||||
/// <param name="upperBound">The maximum column index. If null, <see cref="PatternGenerator.TotalColumns"/> is used.</param>
|
||||
protected int GetRandomColumn(int? lowerBound = null, int? upperBound = null) => Random.Next(lowerBound ?? RandomStart, upperBound ?? TotalColumns);
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when mania conversion is stuck in an infinite loop unable to find columns to place new hitobjects in.
|
||||
/// </summary>
|
||||
public class NotEnoughColumnsException : Exception
|
||||
{
|
||||
public NotEnoughColumnsException()
|
||||
: base("There were not enough columns to complete conversion.")
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -111,6 +111,18 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
||||
bodyPiece.Height = DrawHeight - Head.Height / 2 + Tail.Height / 2;
|
||||
}
|
||||
|
||||
protected void BeginHold()
|
||||
{
|
||||
holdStartTime = Time.Current;
|
||||
bodyPiece.Hitting = true;
|
||||
}
|
||||
|
||||
protected void EndHold()
|
||||
{
|
||||
holdStartTime = null;
|
||||
bodyPiece.Hitting = false;
|
||||
}
|
||||
|
||||
public bool OnPressed(ManiaAction action)
|
||||
{
|
||||
// Make sure the action happened within the body of the hold note
|
||||
@ -123,8 +135,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
||||
// The user has pressed during the body of the hold note, after the head note and its hit windows have passed
|
||||
// and within the limited range of the above if-statement. This state will be managed by the head note if the
|
||||
// user has pressed during the hit windows of the head note.
|
||||
holdStartTime = Time.Current;
|
||||
|
||||
BeginHold();
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -137,7 +148,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
||||
if (action != Action.Value)
|
||||
return false;
|
||||
|
||||
holdStartTime = null;
|
||||
EndHold();
|
||||
|
||||
// If the key has been released too early, the user should not receive full score for the release
|
||||
if (!Tail.IsHit)
|
||||
@ -170,7 +181,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
||||
|
||||
// The head note also handles early hits before the body, but we want accurate early hits to count as the body being held
|
||||
// The body doesn't handle these early early hits, so we have to explicitly set the holding state here
|
||||
holdNote.holdStartTime = Time.Current;
|
||||
holdNote.BeginHold();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -32,6 +32,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces
|
||||
background = new Box { RelativeSizeAxes = Axes.Both },
|
||||
foreground = new BufferedContainer
|
||||
{
|
||||
Blending = BlendingMode.Additive,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
CacheDrawnFrameBuffer = true,
|
||||
Children = new Drawable[]
|
||||
@ -73,6 +74,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces
|
||||
}
|
||||
|
||||
private Color4 accentColour;
|
||||
|
||||
public Color4 AccentColour
|
||||
{
|
||||
get { return accentColour; }
|
||||
@ -86,6 +88,16 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces
|
||||
}
|
||||
}
|
||||
|
||||
public bool Hitting
|
||||
{
|
||||
get { return hitting; }
|
||||
set
|
||||
{
|
||||
hitting = value;
|
||||
updateAccentColour();
|
||||
}
|
||||
}
|
||||
|
||||
private Cached subtractionCache = new Cached();
|
||||
|
||||
public override bool Invalidate(Invalidation invalidation = Invalidation.All, Drawable source = null, bool shallPropagate = true)
|
||||
@ -118,13 +130,26 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces
|
||||
}
|
||||
}
|
||||
|
||||
private bool hitting;
|
||||
|
||||
private void updateAccentColour()
|
||||
{
|
||||
if (!IsLoaded)
|
||||
return;
|
||||
|
||||
foreground.Colour = AccentColour.Opacity(0.9f);
|
||||
background.Colour = AccentColour.Opacity(0.6f);
|
||||
foreground.Colour = AccentColour.Opacity(0.5f);
|
||||
background.Colour = AccentColour.Opacity(0.7f);
|
||||
|
||||
const float animation_length = 50;
|
||||
|
||||
foreground.ClearTransforms(false, nameof(foreground.Colour));
|
||||
if (hitting)
|
||||
{
|
||||
// wait for the next sync point
|
||||
double synchronisedOffset = animation_length * 2 - Time.Current % (animation_length * 2);
|
||||
using (foreground.BeginDelayedSequence(synchronisedOffset))
|
||||
foreground.FadeColour(AccentColour.Lighten(0.2f), animation_length).Then().FadeColour(foreground.Colour, animation_length).Loop();
|
||||
}
|
||||
|
||||
subtractionCache.Invalidate();
|
||||
}
|
||||
|
@ -70,9 +70,6 @@ namespace osu.Game.Rulesets.Mania.Objects
|
||||
|
||||
TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(StartTime);
|
||||
tickSpacing = timingPoint.BeatLength / difficulty.SliderTickRate;
|
||||
|
||||
Head.ApplyDefaults(controlPointInfo, difficulty);
|
||||
Tail.ApplyDefaults(controlPointInfo, difficulty);
|
||||
}
|
||||
|
||||
protected override void CreateNestedHitObjects()
|
||||
@ -80,6 +77,9 @@ namespace osu.Game.Rulesets.Mania.Objects
|
||||
base.CreateNestedHitObjects();
|
||||
|
||||
createTicks();
|
||||
|
||||
AddNested(Head);
|
||||
AddNested(Tail);
|
||||
}
|
||||
|
||||
private void createTicks()
|
||||
|
@ -26,11 +26,11 @@ namespace osu.Game.Rulesets.Mania.UI
|
||||
throw new ArgumentException("Can't have zero or fewer stages.");
|
||||
|
||||
GridContainer playfieldGrid;
|
||||
InternalChild = playfieldGrid = new GridContainer
|
||||
AddInternal(playfieldGrid = new GridContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Content = new[] { new Drawable[stageDefinitions.Count] }
|
||||
};
|
||||
});
|
||||
|
||||
var normalColumnAction = ManiaAction.Key1;
|
||||
var specialColumnAction = ManiaAction.Special1;
|
||||
|
64
osu.Game.Rulesets.Osu.Tests/StackingTest.cs
Normal file
64
osu.Game.Rulesets.Osu.Tests/StackingTest.cs
Normal file
@ -0,0 +1,64 @@
|
||||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using NUnit.Framework;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Tests.Beatmaps;
|
||||
using Decoder = osu.Game.Beatmaps.Formats.Decoder;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Tests
|
||||
{
|
||||
[TestFixture]
|
||||
public class StackingTest
|
||||
{
|
||||
[Test]
|
||||
public void TestStacking()
|
||||
{
|
||||
using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(beatmap_data)))
|
||||
using (var reader = new StreamReader(stream))
|
||||
{
|
||||
var beatmap = Decoder.GetDecoder<Beatmap>(reader).Decode(reader);
|
||||
var converted = new TestWorkingBeatmap(beatmap).GetPlayableBeatmap(new OsuRuleset().RulesetInfo);
|
||||
|
||||
var objects = converted.HitObjects.ToList();
|
||||
|
||||
// The last hitobject triggers the stacking
|
||||
for (int i = 0; i < objects.Count - 1; i++)
|
||||
Assert.AreEqual(0, ((OsuHitObject)objects[i]).StackHeight);
|
||||
}
|
||||
}
|
||||
|
||||
private const string beatmap_data = @"
|
||||
osu file format v14
|
||||
|
||||
[General]
|
||||
StackLeniency: 0.2
|
||||
|
||||
[Difficulty]
|
||||
ApproachRate:9.2
|
||||
SliderMultiplier:1
|
||||
SliderTickRate:0.5
|
||||
|
||||
[TimingPoints]
|
||||
217871,6400,4,2,1,20,1,0
|
||||
217871,-800,4,2,1,20,0,0
|
||||
218071,-787.5,4,2,1,20,0,0
|
||||
218271,-775,4,2,1,20,0,0
|
||||
218471,-762.5,4,2,1,20,0,0
|
||||
218671,-750,4,2,1,20,0,0
|
||||
240271,-10,4,2,0,5,0,0
|
||||
|
||||
[HitObjects]
|
||||
311,185,217871,6,0,L|318:158,1,25
|
||||
311,185,218071,2,0,L|335:170,1,25
|
||||
311,185,218271,2,0,L|338:192,1,25
|
||||
311,185,218471,2,0,L|325:209,1,25
|
||||
311,185,218671,2,0,L|304:212,1,25
|
||||
311,185,240271,5,0,0:0:0:0:
|
||||
";
|
||||
}
|
||||
}
|
@ -85,7 +85,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
}
|
||||
}
|
||||
|
||||
private class TestDrawableHitCircle : DrawableHitCircle
|
||||
protected class TestDrawableHitCircle : DrawableHitCircle
|
||||
{
|
||||
private readonly bool auto;
|
||||
|
||||
@ -94,6 +94,8 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
this.auto = auto;
|
||||
}
|
||||
|
||||
public void TriggerJudgement() => UpdateResult(true);
|
||||
|
||||
protected override void CheckForResult(bool userTriggered, double timeOffset)
|
||||
{
|
||||
if (auto && !userTriggered && timeOffset > 0)
|
||||
|
23
osu.Game.Rulesets.Osu.Tests/TestCaseShaking.cs
Normal file
23
osu.Game.Rulesets.Osu.Tests/TestCaseShaking.cs
Normal file
@ -0,0 +1,23 @@
|
||||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.MathUtils;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Tests
|
||||
{
|
||||
public class TestCaseShaking : TestCaseHitCircle
|
||||
{
|
||||
public override void Add(Drawable drawable)
|
||||
{
|
||||
base.Add(drawable);
|
||||
|
||||
if (drawable is TestDrawableHitCircle hitObject)
|
||||
{
|
||||
Scheduler.AddDelayed(() => hitObject.TriggerJudgement(),
|
||||
hitObject.HitObject.StartTime - (hitObject.HitObject.HitWindows.HalfWindowFor(HitResult.Miss) + RNG.Next(0, 300)) - Time.Current);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -12,7 +12,7 @@ using osu.Game.Rulesets.Osu.UI;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Beatmaps
|
||||
{
|
||||
internal class OsuBeatmapConverter : BeatmapConverter<OsuHitObject>
|
||||
public class OsuBeatmapConverter : BeatmapConverter<OsuHitObject>
|
||||
{
|
||||
public OsuBeatmapConverter(IBeatmap beatmap)
|
||||
: base(beatmap)
|
||||
@ -42,6 +42,7 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
|
||||
RepeatCount = curveData.RepeatCount,
|
||||
Position = positionData?.Position ?? Vector2.Zero,
|
||||
NewCombo = comboData?.NewCombo ?? false,
|
||||
ComboOffset = comboData?.ComboOffset ?? 0,
|
||||
LegacyLastTickOffset = legacyOffset?.LegacyLastTickOffset
|
||||
};
|
||||
}
|
||||
@ -52,7 +53,9 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
|
||||
StartTime = original.StartTime,
|
||||
Samples = original.Samples,
|
||||
EndTime = endTimeData.EndTime,
|
||||
Position = positionData?.Position ?? OsuPlayfield.BASE_SIZE / 2
|
||||
Position = positionData?.Position ?? OsuPlayfield.BASE_SIZE / 2,
|
||||
NewCombo = comboData?.NewCombo ?? false,
|
||||
ComboOffset = comboData?.ComboOffset ?? 0,
|
||||
};
|
||||
}
|
||||
else
|
||||
@ -62,7 +65,8 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
|
||||
StartTime = original.StartTime,
|
||||
Samples = original.Samples,
|
||||
Position = positionData?.Position ?? Vector2.Zero,
|
||||
NewCombo = comboData?.NewCombo ?? false
|
||||
NewCombo = comboData?.NewCombo ?? false,
|
||||
ComboOffset = comboData?.ComboOffset ?? 0,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -8,16 +8,16 @@ using osu.Game.Rulesets.Osu.Objects;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Beatmaps
|
||||
{
|
||||
internal class OsuBeatmapProcessor : BeatmapProcessor
|
||||
public class OsuBeatmapProcessor : BeatmapProcessor
|
||||
{
|
||||
public OsuBeatmapProcessor(IBeatmap beatmap)
|
||||
: base(beatmap)
|
||||
{
|
||||
}
|
||||
|
||||
public override void PreProcess()
|
||||
public override void PostProcess()
|
||||
{
|
||||
base.PreProcess();
|
||||
base.PostProcess();
|
||||
applyStacking((Beatmap<OsuHitObject>)Beatmap);
|
||||
}
|
||||
|
||||
|
@ -2,14 +2,89 @@
|
||||
// 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.Input.States;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||
using osu.Game.Rulesets.UI;
|
||||
using static osu.Game.Input.Handlers.ReplayInputHandler;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Mods
|
||||
{
|
||||
public class OsuModRelax : ModRelax
|
||||
public class OsuModRelax : ModRelax, IApplicableFailOverride, IUpdatableByPlayfield, IApplicableToRulesetContainer<OsuHitObject>
|
||||
{
|
||||
public override string Description => @"You don't need to click. Give your clicking/tapping fingers a break from the heat of things.";
|
||||
public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(OsuModAutopilot)).ToArray();
|
||||
|
||||
public bool AllowFail => false;
|
||||
|
||||
public void Update(Playfield playfield)
|
||||
{
|
||||
bool requiresHold = false;
|
||||
bool requiresHit = false;
|
||||
|
||||
const float relax_leniency = 3;
|
||||
|
||||
foreach (var drawable in playfield.HitObjectContainer.AliveObjects)
|
||||
{
|
||||
if (!(drawable is DrawableOsuHitObject osuHit))
|
||||
continue;
|
||||
|
||||
double time = osuHit.Clock.CurrentTime;
|
||||
double relativetime = time - osuHit.HitObject.StartTime;
|
||||
|
||||
if (time < osuHit.HitObject.StartTime - relax_leniency) continue;
|
||||
|
||||
if (osuHit.HitObject is IHasEndTime hasEnd && time > hasEnd.EndTime || osuHit.IsHit)
|
||||
continue;
|
||||
|
||||
requiresHit |= osuHit is DrawableHitCircle && osuHit.IsHovered && osuHit.HitObject.HitWindows.CanBeHit(relativetime);
|
||||
requiresHold |= osuHit is DrawableSlider slider && (slider.Ball.IsHovered || osuHit.IsHovered) || osuHit is DrawableSpinner;
|
||||
}
|
||||
|
||||
if (requiresHit)
|
||||
{
|
||||
addAction(false);
|
||||
addAction(true);
|
||||
}
|
||||
|
||||
addAction(requiresHold);
|
||||
}
|
||||
|
||||
private bool wasHit;
|
||||
private bool wasLeft;
|
||||
|
||||
private OsuInputManager osuInputManager;
|
||||
|
||||
private void addAction(bool hitting)
|
||||
{
|
||||
if (wasHit == hitting)
|
||||
return;
|
||||
|
||||
wasHit = hitting;
|
||||
|
||||
var state = new ReplayState<OsuAction>
|
||||
{
|
||||
PressedActions = new List<OsuAction>()
|
||||
};
|
||||
|
||||
if (hitting)
|
||||
{
|
||||
state.PressedActions.Add(wasLeft ? OsuAction.LeftButton : OsuAction.RightButton);
|
||||
wasLeft = !wasLeft;
|
||||
}
|
||||
|
||||
osuInputManager.HandleCustomInput(new InputState(), state);
|
||||
}
|
||||
|
||||
public void ApplyToRulesetContainer(RulesetContainer<OsuHitObject> rulesetContainer)
|
||||
{
|
||||
// grab the input manager for future use.
|
||||
osuInputManager = (OsuInputManager)rulesetContainer.KeyBindingInputManager;
|
||||
osuInputManager.AllowUserPresses = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -88,7 +88,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
|
||||
var result = HitObject.HitWindows.ResultFor(timeOffset);
|
||||
if (result == HitResult.None)
|
||||
{
|
||||
Shake(Math.Abs(timeOffset) - HitObject.HitWindows.HalfWindowFor(HitResult.Miss));
|
||||
return;
|
||||
}
|
||||
|
||||
ApplyResult(r => r.Type = result);
|
||||
}
|
||||
|
@ -10,19 +10,29 @@ using osu.Game.Rulesets.Osu.Judgements;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Skinning;
|
||||
using OpenTK.Graphics;
|
||||
using osu.Game.Graphics.Containers;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
{
|
||||
public class DrawableOsuHitObject : DrawableHitObject<OsuHitObject>
|
||||
{
|
||||
public override bool IsPresent => base.IsPresent || State.Value == ArmedState.Idle && Time.Current >= HitObject.StartTime - HitObject.TimePreempt;
|
||||
public override bool IsPresent => base.IsPresent || State.Value == ArmedState.Idle && Clock?.CurrentTime >= HitObject.StartTime - HitObject.TimePreempt;
|
||||
|
||||
private readonly ShakeContainer shakeContainer;
|
||||
|
||||
protected DrawableOsuHitObject(OsuHitObject hitObject)
|
||||
: base(hitObject)
|
||||
{
|
||||
base.AddInternal(shakeContainer = new ShakeContainer { RelativeSizeAxes = Axes.Both });
|
||||
Alpha = 0;
|
||||
}
|
||||
|
||||
// Forward all internal management to shakeContainer.
|
||||
// This is a bit ugly but we don't have the concept of InternalContent so it'll have to do for now. (https://github.com/ppy/osu-framework/issues/1690)
|
||||
protected override void AddInternal(Drawable drawable) => shakeContainer.Add(drawable);
|
||||
protected override void ClearInternal(bool disposeChildren = true) => shakeContainer.Clear(disposeChildren);
|
||||
protected override bool RemoveInternal(Drawable drawable) => shakeContainer.Remove(drawable);
|
||||
|
||||
protected sealed override void UpdateState(ArmedState state)
|
||||
{
|
||||
double transformTime = HitObject.StartTime - HitObject.TimePreempt;
|
||||
@ -68,6 +78,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
private OsuInputManager osuActionInputManager;
|
||||
internal OsuInputManager OsuActionInputManager => osuActionInputManager ?? (osuActionInputManager = GetContainingInputManager() as OsuInputManager);
|
||||
|
||||
protected virtual void Shake(double maximumLength) => shakeContainer.Shake(maximumLength);
|
||||
|
||||
protected override JudgementResult CreateResult(Judgement judgement) => new OsuJudgementResult(judgement);
|
||||
}
|
||||
}
|
||||
|
@ -44,14 +44,17 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
},
|
||||
ticks = new Container<DrawableSliderTick> { RelativeSizeAxes = Axes.Both },
|
||||
repeatPoints = new Container<DrawableRepeatPoint> { RelativeSizeAxes = Axes.Both },
|
||||
Ball = new SliderBall(s)
|
||||
Ball = new SliderBall(s, this)
|
||||
{
|
||||
BypassAutoSizeAxes = Axes.Both,
|
||||
Scale = new Vector2(s.Scale),
|
||||
AlwaysPresent = true,
|
||||
Alpha = 0
|
||||
},
|
||||
HeadCircle = new DrawableSliderHead(s, s.HeadCircle),
|
||||
HeadCircle = new DrawableSliderHead(s, s.HeadCircle)
|
||||
{
|
||||
OnShake = Shake
|
||||
},
|
||||
TailCircle = new DrawableSliderTail(s, s.TailCircle)
|
||||
};
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using System;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
using OpenTK;
|
||||
|
||||
@ -28,5 +29,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
if (!IsHit)
|
||||
Position = slider.CurvePositionAt(completionProgress);
|
||||
}
|
||||
|
||||
public Action<double> OnShake;
|
||||
|
||||
protected override void Shake(double maximumLength) => OnShake?.Invoke(maximumLength);
|
||||
}
|
||||
}
|
||||
|
@ -8,7 +8,6 @@ using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Input.EventArgs;
|
||||
using osu.Framework.Input.States;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
using OpenTK;
|
||||
using OpenTK.Graphics;
|
||||
using osu.Game.Skinning;
|
||||
|
||||
@ -37,9 +36,11 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
|
||||
private readonly Slider slider;
|
||||
public readonly Drawable FollowCircle;
|
||||
private Drawable drawableBall;
|
||||
private readonly DrawableSlider drawableSlider;
|
||||
|
||||
public SliderBall(Slider slider)
|
||||
public SliderBall(Slider slider, DrawableSlider drawableSlider = null)
|
||||
{
|
||||
this.drawableSlider = drawableSlider;
|
||||
this.slider = slider;
|
||||
Masking = true;
|
||||
AutoSizeAxes = Axes.Both;
|
||||
@ -121,9 +122,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
|
||||
return base.OnMouseMove(state);
|
||||
}
|
||||
|
||||
// If the current time is between the start and end of the slider, we should track mouse input regardless of the cursor position.
|
||||
public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => canCurrentlyTrack || base.ReceiveMouseInputAt(screenSpacePos);
|
||||
|
||||
public override void ClearTransformsAfter(double time, bool propagateChildren = false, string targetMember = null)
|
||||
{
|
||||
// Consider the case of rewinding - children's transforms are handled internally, so propagating down
|
||||
@ -158,8 +156,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
|
||||
// Make sure to use the base version of ReceiveMouseInputAt so that we correctly check the position.
|
||||
Tracking = canCurrentlyTrack
|
||||
&& lastState != null
|
||||
&& base.ReceiveMouseInputAt(lastState.Mouse.NativeState.Position)
|
||||
&& ((Parent as DrawableSlider)?.OsuActionInputManager?.PressedActions.Any(x => x == OsuAction.LeftButton || x == OsuAction.RightButton) ?? false);
|
||||
&& ReceiveMouseInputAt(lastState.Mouse.NativeState.Position)
|
||||
&& (drawableSlider?.OsuActionInputManager?.PressedActions.Any(x => x == OsuAction.LeftButton || x == OsuAction.RightButton) ?? false);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -7,14 +7,15 @@ using osu.Framework.Allocation;
|
||||
using osu.Framework.Configuration;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.OpenGL.Textures;
|
||||
using osu.Framework.Graphics.Lines;
|
||||
using osu.Framework.Graphics.Textures;
|
||||
using OpenTK;
|
||||
using OpenTK.Graphics.ES30;
|
||||
using OpenTK.Graphics;
|
||||
using osu.Framework.Graphics.Primitives;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
using OpenTK;
|
||||
using SixLabors.ImageSharp;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
|
||||
{
|
||||
@ -43,6 +44,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
|
||||
public double? SnakedEnd { get; private set; }
|
||||
|
||||
private Color4 accentColour = Color4.White;
|
||||
|
||||
/// <summary>
|
||||
/// Used to colour the path.
|
||||
/// </summary>
|
||||
@ -61,6 +63,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
|
||||
}
|
||||
|
||||
private Color4 borderColour = Color4.White;
|
||||
|
||||
/// <summary>
|
||||
/// Used to colour the path border.
|
||||
/// </summary>
|
||||
@ -85,6 +88,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
|
||||
private Vector2 topLeftOffset;
|
||||
|
||||
private readonly Slider slider;
|
||||
|
||||
public SliderBody(Slider s)
|
||||
{
|
||||
slider = s;
|
||||
@ -139,8 +143,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
|
||||
var texture = new Texture(textureWidth, 1);
|
||||
|
||||
//initialise background
|
||||
var raw = new RawTexture(textureWidth, 1);
|
||||
var bytes = raw.Data;
|
||||
var raw = new Image<Rgba32>(textureWidth, 1);
|
||||
|
||||
const float aa_portion = 0.02f;
|
||||
const float border_portion = 0.128f;
|
||||
@ -155,19 +158,13 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
|
||||
|
||||
if (progress <= border_portion)
|
||||
{
|
||||
bytes[i * 4] = (byte)(BorderColour.R * 255);
|
||||
bytes[i * 4 + 1] = (byte)(BorderColour.G * 255);
|
||||
bytes[i * 4 + 2] = (byte)(BorderColour.B * 255);
|
||||
bytes[i * 4 + 3] = (byte)(Math.Min(progress / aa_portion, 1) * (BorderColour.A * 255));
|
||||
raw[i, 0] = new Rgba32(BorderColour.R, BorderColour.G, BorderColour.B, Math.Min(progress / aa_portion, 1) * BorderColour.A);
|
||||
}
|
||||
else
|
||||
{
|
||||
progress -= border_portion;
|
||||
|
||||
bytes[i * 4] = (byte)(AccentColour.R * 255);
|
||||
bytes[i * 4 + 1] = (byte)(AccentColour.G * 255);
|
||||
bytes[i * 4 + 2] = (byte)(AccentColour.B * 255);
|
||||
bytes[i * 4 + 3] = (byte)((opacity_at_edge - (opacity_at_edge - opacity_at_centre) * progress / gradient_portion) * (AccentColour.A * 255));
|
||||
raw[i, 0] = new Rgba32(AccentColour.R, AccentColour.G, AccentColour.B,
|
||||
(opacity_at_edge - (opacity_at_edge - opacity_at_centre) * progress / gradient_portion) * AccentColour.A);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -54,6 +54,8 @@ namespace osu.Game.Rulesets.Osu.Objects
|
||||
|
||||
public virtual bool NewCombo { get; set; }
|
||||
|
||||
public int ComboOffset { get; set; }
|
||||
|
||||
public virtual int IndexInCurrentCombo { get; set; }
|
||||
|
||||
public virtual int ComboIndex { get; set; }
|
||||
|
@ -20,8 +20,6 @@ namespace osu.Game.Rulesets.Osu.Objects
|
||||
/// </summary>
|
||||
public int SpinsRequired { get; protected set; } = 1;
|
||||
|
||||
public override bool NewCombo => true;
|
||||
|
||||
protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty)
|
||||
{
|
||||
base.ApplyDefaultsToSelf(controlPointInfo, difficulty);
|
||||
|
@ -4,6 +4,8 @@
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using osu.Framework.Input.Bindings;
|
||||
using osu.Framework.Input.EventArgs;
|
||||
using osu.Framework.Input.States;
|
||||
using osu.Game.Rulesets.UI;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu
|
||||
@ -12,8 +14,35 @@ namespace osu.Game.Rulesets.Osu
|
||||
{
|
||||
public IEnumerable<OsuAction> PressedActions => KeyBindingContainer.PressedActions;
|
||||
|
||||
public OsuInputManager(RulesetInfo ruleset) : base(ruleset, 0, SimultaneousBindingMode.Unique)
|
||||
public bool AllowUserPresses
|
||||
{
|
||||
set => ((OsuKeyBindingContainer)KeyBindingContainer).AllowUserPresses = value;
|
||||
}
|
||||
|
||||
protected override RulesetKeyBindingContainer CreateKeyBindingContainer(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique)
|
||||
=> new OsuKeyBindingContainer(ruleset, variant, unique);
|
||||
|
||||
public OsuInputManager(RulesetInfo ruleset)
|
||||
: base(ruleset, 0, SimultaneousBindingMode.Unique)
|
||||
{
|
||||
}
|
||||
|
||||
private class OsuKeyBindingContainer : RulesetKeyBindingContainer
|
||||
{
|
||||
public bool AllowUserPresses = true;
|
||||
|
||||
public OsuKeyBindingContainer(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique)
|
||||
: base(ruleset, variant, unique)
|
||||
{
|
||||
}
|
||||
|
||||
protected override bool OnKeyDown(InputState state, KeyDownEventArgs args) => AllowUserPresses && base.OnKeyDown(state, args);
|
||||
protected override bool OnKeyUp(InputState state, KeyUpEventArgs args) => AllowUserPresses && base.OnKeyUp(state, args);
|
||||
protected override bool OnJoystickPress(InputState state, JoystickEventArgs args) => AllowUserPresses && base.OnJoystickPress(state, args);
|
||||
protected override bool OnJoystickRelease(InputState state, JoystickEventArgs args) => AllowUserPresses && base.OnJoystickRelease(state, args);
|
||||
protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) => AllowUserPresses && base.OnMouseDown(state, args);
|
||||
protected override bool OnMouseUp(InputState state, MouseUpEventArgs args) => AllowUserPresses && base.OnMouseUp(state, args);
|
||||
protected override bool OnScroll(InputState state) => AllowUserPresses && base.OnScroll(state);
|
||||
}
|
||||
}
|
||||
|
||||
@ -21,6 +50,7 @@ namespace osu.Game.Rulesets.Osu
|
||||
{
|
||||
[Description("Left Button")]
|
||||
LeftButton,
|
||||
|
||||
[Description("Right Button")]
|
||||
RightButton
|
||||
}
|
||||
|
@ -197,7 +197,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
|
||||
if (Shared.VertexBuffer == null)
|
||||
Shared.VertexBuffer = new QuadVertexBuffer<TexturedTrailVertex>(max_sprites, BufferUsageHint.DynamicDraw);
|
||||
|
||||
Shader.GetUniform<float>("g_FadeClock").Value = Time;
|
||||
Shader.GetUniform<float>("g_FadeClock").UpdateValue(ref Time);
|
||||
|
||||
int updateStart = -1, updateEnd = 0;
|
||||
for (int i = 0; i < Parts.Length; ++i)
|
||||
@ -216,7 +216,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
|
||||
|
||||
Texture.DrawQuad(
|
||||
new Quad(pos.X - Size.X / 2, pos.Y - Size.Y / 2, Size.X, Size.Y),
|
||||
DrawInfo.Colour,
|
||||
DrawColourInfo.Colour,
|
||||
null,
|
||||
v => Shared.VertexBuffer.Vertices[end++] = new TexturedTrailVertex
|
||||
{
|
||||
|
@ -61,7 +61,7 @@ namespace osu.Game.Rulesets.Osu.UI
|
||||
|
||||
public override void PostProcess()
|
||||
{
|
||||
connectionLayer.HitObjects = HitObjects.Objects.Select(d => d.HitObject).OfType<OsuHitObject>();
|
||||
connectionLayer.HitObjects = HitObjectContainer.Objects.Select(d => d.HitObject).OfType<OsuHitObject>();
|
||||
}
|
||||
|
||||
private void onNewResult(DrawableHitObject judgedObject, JudgementResult result)
|
||||
|
@ -62,7 +62,7 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
|
||||
converted.HitObjects = converted.HitObjects.GroupBy(t => t.StartTime).Select(x =>
|
||||
{
|
||||
TaikoHitObject first = x.First();
|
||||
if (x.Skip(1).Any())
|
||||
if (x.Skip(1).Any() && !(first is Swell))
|
||||
first.IsStrong = true;
|
||||
return first;
|
||||
}).ToList();
|
||||
@ -168,7 +168,6 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
|
||||
{
|
||||
StartTime = obj.StartTime,
|
||||
Samples = obj.Samples,
|
||||
IsStrong = strong,
|
||||
Duration = endTimeData.Duration,
|
||||
RequiredHits = (int)Math.Max(1, endTimeData.Duration / 1000 * hitMultiplier)
|
||||
};
|
||||
|
@ -1,6 +1,7 @@
|
||||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using System;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
|
||||
namespace osu.Game.Rulesets.Taiko.Objects
|
||||
@ -16,6 +17,8 @@ namespace osu.Game.Rulesets.Taiko.Objects
|
||||
/// </summary>
|
||||
public int RequiredHits = 10;
|
||||
|
||||
public override bool IsStrong { set => throw new NotSupportedException($"{nameof(Swell)} cannot be a strong hitobject."); }
|
||||
|
||||
protected override void CreateNestedHitObjects()
|
||||
{
|
||||
base.CreateNestedHitObjects();
|
||||
|
@ -29,7 +29,7 @@ namespace osu.Game.Rulesets.Taiko.Objects
|
||||
/// Whether this HitObject is a "strong" type.
|
||||
/// Strong hit objects give more points for hitting the hit object with both keys.
|
||||
/// </summary>
|
||||
public bool IsStrong;
|
||||
public virtual bool IsStrong { get; set; }
|
||||
|
||||
protected override void CreateNestedHitObjects()
|
||||
{
|
||||
|
@ -11,7 +11,9 @@ using osu.Game.Audio;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
using osu.Game.Beatmaps.Formats;
|
||||
using osu.Game.Beatmaps.Timing;
|
||||
using osu.Game.Rulesets.Catch.Beatmaps;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Osu.Beatmaps;
|
||||
using osu.Game.Skinning;
|
||||
|
||||
namespace osu.Game.Tests.Beatmaps.Formats
|
||||
@ -186,6 +188,50 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestDecodeBeatmapComboOffsetsOsu()
|
||||
{
|
||||
var decoder = new LegacyBeatmapDecoder();
|
||||
using (var resStream = Resource.OpenResource("hitobject-combo-offset.osu"))
|
||||
using (var stream = new StreamReader(resStream))
|
||||
{
|
||||
var beatmap = decoder.Decode(stream);
|
||||
|
||||
var converted = new OsuBeatmapConverter(beatmap).Convert();
|
||||
new OsuBeatmapProcessor(converted).PreProcess();
|
||||
new OsuBeatmapProcessor(converted).PostProcess();
|
||||
|
||||
Assert.AreEqual(4, ((IHasComboInformation)converted.HitObjects.ElementAt(0)).ComboIndex);
|
||||
Assert.AreEqual(5, ((IHasComboInformation)converted.HitObjects.ElementAt(2)).ComboIndex);
|
||||
Assert.AreEqual(5, ((IHasComboInformation)converted.HitObjects.ElementAt(4)).ComboIndex);
|
||||
Assert.AreEqual(6, ((IHasComboInformation)converted.HitObjects.ElementAt(6)).ComboIndex);
|
||||
Assert.AreEqual(11, ((IHasComboInformation)converted.HitObjects.ElementAt(8)).ComboIndex);
|
||||
Assert.AreEqual(14, ((IHasComboInformation)converted.HitObjects.ElementAt(11)).ComboIndex);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestDecodeBeatmapComboOffsetsCatch()
|
||||
{
|
||||
var decoder = new LegacyBeatmapDecoder();
|
||||
using (var resStream = Resource.OpenResource("hitobject-combo-offset.osu"))
|
||||
using (var stream = new StreamReader(resStream))
|
||||
{
|
||||
var beatmap = decoder.Decode(stream);
|
||||
|
||||
var converted = new CatchBeatmapConverter(beatmap).Convert();
|
||||
new CatchBeatmapProcessor(converted).PreProcess();
|
||||
new CatchBeatmapProcessor(converted).PostProcess();
|
||||
|
||||
Assert.AreEqual(4, ((IHasComboInformation)converted.HitObjects.ElementAt(0)).ComboIndex);
|
||||
Assert.AreEqual(5, ((IHasComboInformation)converted.HitObjects.ElementAt(2)).ComboIndex);
|
||||
Assert.AreEqual(5, ((IHasComboInformation)converted.HitObjects.ElementAt(4)).ComboIndex);
|
||||
Assert.AreEqual(6, ((IHasComboInformation)converted.HitObjects.ElementAt(6)).ComboIndex);
|
||||
Assert.AreEqual(11, ((IHasComboInformation)converted.HitObjects.ElementAt(8)).ComboIndex);
|
||||
Assert.AreEqual(14, ((IHasComboInformation)converted.HitObjects.ElementAt(11)).ComboIndex);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestDecodeBeatmapHitObjects()
|
||||
{
|
||||
|
32
osu.Game.Tests/Resources/hitobject-combo-offset.osu
Normal file
32
osu.Game.Tests/Resources/hitobject-combo-offset.osu
Normal file
@ -0,0 +1,32 @@
|
||||
osu file format v14
|
||||
|
||||
[HitObjects]
|
||||
// Circle with combo offset (3)
|
||||
255,193,1000,49,0,0:0:0:0:
|
||||
// Combo index = 4
|
||||
|
||||
// Slider with new combo followed by circle with no new combo
|
||||
256,192,2000,12,0,2000,0:0:0:0:
|
||||
255,193,3000,1,0,0:0:0:0:
|
||||
// Combo index = 5
|
||||
|
||||
// Slider without new combo followed by circle with no new combo
|
||||
256,192,4000,8,0,5000,0:0:0:0:
|
||||
255,193,6000,1,0,0:0:0:0:
|
||||
// Combo index = 5
|
||||
|
||||
// Slider without new combo followed by circle with new combo
|
||||
256,192,7000,8,0,8000,0:0:0:0:
|
||||
255,193,9000,5,0,0:0:0:0:
|
||||
// Combo index = 6
|
||||
|
||||
// Slider with new combo and offset (1) followed by circle with new combo and offset (3)
|
||||
256,192,10000,28,0,11000,0:0:0:0:
|
||||
255,193,12000,53,0,0:0:0:0:
|
||||
// Combo index = 11
|
||||
|
||||
// Slider with new combo and offset (2) followed by slider with no new combo followed by circle with no new combo
|
||||
256,192,13000,44,0,14000,0:0:0:0:
|
||||
256,192,15000,8,0,16000,0:0:0:0:
|
||||
255,193,17000,1,0,0:0:0:0:
|
||||
// Combo index = 14
|
@ -3,6 +3,7 @@
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using JetBrains.Annotations;
|
||||
using NUnit.Framework;
|
||||
using OpenTK;
|
||||
using osu.Framework.Allocation;
|
||||
@ -116,7 +117,7 @@ namespace osu.Game.Tests.Visual
|
||||
|
||||
private void testNullBeatmap()
|
||||
{
|
||||
selectNullBeatmap();
|
||||
selectBeatmap(null);
|
||||
AddAssert("check empty version", () => string.IsNullOrEmpty(infoWedge.Info.VersionLabel.Text));
|
||||
AddAssert("check default title", () => infoWedge.Info.TitleLabel.Text == Beatmap.Default.BeatmapInfo.Metadata.Title);
|
||||
AddAssert("check default artist", () => infoWedge.Info.ArtistLabel.Text == Beatmap.Default.BeatmapInfo.Metadata.Artist);
|
||||
@ -124,28 +125,19 @@ namespace osu.Game.Tests.Visual
|
||||
AddAssert("check no info labels", () => !infoWedge.Info.InfoLabelContainer.Children.Any());
|
||||
}
|
||||
|
||||
private void selectBeatmap(IBeatmap b)
|
||||
private void selectBeatmap([CanBeNull] IBeatmap b)
|
||||
{
|
||||
BeatmapInfoWedge.BufferedWedgeInfo infoBefore = null;
|
||||
|
||||
AddStep($"select {b.Metadata.Title} beatmap", () =>
|
||||
AddStep($"select {b?.Metadata.Title ?? "null"} beatmap", () =>
|
||||
{
|
||||
infoBefore = infoWedge.Info;
|
||||
infoWedge.Beatmap = Beatmap.Value = new TestWorkingBeatmap(b);
|
||||
infoWedge.Beatmap = Beatmap.Value = b == null ? Beatmap.Default : new TestWorkingBeatmap(b);
|
||||
});
|
||||
|
||||
AddUntilStep(() => infoWedge.Info != infoBefore, "wait for async load");
|
||||
}
|
||||
|
||||
private void selectNullBeatmap()
|
||||
{
|
||||
AddStep("select null beatmap", () =>
|
||||
{
|
||||
Beatmap.Value = Beatmap.Default;
|
||||
infoWedge.Beatmap = Beatmap;
|
||||
});
|
||||
}
|
||||
|
||||
private IBeatmap createTestBeatmap(RulesetInfo ruleset)
|
||||
{
|
||||
List<HitObject> objects = new List<HitObject>();
|
||||
|
@ -25,7 +25,7 @@ namespace osu.Game.Tests.Visual
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
AddInternal(trackManager);
|
||||
Add(trackManager);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -75,7 +75,7 @@ namespace osu.Game.Tests.Visual
|
||||
TestTrackOwner owner = null;
|
||||
PreviewTrack track = null;
|
||||
|
||||
AddStep("get track", () => AddInternal(owner = new TestTrackOwner(track = getTrack())));
|
||||
AddStep("get track", () => Add(owner = new TestTrackOwner(track = getTrack())));
|
||||
AddStep("start", () => track.Start());
|
||||
AddStep("attempt stop", () => trackManager.StopAnyPlaying(this));
|
||||
AddAssert("not stopped", () => track.IsRunning);
|
||||
@ -89,7 +89,7 @@ namespace osu.Game.Tests.Visual
|
||||
{
|
||||
var track = getTrack();
|
||||
|
||||
AddInternal(track);
|
||||
Add(track);
|
||||
|
||||
return track;
|
||||
}
|
||||
|
@ -11,6 +11,7 @@ using Microsoft.EntityFrameworkCore;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Audio.Track;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||
using osu.Framework.Graphics.Textures;
|
||||
using osu.Framework.Logging;
|
||||
using osu.Framework.Platform;
|
||||
@ -62,6 +63,8 @@ namespace osu.Game.Beatmaps
|
||||
|
||||
public override string[] HandledExtensions => new[] { ".osz" };
|
||||
|
||||
protected override string ImportFromStablePath => "Songs";
|
||||
|
||||
private readonly RulesetStore rulesets;
|
||||
|
||||
private readonly BeatmapStore beatmaps;
|
||||
@ -72,11 +75,6 @@ namespace osu.Game.Beatmaps
|
||||
|
||||
private readonly List<DownloadBeatmapSetRequest> currentDownloads = new List<DownloadBeatmapSetRequest>();
|
||||
|
||||
/// <summary>
|
||||
/// Set a storage with access to an osu-stable install for import purposes.
|
||||
/// </summary>
|
||||
public Func<Storage> GetStableStorage { private get; set; }
|
||||
|
||||
public BeatmapManager(Storage storage, IDatabaseContextFactory contextFactory, RulesetStore rulesets, APIAccess api, AudioManager audioManager, IIpcHost importHost = null)
|
||||
: base(storage, contextFactory, new BeatmapStore(contextFactory), importHost)
|
||||
{
|
||||
@ -103,6 +101,11 @@ namespace osu.Game.Beatmaps
|
||||
b.BeatmapSet = beatmapSet;
|
||||
}
|
||||
|
||||
validateOnlineIds(beatmapSet.Beatmaps);
|
||||
|
||||
foreach (BeatmapInfo b in beatmapSet.Beatmaps)
|
||||
fetchAndPopulateOnlineIDs(b, beatmapSet.Beatmaps);
|
||||
|
||||
// check if a set already exists with the same online id, delete if it does.
|
||||
if (beatmapSet.OnlineBeatmapSetID != null)
|
||||
{
|
||||
@ -114,11 +117,6 @@ namespace osu.Game.Beatmaps
|
||||
Logger.Log($"Found existing beatmap set with same OnlineBeatmapSetID ({beatmapSet.OnlineBeatmapSetID}). It has been purged.", LoggingTarget.Database);
|
||||
}
|
||||
}
|
||||
|
||||
validateOnlineIds(beatmapSet.Beatmaps);
|
||||
|
||||
foreach (BeatmapInfo b in beatmapSet.Beatmaps)
|
||||
fetchAndPopulateOnlineIDs(b, beatmapSet.Beatmaps);
|
||||
}
|
||||
|
||||
private void validateOnlineIds(List<BeatmapInfo> beatmaps)
|
||||
@ -195,7 +193,7 @@ namespace osu.Game.Beatmaps
|
||||
|
||||
downloadNotification.CompletionClickAction = () =>
|
||||
{
|
||||
PresentBeatmap?.Invoke(importedBeatmap);
|
||||
PresentCompletedImport(importedBeatmap.Yield());
|
||||
return true;
|
||||
};
|
||||
downloadNotification.State = ProgressNotificationState.Completed;
|
||||
@ -231,6 +229,12 @@ namespace osu.Game.Beatmaps
|
||||
BeatmapDownloadBegan?.Invoke(request);
|
||||
}
|
||||
|
||||
protected override void PresentCompletedImport(IEnumerable<BeatmapSetInfo> imported)
|
||||
{
|
||||
base.PresentCompletedImport(imported);
|
||||
PresentBeatmap?.Invoke(imported.LastOrDefault());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get an existing download request if it exists.
|
||||
/// </summary>
|
||||
@ -311,27 +315,6 @@ namespace osu.Game.Beatmaps
|
||||
/// <returns>Results from the provided query.</returns>
|
||||
public IQueryable<BeatmapInfo> QueryBeatmaps(Expression<Func<BeatmapInfo, bool>> query) => beatmaps.Beatmaps.AsNoTracking().Where(query);
|
||||
|
||||
/// <summary>
|
||||
/// Denotes whether an osu-stable installation is present to perform automated imports from.
|
||||
/// </summary>
|
||||
public bool StableInstallationAvailable => GetStableStorage?.Invoke() != null;
|
||||
|
||||
/// <summary>
|
||||
/// This is a temporary method and will likely be replaced by a full-fledged (and more correctly placed) migration process in the future.
|
||||
/// </summary>
|
||||
public async Task ImportFromStable()
|
||||
{
|
||||
var stable = GetStableStorage?.Invoke();
|
||||
|
||||
if (stable == null)
|
||||
{
|
||||
Logger.Log("No osu!stable installation available!", LoggingTarget.Information, LogLevel.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
await Task.Factory.StartNew(() => Import(stable.GetDirectories("Songs")), TaskCreationOptions.LongRunning);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a SHA-2 hash from the provided archive based on contained beatmap (.osu) file content.
|
||||
/// </summary>
|
||||
@ -350,7 +333,11 @@ namespace osu.Game.Beatmaps
|
||||
{
|
||||
// let's make sure there are actually .osu files to import.
|
||||
string mapName = reader.Filenames.FirstOrDefault(f => f.EndsWith(".osu"));
|
||||
if (string.IsNullOrEmpty(mapName)) throw new InvalidOperationException("No beatmap files found in this beatmap archive.");
|
||||
if (string.IsNullOrEmpty(mapName))
|
||||
{
|
||||
Logger.Log($"No beatmap files found in the beatmap archive ({reader.Name}).", LoggingTarget.Database);
|
||||
return null;
|
||||
}
|
||||
|
||||
Beatmap beatmap;
|
||||
using (var stream = new StreamReader(reader.GetStream(mapName)))
|
||||
|
@ -10,7 +10,6 @@ using osu.Framework.Graphics.Textures;
|
||||
using osu.Framework.IO.Stores;
|
||||
using osu.Framework.Logging;
|
||||
using osu.Game.Beatmaps.Formats;
|
||||
using osu.Game.Graphics.Textures;
|
||||
using osu.Game.Skinning;
|
||||
using osu.Game.Storyboards;
|
||||
|
||||
@ -45,6 +44,10 @@ namespace osu.Game.Beatmaps
|
||||
|
||||
private string getPathForFile(string filename) => BeatmapSetInfo.Files.First(f => string.Equals(f.Filename, filename, StringComparison.InvariantCultureIgnoreCase)).FileInfo.StoragePath;
|
||||
|
||||
private LargeTextureStore textureStore;
|
||||
|
||||
protected override bool BackgroundStillValid(Texture b) => false; // bypass lazy logic. we want to return a new background each time for refcounting purposes.
|
||||
|
||||
protected override Texture GetBackground()
|
||||
{
|
||||
if (Metadata?.BackgroundFile == null)
|
||||
@ -52,7 +55,7 @@ namespace osu.Game.Beatmaps
|
||||
|
||||
try
|
||||
{
|
||||
return new LargeTextureStore(new RawTextureLoaderStore(store)).Get(getPathForFile(Metadata.BackgroundFile));
|
||||
return (textureStore ?? (textureStore = new LargeTextureStore(new TextureLoaderStore(store)))).Get(getPathForFile(Metadata.BackgroundFile));
|
||||
}
|
||||
catch
|
||||
{
|
||||
@ -73,6 +76,14 @@ namespace osu.Game.Beatmaps
|
||||
}
|
||||
}
|
||||
|
||||
public override void TransferTo(WorkingBeatmap other)
|
||||
{
|
||||
base.TransferTo(other);
|
||||
|
||||
if (other is BeatmapManagerWorkingBeatmap owb && textureStore != null && BeatmapInfo.BackgroundEquals(other.BeatmapInfo))
|
||||
owb.textureStore = textureStore;
|
||||
}
|
||||
|
||||
protected override Waveform GetWaveform()
|
||||
{
|
||||
try
|
||||
|
@ -27,11 +27,10 @@ namespace osu.Game.Beatmaps
|
||||
if (obj.NewCombo)
|
||||
{
|
||||
obj.IndexInCurrentCombo = 0;
|
||||
obj.ComboIndex = (lastObj?.ComboIndex ?? 0) + obj.ComboOffset + 1;
|
||||
|
||||
if (lastObj != null)
|
||||
{
|
||||
lastObj.LastInCombo = true;
|
||||
obj.ComboIndex = lastObj.ComboIndex + 1;
|
||||
}
|
||||
}
|
||||
else if (lastObj != null)
|
||||
{
|
||||
|
@ -55,11 +55,11 @@ namespace osu.Game.Beatmaps.Formats
|
||||
} while (line != null && line.Length == 0);
|
||||
|
||||
if (line == null)
|
||||
throw new IOException(@"Unknown file format");
|
||||
throw new IOException(@"Unknown file format (null)");
|
||||
|
||||
var decoder = typedDecoders.Select(d => line.StartsWith(d.Key) ? d.Value : null).FirstOrDefault();
|
||||
var decoder = typedDecoders.Select(d => line.StartsWith(d.Key, StringComparison.InvariantCulture) ? d.Value : null).FirstOrDefault();
|
||||
if (decoder == null)
|
||||
throw new IOException(@"Unknown file format");
|
||||
throw new IOException($@"Unknown file format ({line})");
|
||||
|
||||
return (Decoder<T>)decoder.Invoke(line);
|
||||
}
|
||||
|
@ -126,16 +126,16 @@ namespace osu.Game.Beatmaps.Formats
|
||||
switch (beatmap.BeatmapInfo.RulesetID)
|
||||
{
|
||||
case 0:
|
||||
parser = new Rulesets.Objects.Legacy.Osu.ConvertHitObjectParser();
|
||||
parser = new Rulesets.Objects.Legacy.Osu.ConvertHitObjectParser(getOffsetTime(), FormatVersion);
|
||||
break;
|
||||
case 1:
|
||||
parser = new Rulesets.Objects.Legacy.Taiko.ConvertHitObjectParser();
|
||||
parser = new Rulesets.Objects.Legacy.Taiko.ConvertHitObjectParser(getOffsetTime(), FormatVersion);
|
||||
break;
|
||||
case 2:
|
||||
parser = new Rulesets.Objects.Legacy.Catch.ConvertHitObjectParser();
|
||||
parser = new Rulesets.Objects.Legacy.Catch.ConvertHitObjectParser(getOffsetTime(), FormatVersion);
|
||||
break;
|
||||
case 3:
|
||||
parser = new Rulesets.Objects.Legacy.Mania.ConvertHitObjectParser();
|
||||
parser = new Rulesets.Objects.Legacy.Mania.ConvertHitObjectParser(getOffsetTime(), FormatVersion);
|
||||
break;
|
||||
}
|
||||
|
||||
@ -405,15 +405,12 @@ namespace osu.Game.Beatmaps.Formats
|
||||
{
|
||||
// If the ruleset wasn't specified, assume the osu!standard ruleset.
|
||||
if (parser == null)
|
||||
parser = new Rulesets.Objects.Legacy.Osu.ConvertHitObjectParser();
|
||||
|
||||
var obj = parser.Parse(line, getOffsetTime());
|
||||
parser = new Rulesets.Objects.Legacy.Osu.ConvertHitObjectParser(getOffsetTime(), FormatVersion);
|
||||
|
||||
var obj = parser.Parse(line);
|
||||
if (obj != null)
|
||||
{
|
||||
beatmap.HitObjects.Add(obj);
|
||||
}
|
||||
}
|
||||
|
||||
private int getOffsetTime(int time) => time + (ApplyOffsets ? offset : 0);
|
||||
|
||||
|
@ -31,7 +31,7 @@ namespace osu.Game.Beatmaps.Formats
|
||||
if (ShouldSkipLine(line))
|
||||
continue;
|
||||
|
||||
if (line.StartsWith(@"[") && line.EndsWith(@"]"))
|
||||
if (line.StartsWith(@"[", StringComparison.Ordinal) && line.EndsWith(@"]", StringComparison.Ordinal))
|
||||
{
|
||||
if (!Enum.TryParse(line.Substring(1, line.Length - 2), out section))
|
||||
{
|
||||
@ -53,7 +53,7 @@ namespace osu.Game.Beatmaps.Formats
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual bool ShouldSkipLine(string line) => string.IsNullOrWhiteSpace(line) || line.StartsWith("//");
|
||||
protected virtual bool ShouldSkipLine(string line) => string.IsNullOrWhiteSpace(line) || line.StartsWith("//", StringComparison.Ordinal);
|
||||
|
||||
protected virtual void ParseLine(T output, Section section, string line)
|
||||
{
|
||||
|
@ -60,7 +60,7 @@ namespace osu.Game.Beatmaps.Formats
|
||||
private void handleEvents(string line)
|
||||
{
|
||||
var depth = 0;
|
||||
while (line.StartsWith(" ") || line.StartsWith("_"))
|
||||
while (line.StartsWith(" ", StringComparison.Ordinal) || line.StartsWith("_", StringComparison.Ordinal))
|
||||
{
|
||||
++depth;
|
||||
line = line.Substring(1);
|
||||
@ -269,9 +269,9 @@ namespace osu.Game.Beatmaps.Formats
|
||||
return Anchor.BottomCentre;
|
||||
case LegacyOrigins.BottomRight:
|
||||
return Anchor.BottomRight;
|
||||
default:
|
||||
return Anchor.TopLeft;
|
||||
}
|
||||
|
||||
throw new InvalidDataException($@"Unknown origin: {value}");
|
||||
}
|
||||
|
||||
private void handleVariables(string line)
|
||||
|
@ -8,10 +8,10 @@ using osu.Game.Rulesets.Mods;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using osu.Game.Storyboards;
|
||||
using osu.Framework.IO.File;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using osu.Game.IO.Serialization;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
@ -38,12 +38,26 @@ namespace osu.Game.Beatmaps
|
||||
|
||||
Mods.ValueChanged += mods => applyRateAdjustments();
|
||||
|
||||
beatmap = new AsyncLazy<IBeatmap>(populateBeatmap);
|
||||
background = new AsyncLazy<Texture>(populateBackground, b => b == null || !b.IsDisposed);
|
||||
track = new AsyncLazy<Track>(populateTrack);
|
||||
waveform = new AsyncLazy<Waveform>(populateWaveform);
|
||||
storyboard = new AsyncLazy<Storyboard>(populateStoryboard);
|
||||
skin = new AsyncLazy<Skin>(populateSkin);
|
||||
beatmap = new RecyclableLazy<IBeatmap>(() =>
|
||||
{
|
||||
var b = GetBeatmap() ?? new Beatmap();
|
||||
// use the database-backed info.
|
||||
b.BeatmapInfo = BeatmapInfo;
|
||||
return b;
|
||||
});
|
||||
|
||||
track = new RecyclableLazy<Track>(() =>
|
||||
{
|
||||
// we want to ensure that we always have a track, even if it's a fake one.
|
||||
var t = GetTrack() ?? new VirtualBeatmapTrack(Beatmap);
|
||||
applyRateAdjustments(t);
|
||||
return t;
|
||||
});
|
||||
|
||||
background = new RecyclableLazy<Texture>(GetBackground, BackgroundStillValid);
|
||||
waveform = new RecyclableLazy<Waveform>(GetWaveform);
|
||||
storyboard = new RecyclableLazy<Storyboard>(GetStoryboard);
|
||||
skin = new RecyclableLazy<Skin>(GetSkin);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -58,28 +72,6 @@ namespace osu.Game.Beatmaps
|
||||
return path;
|
||||
}
|
||||
|
||||
protected abstract IBeatmap GetBeatmap();
|
||||
protected abstract Texture GetBackground();
|
||||
protected abstract Track GetTrack();
|
||||
protected virtual Skin GetSkin() => new DefaultSkin();
|
||||
protected virtual Waveform GetWaveform() => new Waveform();
|
||||
protected virtual Storyboard GetStoryboard() => new Storyboard { BeatmapInfo = BeatmapInfo };
|
||||
|
||||
public bool BeatmapLoaded => beatmap.IsResultAvailable;
|
||||
public IBeatmap Beatmap => beatmap.Value.Result;
|
||||
public async Task<IBeatmap> GetBeatmapAsync() => await beatmap.Value;
|
||||
private readonly AsyncLazy<IBeatmap> beatmap;
|
||||
|
||||
private IBeatmap populateBeatmap()
|
||||
{
|
||||
var b = GetBeatmap() ?? new Beatmap();
|
||||
|
||||
// use the database-backed info.
|
||||
b.BeatmapInfo = BeatmapInfo;
|
||||
|
||||
return b;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a playable <see cref="IBeatmap"/> from <see cref="Beatmap"/> using the applicable converters for a specific <see cref="RulesetInfo"/>.
|
||||
/// <para>
|
||||
@ -136,62 +128,53 @@ namespace osu.Game.Beatmaps
|
||||
|
||||
public override string ToString() => BeatmapInfo.ToString();
|
||||
|
||||
public bool BackgroundLoaded => background.IsResultAvailable;
|
||||
public Texture Background => background.Value.Result;
|
||||
public async Task<Texture> GetBackgroundAsync() => await background.Value;
|
||||
private AsyncLazy<Texture> background;
|
||||
public bool BeatmapLoaded => beatmap.IsResultAvailable;
|
||||
public IBeatmap Beatmap => beatmap.Value;
|
||||
protected abstract IBeatmap GetBeatmap();
|
||||
private readonly RecyclableLazy<IBeatmap> beatmap;
|
||||
|
||||
private Texture populateBackground() => GetBackground();
|
||||
public bool BackgroundLoaded => background.IsResultAvailable;
|
||||
public Texture Background => background.Value;
|
||||
protected virtual bool BackgroundStillValid(Texture b) => b == null || !b.IsDisposed;
|
||||
protected abstract Texture GetBackground();
|
||||
private readonly RecyclableLazy<Texture> background;
|
||||
|
||||
public bool TrackLoaded => track.IsResultAvailable;
|
||||
public Track Track => track.Value.Result;
|
||||
public async Task<Track> GetTrackAsync() => await track.Value;
|
||||
private AsyncLazy<Track> track;
|
||||
|
||||
private Track populateTrack()
|
||||
{
|
||||
// we want to ensure that we always have a track, even if it's a fake one.
|
||||
var t = GetTrack() ?? new VirtualBeatmapTrack(Beatmap);
|
||||
applyRateAdjustments(t);
|
||||
return t;
|
||||
}
|
||||
public Track Track => track.Value;
|
||||
protected abstract Track GetTrack();
|
||||
private RecyclableLazy<Track> track;
|
||||
|
||||
public bool WaveformLoaded => waveform.IsResultAvailable;
|
||||
public Waveform Waveform => waveform.Value.Result;
|
||||
public async Task<Waveform> GetWaveformAsync() => await waveform.Value;
|
||||
private readonly AsyncLazy<Waveform> waveform;
|
||||
|
||||
private Waveform populateWaveform() => GetWaveform();
|
||||
public Waveform Waveform => waveform.Value;
|
||||
protected virtual Waveform GetWaveform() => new Waveform();
|
||||
private readonly RecyclableLazy<Waveform> waveform;
|
||||
|
||||
public bool StoryboardLoaded => storyboard.IsResultAvailable;
|
||||
public Storyboard Storyboard => storyboard.Value.Result;
|
||||
public async Task<Storyboard> GetStoryboardAsync() => await storyboard.Value;
|
||||
private readonly AsyncLazy<Storyboard> storyboard;
|
||||
|
||||
private Storyboard populateStoryboard() => GetStoryboard();
|
||||
public Storyboard Storyboard => storyboard.Value;
|
||||
protected virtual Storyboard GetStoryboard() => new Storyboard { BeatmapInfo = BeatmapInfo };
|
||||
private readonly RecyclableLazy<Storyboard> storyboard;
|
||||
|
||||
public bool SkinLoaded => skin.IsResultAvailable;
|
||||
public Skin Skin => skin.Value.Result;
|
||||
public async Task<Skin> GetSkinAsync() => await skin.Value;
|
||||
private readonly AsyncLazy<Skin> skin;
|
||||
public Skin Skin => skin.Value;
|
||||
protected virtual Skin GetSkin() => new DefaultSkin();
|
||||
private readonly RecyclableLazy<Skin> skin;
|
||||
|
||||
private Skin populateSkin() => GetSkin();
|
||||
|
||||
public void TransferTo(WorkingBeatmap other)
|
||||
/// <summary>
|
||||
/// Transfer pieces of a beatmap to a new one, where possible, to save on loading.
|
||||
/// </summary>
|
||||
/// <param name="other">The new beatmap which is being switched to.</param>
|
||||
public virtual void TransferTo(WorkingBeatmap other)
|
||||
{
|
||||
if (track.IsResultAvailable && Track != null && BeatmapInfo.AudioEquals(other.BeatmapInfo))
|
||||
other.track = track;
|
||||
|
||||
if (background.IsResultAvailable && Background != null && BeatmapInfo.BackgroundEquals(other.BeatmapInfo))
|
||||
other.background = background;
|
||||
}
|
||||
|
||||
public virtual void Dispose()
|
||||
{
|
||||
if (BackgroundLoaded) Background?.Dispose();
|
||||
if (WaveformLoaded) Waveform?.Dispose();
|
||||
if (StoryboardLoaded) Storyboard?.Dispose();
|
||||
if (SkinLoaded) Skin?.Dispose();
|
||||
background.Recycle();
|
||||
waveform.Recycle();
|
||||
storyboard.Recycle();
|
||||
skin.Recycle();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -210,15 +193,15 @@ namespace osu.Game.Beatmaps
|
||||
mod.ApplyToClock(t);
|
||||
}
|
||||
|
||||
public class AsyncLazy<T>
|
||||
public class RecyclableLazy<T>
|
||||
{
|
||||
private Lazy<Task<T>> lazy;
|
||||
private Lazy<T> lazy;
|
||||
private readonly Func<T> valueFactory;
|
||||
private readonly Func<T, bool> stillValidFunction;
|
||||
|
||||
private readonly object initLock = new object();
|
||||
private readonly object fetchLock = new object();
|
||||
|
||||
public AsyncLazy(Func<T> valueFactory, Func<T, bool> stillValidFunction = null)
|
||||
public RecyclableLazy(Func<T> valueFactory, Func<T, bool> stillValidFunction = null)
|
||||
{
|
||||
this.valueFactory = valueFactory;
|
||||
this.stillValidFunction = stillValidFunction;
|
||||
@ -230,45 +213,28 @@ namespace osu.Game.Beatmaps
|
||||
{
|
||||
if (!IsResultAvailable) return;
|
||||
|
||||
(lazy.Value.Result as IDisposable)?.Dispose();
|
||||
(lazy.Value as IDisposable)?.Dispose();
|
||||
recreate();
|
||||
}
|
||||
|
||||
public bool IsResultAvailable
|
||||
{
|
||||
get
|
||||
{
|
||||
recreateIfInvalid();
|
||||
return lazy.Value.IsCompleted;
|
||||
}
|
||||
}
|
||||
public bool IsResultAvailable => stillValid;
|
||||
|
||||
public Task<T> Value
|
||||
public T Value
|
||||
{
|
||||
get
|
||||
{
|
||||
recreateIfInvalid();
|
||||
lock (fetchLock)
|
||||
{
|
||||
if (!stillValid)
|
||||
recreate();
|
||||
return lazy.Value;
|
||||
}
|
||||
}
|
||||
|
||||
private void recreateIfInvalid()
|
||||
{
|
||||
lock (initLock)
|
||||
{
|
||||
if (!lazy.IsValueCreated || !lazy.Value.IsCompleted)
|
||||
// we have not yet been initialised or haven't run the task.
|
||||
return;
|
||||
|
||||
if (stillValidFunction?.Invoke(lazy.Value.Result) ?? true)
|
||||
// we are still in a valid state.
|
||||
return;
|
||||
|
||||
recreate();
|
||||
}
|
||||
}
|
||||
|
||||
private void recreate() => lazy = new Lazy<Task<T>>(() => Task.Run(valueFactory));
|
||||
private bool stillValid => lazy.IsValueCreated && (stillValidFunction?.Invoke(lazy.Value) ?? true);
|
||||
|
||||
private void recreate() => lazy = new Lazy<T>(valueFactory, LazyThreadSafetyMode.ExecutionAndPublication);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using JetBrains.Annotations;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using osu.Framework.IO.File;
|
||||
@ -129,7 +130,6 @@ namespace osu.Game.Database
|
||||
List<TModel> imported = new List<TModel>();
|
||||
|
||||
int current = 0;
|
||||
int errors = 0;
|
||||
foreach (string path in paths)
|
||||
{
|
||||
if (notification.State == ProgressNotificationState.Cancelled)
|
||||
@ -162,13 +162,30 @@ namespace osu.Game.Database
|
||||
{
|
||||
e = e.InnerException ?? e;
|
||||
Logger.Error(e, $@"Could not import ({Path.GetFileName(path)})");
|
||||
errors++;
|
||||
}
|
||||
}
|
||||
|
||||
notification.Text = errors > 0 ? $"Import complete with {errors} errors" : "Import successful!";
|
||||
if (imported.Count == 0)
|
||||
{
|
||||
notification.Text = "Import failed!";
|
||||
notification.State = ProgressNotificationState.Cancelled;
|
||||
}
|
||||
else
|
||||
{
|
||||
notification.CompletionText = $"Imported {current} {typeof(TModel).Name.Replace("Info", "").ToLower()}s!";
|
||||
notification.CompletionClickAction += () =>
|
||||
{
|
||||
if (imported.Count > 0)
|
||||
PresentCompletedImport(imported);
|
||||
return true;
|
||||
};
|
||||
notification.State = ProgressNotificationState.Completed;
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void PresentCompletedImport(IEnumerable<TModel> imported)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Import an item from an <see cref="ArchiveReader"/>.
|
||||
@ -178,7 +195,8 @@ namespace osu.Game.Database
|
||||
{
|
||||
try
|
||||
{
|
||||
return Import(CreateModel(archive), archive);
|
||||
var model = CreateModel(archive);
|
||||
return model == null ? null : Import(model, archive);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@ -198,6 +216,8 @@ namespace osu.Game.Database
|
||||
|
||||
try
|
||||
{
|
||||
Logger.Log($"Importing {item}...", LoggingTarget.Database);
|
||||
|
||||
using (var write = ContextFactory.GetForWrite()) // used to share a context for full import. keep in mind this will block all writes.
|
||||
{
|
||||
try
|
||||
@ -280,7 +300,7 @@ namespace osu.Game.Database
|
||||
var notification = new ProgressNotification
|
||||
{
|
||||
Progress = 0,
|
||||
CompletionText = "Deleted all beatmaps!",
|
||||
CompletionText = $"Deleted all {typeof(TModel).Name.Replace("Info", "").ToLower()}s!",
|
||||
State = ProgressNotificationState.Active,
|
||||
};
|
||||
|
||||
@ -382,12 +402,47 @@ namespace osu.Game.Database
|
||||
return fileInfos;
|
||||
}
|
||||
|
||||
#region osu-stable import
|
||||
|
||||
/// <summary>
|
||||
/// Set a storage with access to an osu-stable install for import purposes.
|
||||
/// </summary>
|
||||
public Func<Storage> GetStableStorage { private get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Denotes whether an osu-stable installation is present to perform automated imports from.
|
||||
/// </summary>
|
||||
public bool StableInstallationAvailable => GetStableStorage?.Invoke() != null;
|
||||
|
||||
/// <summary>
|
||||
/// The relative path from osu-stable's data directory to import items from.
|
||||
/// </summary>
|
||||
protected virtual string ImportFromStablePath => null;
|
||||
|
||||
/// <summary>
|
||||
/// This is a temporary method and will likely be replaced by a full-fledged (and more correctly placed) migration process in the future.
|
||||
/// </summary>
|
||||
public Task ImportFromStableAsync()
|
||||
{
|
||||
var stable = GetStableStorage?.Invoke();
|
||||
|
||||
if (stable == null)
|
||||
{
|
||||
Logger.Log("No osu!stable installation available!", LoggingTarget.Information, LogLevel.Error);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
return Task.Factory.StartNew(() => Import(stable.GetDirectories(ImportFromStablePath).Select(f => stable.GetFullPath(f)).ToArray()), TaskCreationOptions.LongRunning);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Create a barebones model from the provided archive.
|
||||
/// Actual expensive population should be done in <see cref="Populate"/>; this should just prepare for duplicate checking.
|
||||
/// </summary>
|
||||
/// <param name="archive">The archive to create the model for.</param>
|
||||
/// <returns>A model populated with minimal information.</returns>
|
||||
/// <returns>A model populated with minimal information. Returning a null will abort importing silently.</returns>
|
||||
protected abstract TModel CreateModel(ArchiveReader archive);
|
||||
|
||||
/// <summary>
|
||||
@ -412,7 +467,7 @@ namespace osu.Game.Database
|
||||
private ArchiveReader getReaderFrom(string path)
|
||||
{
|
||||
if (ZipUtils.IsZipArchive(path))
|
||||
return new ZipArchiveReader(Files.Storage.GetStream(path), Path.GetFileName(path));
|
||||
return new ZipArchiveReader(File.Open(path, FileMode.Open, FileAccess.Read, FileShare.Read), Path.GetFileName(path));
|
||||
if (Directory.Exists(path))
|
||||
return new LegacyFilesystemReader(path);
|
||||
throw new InvalidFormatException($"{path} is not a valid archive");
|
||||
|
@ -5,7 +5,6 @@ using System;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using Microsoft.EntityFrameworkCore.Storage;
|
||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||
using osu.Framework.Platform;
|
||||
|
||||
namespace osu.Game.Database
|
||||
@ -118,7 +117,9 @@ namespace osu.Game.Database
|
||||
|
||||
private void recycleThreadContexts()
|
||||
{
|
||||
threadContexts?.Values.ForEach(c => c.Dispose());
|
||||
// Contexts for other threads are not disposed as they may be in use elsewhere. Instead, fresh contexts are exposed
|
||||
// for other threads to use, and we rely on the finalizer inside OsuDbContext to handle their previous contexts
|
||||
threadContexts?.Value.Dispose();
|
||||
threadContexts = new ThreadLocal<OsuDbContext>(CreateContext, true);
|
||||
}
|
||||
|
||||
|
@ -75,6 +75,13 @@ namespace osu.Game.Database
|
||||
}
|
||||
}
|
||||
|
||||
~OsuDbContext()
|
||||
{
|
||||
// DbContext does not contain a finalizer (https://github.com/aspnet/EntityFrameworkCore/issues/8872)
|
||||
// This is used to clean up previous contexts when fresh contexts are exposed via DatabaseContextFactory
|
||||
Dispose();
|
||||
}
|
||||
|
||||
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
|
||||
{
|
||||
base.OnConfiguring(optionsBuilder);
|
||||
|
@ -5,8 +5,8 @@ using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Graphics.Textures;
|
||||
using OpenTK.Graphics;
|
||||
using osu.Game.Graphics.Textures;
|
||||
|
||||
namespace osu.Game.Graphics.Backgrounds
|
||||
{
|
||||
|
@ -116,7 +116,7 @@ namespace osu.Game.Graphics.Backgrounds
|
||||
|
||||
float adjustedAlpha = HideAlphaDiscrepancies ?
|
||||
// Cubically scale alpha to make it drop off more sharply.
|
||||
(float)Math.Pow(DrawInfo.Colour.AverageColour.Linear.A, 3) :
|
||||
(float)Math.Pow(DrawColourInfo.Colour.AverageColour.Linear.A, 3) :
|
||||
1;
|
||||
|
||||
float elapsedSeconds = (float)Time.Elapsed / 1000;
|
||||
@ -235,7 +235,7 @@ namespace osu.Game.Graphics.Backgrounds
|
||||
Vector2Extensions.Transform(particle.Position * Size + new Vector2(-offset.X, offset.Y), DrawInfo.Matrix)
|
||||
);
|
||||
|
||||
ColourInfo colourInfo = DrawInfo.Colour;
|
||||
ColourInfo colourInfo = DrawColourInfo.Colour;
|
||||
colourInfo.ApplyChild(particle.Colour);
|
||||
|
||||
Texture.DrawTriangle(
|
||||
|
39
osu.Game/Graphics/Containers/ShakeContainer.cs
Normal file
39
osu.Game/Graphics/Containers/ShakeContainer.cs
Normal file
@ -0,0 +1,39 @@
|
||||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
|
||||
namespace osu.Game.Graphics.Containers
|
||||
{
|
||||
/// <summary>
|
||||
/// A container that adds the ability to shake its contents.
|
||||
/// </summary>
|
||||
public class ShakeContainer : Container
|
||||
{
|
||||
/// <summary>
|
||||
/// Shake the contents of this container.
|
||||
/// </summary>
|
||||
/// <param name="maximumLength">The maximum length the shake should last.</param>
|
||||
public void Shake(double maximumLength)
|
||||
{
|
||||
const float shake_amount = 8;
|
||||
const float shake_duration = 30;
|
||||
|
||||
// if we don't have enough time, don't bother shaking.
|
||||
if (maximumLength < shake_duration * 2)
|
||||
return;
|
||||
|
||||
var sequence = this.MoveToX(shake_amount, shake_duration / 2, Easing.OutSine).Then()
|
||||
.MoveToX(-shake_amount, shake_duration, Easing.InOutSine).Then();
|
||||
|
||||
// if we don't have enough time for the second shake, skip it.
|
||||
if (maximumLength > shake_duration * 4)
|
||||
sequence = sequence
|
||||
.MoveToX(shake_amount, shake_duration, Easing.InOutSine).Then()
|
||||
.MoveToX(-shake_amount, shake_duration, Easing.InOutSine).Then();
|
||||
|
||||
sequence.MoveToX(0, shake_duration / 2, Easing.InSine);
|
||||
}
|
||||
}
|
||||
}
|
@ -4,7 +4,6 @@
|
||||
using System;
|
||||
using Humanizer;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Cursor;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
|
||||
@ -16,7 +15,6 @@ namespace osu.Game.Graphics
|
||||
|
||||
public DrawableDate(DateTimeOffset date)
|
||||
{
|
||||
AutoSizeAxes = Axes.Both;
|
||||
Font = "Exo2.0-RegularItalic";
|
||||
|
||||
Date = date.ToLocalTime();
|
||||
|
@ -2,7 +2,6 @@
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using System;
|
||||
using System.Drawing.Imaging;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
@ -19,6 +18,7 @@ using osu.Game.Configuration;
|
||||
using osu.Game.Input.Bindings;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Overlays.Notifications;
|
||||
using SixLabors.ImageSharp;
|
||||
|
||||
namespace osu.Game.Graphics
|
||||
{
|
||||
@ -71,7 +71,7 @@ namespace osu.Game.Graphics
|
||||
|
||||
private volatile int screenShotTasks;
|
||||
|
||||
public async Task TakeScreenshotAsync() => await Task.Run(async () =>
|
||||
public Task TakeScreenshotAsync() => Task.Run(async () =>
|
||||
{
|
||||
Interlocked.Increment(ref screenShotTasks);
|
||||
|
||||
@ -90,7 +90,7 @@ namespace osu.Game.Graphics
|
||||
waitDelegate.Cancel();
|
||||
}
|
||||
|
||||
using (var bitmap = await host.TakeScreenshotAsync())
|
||||
using (var image = await host.TakeScreenshotAsync())
|
||||
{
|
||||
Interlocked.Decrement(ref screenShotTasks);
|
||||
|
||||
@ -102,10 +102,10 @@ namespace osu.Game.Graphics
|
||||
switch (screenshotFormat.Value)
|
||||
{
|
||||
case ScreenshotFormat.Png:
|
||||
bitmap.Save(stream, ImageFormat.Png);
|
||||
image.SaveAsPng(stream);
|
||||
break;
|
||||
case ScreenshotFormat.Jpg:
|
||||
bitmap.Save(stream, ImageFormat.Jpeg);
|
||||
image.SaveAsJpeg(stream);
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(screenshotFormat));
|
||||
|
@ -71,7 +71,7 @@ namespace osu.Game.Graphics
|
||||
|
||||
if (loadableIcon == loadedIcon) return;
|
||||
|
||||
var texture = store?.Get(((char)loadableIcon).ToString());
|
||||
var texture = store.Get(((char)loadableIcon).ToString());
|
||||
|
||||
spriteMain.Texture = texture;
|
||||
spriteShadow.Texture = texture;
|
||||
@ -95,7 +95,7 @@ namespace osu.Game.Graphics
|
||||
{
|
||||
//adjust shadow alpha based on highest component intensity to avoid muddy display of darker text.
|
||||
//squared result for quadratic fall-off seems to give the best result.
|
||||
var avgColour = (Color4)DrawInfo.Colour.AverageColour;
|
||||
var avgColour = (Color4)DrawColourInfo.Colour.AverageColour;
|
||||
|
||||
spriteShadow.Alpha = (float)Math.Pow(Math.Max(Math.Max(avgColour.R, avgColour.G), avgColour.B), 2);
|
||||
|
||||
@ -129,7 +129,7 @@ namespace osu.Game.Graphics
|
||||
if (icon == value) return;
|
||||
|
||||
icon = value;
|
||||
if (IsLoaded)
|
||||
if (LoadState == LoadState.Loaded)
|
||||
updateTexture();
|
||||
}
|
||||
}
|
||||
|
@ -3,9 +3,6 @@
|
||||
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.MathUtils;
|
||||
using OpenTK;
|
||||
using OpenTK.Graphics;
|
||||
using osu.Framework.Graphics.Transforms;
|
||||
|
||||
namespace osu.Game.Graphics.Sprites
|
||||
@ -19,27 +16,6 @@ namespace osu.Game.Graphics.Sprites
|
||||
Shadow = true;
|
||||
TextSize = FONT_SIZE;
|
||||
}
|
||||
|
||||
protected override Drawable CreateFallbackCharacterDrawable()
|
||||
{
|
||||
var tex = GetTextureForCharacter('?');
|
||||
|
||||
if (tex != null)
|
||||
{
|
||||
float adjust = (RNG.NextSingle() - 0.5f) * 2;
|
||||
return new Sprite
|
||||
{
|
||||
Texture = tex,
|
||||
Origin = Anchor.Centre,
|
||||
Anchor = Anchor.Centre,
|
||||
Scale = new Vector2(1 + adjust * 0.2f),
|
||||
Rotation = adjust * 15,
|
||||
Colour = Color4.White,
|
||||
};
|
||||
}
|
||||
|
||||
return base.CreateFallbackCharacterDrawable();
|
||||
}
|
||||
}
|
||||
|
||||
public static class OsuSpriteTextTransformExtensions
|
||||
|
@ -1,18 +0,0 @@
|
||||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using osu.Framework.Graphics.Textures;
|
||||
using osu.Framework.IO.Stores;
|
||||
|
||||
namespace osu.Game.Graphics.Textures
|
||||
{
|
||||
/// <summary>
|
||||
/// A texture store that bypasses atlasing.
|
||||
/// </summary>
|
||||
public class LargeTextureStore : TextureStore
|
||||
{
|
||||
public LargeTextureStore(IResourceStore<RawTexture> store = null) : base(store, false)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
@ -1,7 +1,16 @@
|
||||
// Copyright (c) 2007-2018 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.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Framework.Input.EventArgs;
|
||||
using osu.Framework.Input.States;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using OpenTK.Graphics;
|
||||
|
||||
namespace osu.Game.Graphics.UserInterface
|
||||
{
|
||||
@ -10,9 +19,73 @@ namespace osu.Game.Graphics.UserInterface
|
||||
/// </summary>
|
||||
public class OsuButton : Button
|
||||
{
|
||||
private Box hover;
|
||||
|
||||
public OsuButton()
|
||||
{
|
||||
Add(new HoverClickSounds(HoverSampleSet.Loud));
|
||||
Height = 40;
|
||||
|
||||
Content.Masking = true;
|
||||
Content.CornerRadius = 5;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuColour colours)
|
||||
{
|
||||
BackgroundColour = colours.BlueDark;
|
||||
|
||||
AddRange(new Drawable[]
|
||||
{
|
||||
hover = new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Blending = BlendingMode.Additive,
|
||||
Colour = Color4.White.Opacity(0.1f),
|
||||
Alpha = 0,
|
||||
Depth = -1
|
||||
},
|
||||
new HoverClickSounds(HoverSampleSet.Loud),
|
||||
});
|
||||
|
||||
Enabled.ValueChanged += enabled_ValueChanged;
|
||||
Enabled.TriggerChange();
|
||||
}
|
||||
|
||||
private void enabled_ValueChanged(bool enabled)
|
||||
{
|
||||
this.FadeColour(enabled ? Color4.White : Color4.Gray, 200, Easing.OutQuint);
|
||||
}
|
||||
|
||||
protected override bool OnHover(InputState state)
|
||||
{
|
||||
hover.FadeIn(200);
|
||||
return base.OnHover(state);
|
||||
}
|
||||
|
||||
protected override void OnHoverLost(InputState state)
|
||||
{
|
||||
hover.FadeOut(200);
|
||||
base.OnHoverLost(state);
|
||||
}
|
||||
|
||||
protected override bool OnMouseDown(InputState state, MouseDownEventArgs args)
|
||||
{
|
||||
Content.ScaleTo(0.9f, 4000, Easing.OutQuint);
|
||||
return base.OnMouseDown(state, args);
|
||||
}
|
||||
|
||||
protected override bool OnMouseUp(InputState state, MouseUpEventArgs args)
|
||||
{
|
||||
Content.ScaleTo(1, 1000, Easing.OutElastic);
|
||||
return base.OnMouseUp(state, args);
|
||||
}
|
||||
|
||||
protected override SpriteText CreateText() => new OsuSpriteText
|
||||
{
|
||||
Depth = -1,
|
||||
Origin = Anchor.Centre,
|
||||
Anchor = Anchor.Centre,
|
||||
Font = @"Exo2.0-Bold",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -2,17 +2,10 @@
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using System.Collections.Generic;
|
||||
using OpenTK.Graphics;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Input.EventArgs;
|
||||
using osu.Framework.Input.States;
|
||||
using osu.Game.Graphics.Backgrounds;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
|
||||
namespace osu.Game.Graphics.UserInterface
|
||||
{
|
||||
@ -21,79 +14,17 @@ namespace osu.Game.Graphics.UserInterface
|
||||
/// </summary>
|
||||
public class TriangleButton : OsuButton, IFilterable
|
||||
{
|
||||
private Box hover;
|
||||
|
||||
protected Triangles Triangles;
|
||||
|
||||
public TriangleButton()
|
||||
{
|
||||
Height = 40;
|
||||
}
|
||||
|
||||
protected override SpriteText CreateText() => new OsuSpriteText
|
||||
{
|
||||
Depth = -1,
|
||||
Origin = Anchor.Centre,
|
||||
Anchor = Anchor.Centre,
|
||||
Font = @"Exo2.0-Bold",
|
||||
};
|
||||
protected Triangles Triangles { get; private set; }
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuColour colours)
|
||||
{
|
||||
BackgroundColour = colours.BlueDark;
|
||||
|
||||
Content.Masking = true;
|
||||
Content.CornerRadius = 5;
|
||||
|
||||
AddRange(new Drawable[]
|
||||
{
|
||||
Triangles = new Triangles
|
||||
Add(Triangles = new Triangles
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
ColourDark = colours.BlueDarker,
|
||||
ColourLight = colours.Blue,
|
||||
},
|
||||
hover = new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Blending = BlendingMode.Additive,
|
||||
Colour = Color4.White.Opacity(0.1f),
|
||||
Alpha = 0,
|
||||
},
|
||||
});
|
||||
|
||||
Enabled.ValueChanged += enabled_ValueChanged;
|
||||
Enabled.TriggerChange();
|
||||
}
|
||||
|
||||
private void enabled_ValueChanged(bool enabled)
|
||||
{
|
||||
this.FadeColour(enabled ? Color4.White : Color4.Gray, 200, Easing.OutQuint);
|
||||
}
|
||||
|
||||
protected override bool OnHover(InputState state)
|
||||
{
|
||||
hover.FadeIn(200);
|
||||
return base.OnHover(state);
|
||||
}
|
||||
|
||||
protected override void OnHoverLost(InputState state)
|
||||
{
|
||||
hover.FadeOut(200);
|
||||
base.OnHoverLost(state);
|
||||
}
|
||||
|
||||
protected override bool OnMouseDown(InputState state, MouseDownEventArgs args)
|
||||
{
|
||||
Content.ScaleTo(0.9f, 4000, Easing.OutQuint);
|
||||
return base.OnMouseDown(state, args);
|
||||
}
|
||||
|
||||
protected override bool OnMouseUp(InputState state, MouseUpEventArgs args)
|
||||
{
|
||||
Content.ScaleTo(1, 1000, Easing.OutElastic);
|
||||
return base.OnMouseUp(state, args);
|
||||
}
|
||||
|
||||
public IEnumerable<string> FilterTerms => new[] { Text };
|
||||
|
@ -3,6 +3,7 @@
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using osu.Framework.IO.Stores;
|
||||
|
||||
namespace osu.Game.IO.Archives
|
||||
@ -28,7 +29,9 @@ namespace osu.Game.IO.Archives
|
||||
|
||||
public abstract IEnumerable<string> Filenames { get; }
|
||||
|
||||
public virtual byte[] Get(string name)
|
||||
public virtual byte[] Get(string name) => GetAsync(name).Result;
|
||||
|
||||
public async Task<byte[]> GetAsync(string name)
|
||||
{
|
||||
using (Stream input = GetStream(name))
|
||||
{
|
||||
@ -36,7 +39,7 @@ namespace osu.Game.IO.Archives
|
||||
return null;
|
||||
|
||||
byte[] buffer = new byte[input.Length];
|
||||
input.Read(buffer, 0, buffer.Length);
|
||||
await input.ReadAsync(buffer, 0, buffer.Length);
|
||||
return buffer;
|
||||
}
|
||||
}
|
||||
|
@ -2,12 +2,10 @@
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Net;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using osu.Framework.Configuration;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Logging;
|
||||
@ -26,7 +24,7 @@ namespace osu.Game.Online.API
|
||||
private const string client_id = @"5";
|
||||
private const string client_secret = @"FGc9GAtyHzeQDshWP5Ah7dega8hJACAJpQtw6OXk";
|
||||
|
||||
private ConcurrentQueue<APIRequest> queue = new ConcurrentQueue<APIRequest>();
|
||||
private readonly Queue<APIRequest> queue = new Queue<APIRequest>();
|
||||
|
||||
/// <summary>
|
||||
/// The username/email provided by the user when initiating a login.
|
||||
@ -55,7 +53,13 @@ namespace osu.Game.Online.API
|
||||
authentication.TokenString = config.Get<string>(OsuSetting.Token);
|
||||
authentication.Token.ValueChanged += onTokenChanged;
|
||||
|
||||
Task.Factory.StartNew(run, cancellationToken.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default);
|
||||
var thread = new Thread(run)
|
||||
{
|
||||
Name = "APIAccess",
|
||||
IsBackground = true
|
||||
};
|
||||
|
||||
thread.Start();
|
||||
}
|
||||
|
||||
private void onTokenChanged(OAuthToken token) => config.Set(OsuSetting.Token, config.Get<bool>(OsuSetting.SavePassword) ? authentication.TokenString : string.Empty);
|
||||
@ -75,10 +79,7 @@ namespace osu.Game.Online.API
|
||||
|
||||
public void Unregister(IOnlineComponent component)
|
||||
{
|
||||
Scheduler.Add(delegate
|
||||
{
|
||||
components.Remove(component);
|
||||
});
|
||||
Scheduler.Add(delegate { components.Remove(component); });
|
||||
}
|
||||
|
||||
public string AccessToken => authentication.RequestAccessToken();
|
||||
@ -103,6 +104,7 @@ namespace osu.Game.Online.API
|
||||
log.Add(@"Queueing a ping request");
|
||||
Queue(new ListChannelsRequest { Timeout = 5000 });
|
||||
}
|
||||
|
||||
break;
|
||||
case APIState.Offline:
|
||||
case APIState.Connecting:
|
||||
@ -161,18 +163,21 @@ namespace osu.Game.Online.API
|
||||
continue;
|
||||
}
|
||||
|
||||
//process the request queue.
|
||||
while (true)
|
||||
{
|
||||
APIRequest req;
|
||||
while (queue.TryPeek(out req))
|
||||
|
||||
lock (queue)
|
||||
{
|
||||
if (handleRequest(req))
|
||||
{
|
||||
//we have succeeded, so let's unqueue.
|
||||
queue.TryDequeue(out req);
|
||||
}
|
||||
if (queue.Count == 0) break;
|
||||
req = queue.Dequeue();
|
||||
}
|
||||
|
||||
Thread.Sleep(1);
|
||||
// TODO: handle failures better
|
||||
handleRequest(req);
|
||||
}
|
||||
|
||||
Thread.Sleep(50);
|
||||
}
|
||||
}
|
||||
|
||||
@ -205,7 +210,8 @@ namespace osu.Game.Online.API
|
||||
}
|
||||
catch (WebException we)
|
||||
{
|
||||
HttpStatusCode statusCode = (we.Response as HttpWebResponse)?.StatusCode ?? (we.Status == WebExceptionStatus.UnknownError ? HttpStatusCode.NotAcceptable : HttpStatusCode.RequestTimeout);
|
||||
HttpStatusCode statusCode = (we.Response as HttpWebResponse)?.StatusCode
|
||||
?? (we.Status == WebExceptionStatus.UnknownError ? HttpStatusCode.NotAcceptable : HttpStatusCode.RequestTimeout);
|
||||
|
||||
// special cases for un-typed but useful message responses.
|
||||
switch (we.Message)
|
||||
@ -247,6 +253,7 @@ namespace osu.Game.Online.API
|
||||
}
|
||||
|
||||
private APIState state;
|
||||
|
||||
public APIState State
|
||||
{
|
||||
get { return state; }
|
||||
@ -271,7 +278,10 @@ namespace osu.Game.Online.API
|
||||
|
||||
public bool IsLoggedIn => LocalUser.Value.Id > 1;
|
||||
|
||||
public void Queue(APIRequest request) => queue.Enqueue(request);
|
||||
public void Queue(APIRequest request)
|
||||
{
|
||||
lock (queue) queue.Enqueue(request);
|
||||
}
|
||||
|
||||
public event StateChangeDelegate OnStateChange;
|
||||
|
||||
@ -279,18 +289,19 @@ namespace osu.Game.Online.API
|
||||
|
||||
private void flushQueue(bool failOldRequests = true)
|
||||
{
|
||||
var oldQueue = queue;
|
||||
lock (queue)
|
||||
{
|
||||
var oldQueueRequests = queue.ToArray();
|
||||
|
||||
//flush the queue.
|
||||
queue = new ConcurrentQueue<APIRequest>();
|
||||
queue.Clear();
|
||||
|
||||
if (failOldRequests)
|
||||
{
|
||||
APIRequest req;
|
||||
while (oldQueue.TryDequeue(out req))
|
||||
foreach (var req in oldQueueRequests)
|
||||
req.Fail(new WebException(@"Disconnected from server"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Logout(bool clearUsername = true)
|
||||
{
|
||||
|
@ -36,6 +36,8 @@ using osu.Game.Skinning;
|
||||
using OpenTK.Graphics;
|
||||
using osu.Game.Overlays.Volume;
|
||||
using osu.Game.Screens.Select;
|
||||
using osu.Game.Utils;
|
||||
using LogLevel = osu.Framework.Logging.LogLevel;
|
||||
|
||||
namespace osu.Game
|
||||
{
|
||||
@ -65,16 +67,18 @@ namespace osu.Game
|
||||
|
||||
private ScreenshotManager screenshotManager;
|
||||
|
||||
protected RavenLogger RavenLogger;
|
||||
|
||||
public virtual Storage GetStorageForStableInstall() => null;
|
||||
|
||||
private Intro intro
|
||||
{
|
||||
get
|
||||
{
|
||||
Screen s = screenStack;
|
||||
while (s != null && !(s is Intro))
|
||||
s = s.ChildScreen;
|
||||
return s as Intro;
|
||||
Screen screen = screenStack;
|
||||
while (screen != null && !(screen is Intro))
|
||||
screen = screen.ChildScreen;
|
||||
return screen as Intro;
|
||||
}
|
||||
}
|
||||
|
||||
@ -108,6 +112,8 @@ namespace osu.Game
|
||||
this.args = args;
|
||||
|
||||
forwardLoggedErrorsToNotifications();
|
||||
|
||||
RavenLogger = new RavenLogger(this);
|
||||
}
|
||||
|
||||
public void ToggleSettings() => settings.ToggleVisibility();
|
||||
@ -120,8 +126,8 @@ namespace osu.Game
|
||||
/// <param name="toolbar">Whether the toolbar should also be hidden.</param>
|
||||
public void CloseAllOverlays(bool toolbar = true)
|
||||
{
|
||||
foreach (var o in overlays)
|
||||
o.State = Visibility.Hidden;
|
||||
foreach (var overlay in overlays)
|
||||
overlay.State = Visibility.Hidden;
|
||||
if (toolbar) Toolbar.State = Visibility.Hidden;
|
||||
}
|
||||
|
||||
@ -145,13 +151,15 @@ namespace osu.Game
|
||||
|
||||
if (args?.Length > 0)
|
||||
{
|
||||
var paths = args.Where(a => !a.StartsWith(@"-"));
|
||||
|
||||
Task.Run(() => Import(paths.ToArray()));
|
||||
var paths = args.Where(a => !a.StartsWith(@"-")).ToArray();
|
||||
if (paths.Length > 0)
|
||||
Task.Run(() => Import(paths));
|
||||
}
|
||||
|
||||
dependencies.CacheAs(this);
|
||||
|
||||
dependencies.Cache(RavenLogger);
|
||||
|
||||
dependencies.CacheAs(ruleset);
|
||||
dependencies.CacheAs<IBindable<RulesetInfo>>(ruleset);
|
||||
|
||||
@ -236,7 +244,7 @@ namespace osu.Game
|
||||
/// <param name="beatmapId">The beatmap to show.</param>
|
||||
public void ShowBeatmap(int beatmapId) => beatmapSetOverlay.FetchAndShowBeatmap(beatmapId);
|
||||
|
||||
protected void LoadScore(Score s)
|
||||
protected void LoadScore(Score score)
|
||||
{
|
||||
scoreLoad?.Cancel();
|
||||
|
||||
@ -244,18 +252,18 @@ namespace osu.Game
|
||||
|
||||
if (menu == null)
|
||||
{
|
||||
scoreLoad = Schedule(() => LoadScore(s));
|
||||
scoreLoad = Schedule(() => LoadScore(score));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!menu.IsCurrentScreen)
|
||||
{
|
||||
menu.MakeCurrent();
|
||||
this.Delay(500).Schedule(() => LoadScore(s), out scoreLoad);
|
||||
this.Delay(500).Schedule(() => LoadScore(score), out scoreLoad);
|
||||
return;
|
||||
}
|
||||
|
||||
if (s.Beatmap == null)
|
||||
if (score.Beatmap == null)
|
||||
{
|
||||
notifications.Post(new SimpleNotification
|
||||
{
|
||||
@ -265,12 +273,18 @@ namespace osu.Game
|
||||
return;
|
||||
}
|
||||
|
||||
ruleset.Value = s.Ruleset;
|
||||
ruleset.Value = score.Ruleset;
|
||||
|
||||
Beatmap.Value = BeatmapManager.GetWorkingBeatmap(s.Beatmap);
|
||||
Beatmap.Value.Mods.Value = s.Mods;
|
||||
Beatmap.Value = BeatmapManager.GetWorkingBeatmap(score.Beatmap);
|
||||
Beatmap.Value.Mods.Value = score.Mods;
|
||||
|
||||
menu.Push(new PlayerLoader(new ReplayPlayer(s.Replay)));
|
||||
menu.Push(new PlayerLoader(new ReplayPlayer(score.Replay)));
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
base.Dispose(isDisposing);
|
||||
RavenLogger.Dispose();
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
@ -285,11 +299,13 @@ namespace osu.Game
|
||||
// This prevents the cursor from showing until we have a screen with CursorVisible = true
|
||||
MenuCursorContainer.CanShowCursor = currentScreen?.CursorVisible ?? false;
|
||||
|
||||
// hook up notifications to components.
|
||||
// todo: all archive managers should be able to be looped here.
|
||||
SkinManager.PostNotification = n => notifications?.Post(n);
|
||||
BeatmapManager.PostNotification = n => notifications?.Post(n);
|
||||
SkinManager.GetStableStorage = GetStorageForStableInstall;
|
||||
|
||||
BeatmapManager.PostNotification = n => notifications?.Post(n);
|
||||
BeatmapManager.GetStableStorage = GetStorageForStableInstall;
|
||||
|
||||
BeatmapManager.PresentBeatmap = PresentBeatmap;
|
||||
|
||||
AddRange(new Drawable[]
|
||||
@ -449,7 +465,7 @@ namespace osu.Game
|
||||
Schedule(() => notifications.Post(new SimpleNotification
|
||||
{
|
||||
Icon = entry.Level == LogLevel.Important ? FontAwesome.fa_exclamation_circle : FontAwesome.fa_bomb,
|
||||
Text = entry.Message,
|
||||
Text = entry.Message + (entry.Exception != null && IsDeployedBuild ? "\n\nThis error has been automatically reported to the devs." : string.Empty),
|
||||
}));
|
||||
}
|
||||
else if (recentLogCount == short_term_display_limit)
|
||||
@ -490,7 +506,27 @@ namespace osu.Game
|
||||
// schedule is here to ensure that all component loads are done after LoadComplete is run (and thus all dependencies are cached).
|
||||
// with some better organisation of LoadComplete to do construction and dependency caching in one step, followed by calls to loadComponentSingleFile,
|
||||
// we could avoid the need for scheduling altogether.
|
||||
Schedule(() => { asyncLoadStream = asyncLoadStream?.ContinueWith(t => LoadComponentAsync(d, add).Wait()) ?? LoadComponentAsync(d, add); });
|
||||
Schedule(() =>
|
||||
{
|
||||
var previousLoadStream = asyncLoadStream;
|
||||
|
||||
//chain with existing load stream
|
||||
asyncLoadStream = Task.Run(async () =>
|
||||
{
|
||||
if (previousLoadStream != null)
|
||||
await previousLoadStream;
|
||||
|
||||
try
|
||||
{
|
||||
Logger.Log($"Loading {d}...", LoggingTarget.Debug);
|
||||
await LoadComponentAsync(d, add);
|
||||
Logger.Log($"Loaded {d}!", LoggingTarget.Debug);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public bool OnPressed(GlobalAction action)
|
||||
@ -601,6 +637,7 @@ namespace osu.Game
|
||||
private void screenAdded(Screen newScreen)
|
||||
{
|
||||
currentScreen = (OsuScreen)newScreen;
|
||||
Logger.Log($"Screen changed → {currentScreen}");
|
||||
|
||||
newScreen.ModePushed += screenAdded;
|
||||
newScreen.Exited += screenRemoved;
|
||||
@ -609,6 +646,7 @@ namespace osu.Game
|
||||
private void screenRemoved(Screen newScreen)
|
||||
{
|
||||
currentScreen = (OsuScreen)newScreen;
|
||||
Logger.Log($"Screen changed ← {currentScreen}");
|
||||
|
||||
if (newScreen == null)
|
||||
Exit();
|
||||
|
@ -24,7 +24,6 @@ using osu.Framework.Input;
|
||||
using osu.Framework.Logging;
|
||||
using osu.Game.Audio;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.Graphics.Textures;
|
||||
using osu.Game.Input;
|
||||
using osu.Game.Input.Bindings;
|
||||
using osu.Game.IO;
|
||||
@ -109,11 +108,37 @@ namespace osu.Game
|
||||
|
||||
dependencies.Cache(contextFactory = new DatabaseContextFactory(Host.Storage));
|
||||
|
||||
dependencies.Cache(new LargeTextureStore(new RawTextureLoaderStore(new NamespacedResourceStore<byte[]>(Resources, @"Textures"))));
|
||||
dependencies.Cache(new LargeTextureStore(new TextureLoaderStore(new NamespacedResourceStore<byte[]>(Resources, @"Textures"))));
|
||||
|
||||
dependencies.CacheAs(this);
|
||||
dependencies.Cache(LocalConfig);
|
||||
|
||||
//this completely overrides the framework default. will need to change once we make a proper FontStore.
|
||||
dependencies.Cache(Fonts = new FontStore(new GlyphStore(Resources, @"Fonts/FontAwesome")));
|
||||
|
||||
Fonts.AddStore(new GlyphStore(Resources, @"Fonts/osuFont"));
|
||||
Fonts.AddStore(new GlyphStore(Resources, @"Fonts/Exo2.0-Medium"));
|
||||
Fonts.AddStore(new GlyphStore(Resources, @"Fonts/Exo2.0-MediumItalic"));
|
||||
|
||||
Fonts.AddStore(new GlyphStore(Resources, @"Fonts/Noto-Basic"));
|
||||
Fonts.AddStore(new GlyphStore(Resources, @"Fonts/Noto-Hangul"));
|
||||
Fonts.AddStore(new GlyphStore(Resources, @"Fonts/Noto-CJK-Basic"));
|
||||
Fonts.AddStore(new GlyphStore(Resources, @"Fonts/Noto-CJK-Compatibility"));
|
||||
|
||||
Fonts.AddStore(new GlyphStore(Resources, @"Fonts/Exo2.0-Regular"));
|
||||
Fonts.AddStore(new GlyphStore(Resources, @"Fonts/Exo2.0-RegularItalic"));
|
||||
Fonts.AddStore(new GlyphStore(Resources, @"Fonts/Exo2.0-SemiBold"));
|
||||
Fonts.AddStore(new GlyphStore(Resources, @"Fonts/Exo2.0-SemiBoldItalic"));
|
||||
Fonts.AddStore(new GlyphStore(Resources, @"Fonts/Exo2.0-Bold"));
|
||||
Fonts.AddStore(new GlyphStore(Resources, @"Fonts/Exo2.0-BoldItalic"));
|
||||
Fonts.AddStore(new GlyphStore(Resources, @"Fonts/Exo2.0-Light"));
|
||||
Fonts.AddStore(new GlyphStore(Resources, @"Fonts/Exo2.0-LightItalic"));
|
||||
Fonts.AddStore(new GlyphStore(Resources, @"Fonts/Exo2.0-Black"));
|
||||
Fonts.AddStore(new GlyphStore(Resources, @"Fonts/Exo2.0-BlackItalic"));
|
||||
|
||||
Fonts.AddStore(new GlyphStore(Resources, @"Fonts/Venera"));
|
||||
Fonts.AddStore(new GlyphStore(Resources, @"Fonts/Venera-Light"));
|
||||
|
||||
runMigrations();
|
||||
|
||||
dependencies.Cache(SkinManager = new SkinManager(Host.Storage, contextFactory, Host, Audio));
|
||||
@ -137,33 +162,6 @@ namespace osu.Game
|
||||
fileImporters.Add(ScoreStore);
|
||||
fileImporters.Add(SkinManager);
|
||||
|
||||
//this completely overrides the framework default. will need to change once we make a proper FontStore.
|
||||
dependencies.Cache(Fonts = new FontStore { ScaleAdjust = 100 });
|
||||
|
||||
Fonts.AddStore(new GlyphStore(Resources, @"Fonts/FontAwesome"));
|
||||
Fonts.AddStore(new GlyphStore(Resources, @"Fonts/osuFont"));
|
||||
Fonts.AddStore(new GlyphStore(Resources, @"Fonts/Exo2.0-Medium"));
|
||||
Fonts.AddStore(new GlyphStore(Resources, @"Fonts/Exo2.0-MediumItalic"));
|
||||
|
||||
Fonts.AddStore(new GlyphStore(Resources, @"Fonts/Noto-Basic"));
|
||||
Fonts.AddStore(new GlyphStore(Resources, @"Fonts/Noto-Hangul"));
|
||||
Fonts.AddStore(new GlyphStore(Resources, @"Fonts/Noto-CJK-Basic"));
|
||||
Fonts.AddStore(new GlyphStore(Resources, @"Fonts/Noto-CJK-Compatibility"));
|
||||
|
||||
Fonts.AddStore(new GlyphStore(Resources, @"Fonts/Exo2.0-Regular"));
|
||||
Fonts.AddStore(new GlyphStore(Resources, @"Fonts/Exo2.0-RegularItalic"));
|
||||
Fonts.AddStore(new GlyphStore(Resources, @"Fonts/Exo2.0-SemiBold"));
|
||||
Fonts.AddStore(new GlyphStore(Resources, @"Fonts/Exo2.0-SemiBoldItalic"));
|
||||
Fonts.AddStore(new GlyphStore(Resources, @"Fonts/Exo2.0-Bold"));
|
||||
Fonts.AddStore(new GlyphStore(Resources, @"Fonts/Exo2.0-BoldItalic"));
|
||||
Fonts.AddStore(new GlyphStore(Resources, @"Fonts/Exo2.0-Light"));
|
||||
Fonts.AddStore(new GlyphStore(Resources, @"Fonts/Exo2.0-LightItalic"));
|
||||
Fonts.AddStore(new GlyphStore(Resources, @"Fonts/Exo2.0-Black"));
|
||||
Fonts.AddStore(new GlyphStore(Resources, @"Fonts/Exo2.0-BlackItalic"));
|
||||
|
||||
Fonts.AddStore(new GlyphStore(Resources, @"Fonts/Venera"));
|
||||
Fonts.AddStore(new GlyphStore(Resources, @"Fonts/Venera-Light"));
|
||||
|
||||
var defaultBeatmap = new DummyWorkingBeatmap(this);
|
||||
beatmap = new OsuBindableBeatmap(defaultBeatmap, Audio);
|
||||
BeatmapManager.DefaultBeatmap = defaultBeatmap;
|
||||
|
@ -62,7 +62,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores
|
||||
loading = true;
|
||||
|
||||
getScoresRequest = new GetScoresRequest(beatmap, beatmap.Ruleset);
|
||||
getScoresRequest.Success += r => Scores = r.Scores;
|
||||
getScoresRequest.Success += r => Schedule(() => Scores = r.Scores);
|
||||
api.Queue(getScoresRequest);
|
||||
}
|
||||
}
|
||||
@ -134,5 +134,10 @@ namespace osu.Game.Overlays.BeatmapSet.Scores
|
||||
this.api = api;
|
||||
updateDisplay();
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
getScoresRequest?.Cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -187,7 +187,7 @@ namespace osu.Game.Overlays.Direct
|
||||
base.LoadComplete();
|
||||
this.FadeInFromZero(200, Easing.Out);
|
||||
|
||||
PreviewPlaying.ValueChanged += newValue => PlayButton.FadeTo(newValue || IsHovered ? 1 : 0, 120, Easing.InOutQuint);
|
||||
PreviewPlaying.ValueChanged += newValue => PlayButton.FadeTo(newValue || IsHovered || !FadePlayButton ? 1 : 0, 120, Easing.InOutQuint);
|
||||
PreviewPlaying.ValueChanged += newValue => PreviewBar.FadeTo(newValue ? 1 : 0, 120, Easing.InOutQuint);
|
||||
}
|
||||
|
||||
|
@ -240,6 +240,15 @@ namespace osu.Game.Overlays
|
||||
});
|
||||
}
|
||||
|
||||
protected override void PopIn()
|
||||
{
|
||||
base.PopIn();
|
||||
|
||||
// Queries are allowed to be run only on the first pop-in
|
||||
if (getSetsRequest == null)
|
||||
Scheduler.AddOnce(updateSearch);
|
||||
}
|
||||
|
||||
private SearchBeatmapSetsRequest getSetsRequest;
|
||||
|
||||
private readonly Bindable<string> currentQuery = new Bindable<string>();
|
||||
@ -251,16 +260,22 @@ namespace osu.Game.Overlays
|
||||
{
|
||||
queryChangedDebounce?.Cancel();
|
||||
|
||||
if (!IsLoaded) return;
|
||||
if (!IsLoaded)
|
||||
return;
|
||||
|
||||
if (State == Visibility.Hidden)
|
||||
return;
|
||||
|
||||
BeatmapSets = null;
|
||||
ResultAmounts = null;
|
||||
|
||||
getSetsRequest?.Cancel();
|
||||
|
||||
if (api == null) return;
|
||||
if (api == null)
|
||||
return;
|
||||
|
||||
if (Header.Tabs.Current.Value == DirectTab.Search && (Filter.Search.Text == string.Empty || currentQuery == string.Empty)) return;
|
||||
if (Header.Tabs.Current.Value == DirectTab.Search && (Filter.Search.Text == string.Empty || currentQuery == string.Empty))
|
||||
return;
|
||||
|
||||
previewTrackManager.StopAnyPlaying(this);
|
||||
|
||||
|
@ -27,6 +27,11 @@ namespace osu.Game.Overlays.Mods
|
||||
{
|
||||
public class ModSelectOverlay : WaveOverlayContainer
|
||||
{
|
||||
/// <summary>
|
||||
/// How much this container should overflow the sides of the screen to account for parallax shifting.
|
||||
/// </summary>
|
||||
private const float overflow_padding = 50;
|
||||
|
||||
private const float content_width = 0.8f;
|
||||
|
||||
protected Color4 LowMultiplierColour, HighMultiplierColour;
|
||||
@ -199,6 +204,11 @@ namespace osu.Game.Overlays.Mods
|
||||
Waves.FourthWaveColour = OsuColour.FromHex(@"003a4e");
|
||||
|
||||
Height = 510;
|
||||
Padding = new MarginPadding
|
||||
{
|
||||
Left = -overflow_padding,
|
||||
Right = -overflow_padding
|
||||
};
|
||||
|
||||
Children = new Drawable[]
|
||||
{
|
||||
@ -258,6 +268,11 @@ namespace osu.Game.Overlays.Mods
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Direction = FillDirection.Vertical,
|
||||
Width = content_width,
|
||||
Padding = new MarginPadding
|
||||
{
|
||||
Left = overflow_padding,
|
||||
Right = overflow_padding
|
||||
},
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new OsuSpriteText
|
||||
@ -295,7 +310,12 @@ namespace osu.Game.Overlays.Mods
|
||||
Origin = Anchor.TopCentre,
|
||||
Anchor = Anchor.TopCentre,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Padding = new MarginPadding { Vertical = 10 },
|
||||
Padding = new MarginPadding
|
||||
{
|
||||
Vertical = 10,
|
||||
Left = overflow_padding,
|
||||
Right = overflow_padding
|
||||
},
|
||||
Child = ModSectionsContainer = new FillFlowContainer<ModSection>
|
||||
{
|
||||
Origin = Anchor.TopCentre,
|
||||
@ -341,7 +361,9 @@ namespace osu.Game.Overlays.Mods
|
||||
Direction = FillDirection.Horizontal,
|
||||
Padding = new MarginPadding
|
||||
{
|
||||
Vertical = 15
|
||||
Vertical = 15,
|
||||
Left = overflow_padding,
|
||||
Right = overflow_padding
|
||||
},
|
||||
Children = new Drawable[]
|
||||
{
|
||||
|
@ -96,8 +96,7 @@ namespace osu.Game.Overlays
|
||||
base.LoadComplete();
|
||||
|
||||
StateChanged += _ => updateProcessingMode();
|
||||
OverlayActivationMode.ValueChanged += _ => updateProcessingMode();
|
||||
OverlayActivationMode.TriggerChange();
|
||||
OverlayActivationMode.BindValueChanged(_ => updateProcessingMode(), true);
|
||||
}
|
||||
|
||||
private int totalCount => sections.Select(c => c.DisplayedCount).Sum();
|
||||
|
@ -169,7 +169,7 @@ namespace osu.Game.Overlays.Notifications
|
||||
public Action<Notification> CompletionTarget { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// An action to complete when the completion notification is clicked.
|
||||
/// An action to complete when the completion notification is clicked. Return true to close.
|
||||
/// </summary>
|
||||
public Func<bool> CompletionClickAction;
|
||||
|
||||
|
@ -14,8 +14,8 @@ namespace osu.Game.Overlays.Profile.Sections.Beatmaps
|
||||
public class PaginatedBeatmapContainer : PaginatedContainer
|
||||
{
|
||||
private const float panel_padding = 10f;
|
||||
|
||||
private readonly BeatmapSetType type;
|
||||
private GetUserBeatmapsRequest request;
|
||||
|
||||
public PaginatedBeatmapContainer(BeatmapSetType type, Bindable<User> user, string header, string missing = "None... yet.")
|
||||
: base(user, header, missing)
|
||||
@ -31,9 +31,8 @@ namespace osu.Game.Overlays.Profile.Sections.Beatmaps
|
||||
{
|
||||
base.ShowMore();
|
||||
|
||||
var req = new GetUserBeatmapsRequest(User.Value.Id, type, VisiblePages++ * ItemsPerPage);
|
||||
|
||||
req.Success += sets =>
|
||||
request = new GetUserBeatmapsRequest(User.Value.Id, type, VisiblePages++ * ItemsPerPage);
|
||||
request.Success += sets => Schedule(() =>
|
||||
{
|
||||
ShowMoreButton.FadeTo(sets.Count == ItemsPerPage ? 1 : 0);
|
||||
ShowMoreLoading.Hide();
|
||||
@ -52,9 +51,15 @@ namespace osu.Game.Overlays.Profile.Sections.Beatmaps
|
||||
var panel = new DirectGridPanel(s.ToBeatmapSet(Rulesets));
|
||||
ItemsContainer.Add(panel);
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
Api.Queue(req);
|
||||
Api.Queue(request);
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
base.Dispose(isDisposing);
|
||||
request?.Cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -12,6 +12,8 @@ namespace osu.Game.Overlays.Profile.Sections.Historical
|
||||
{
|
||||
public class PaginatedMostPlayedBeatmapContainer : PaginatedContainer
|
||||
{
|
||||
private GetUserMostPlayedBeatmapsRequest request;
|
||||
|
||||
public PaginatedMostPlayedBeatmapContainer(Bindable<User> user)
|
||||
:base(user, "Most Played Beatmaps", "No records. :(")
|
||||
{
|
||||
@ -24,9 +26,8 @@ namespace osu.Game.Overlays.Profile.Sections.Historical
|
||||
{
|
||||
base.ShowMore();
|
||||
|
||||
var req = new GetUserMostPlayedBeatmapsRequest(User.Value.Id, VisiblePages++ * ItemsPerPage);
|
||||
|
||||
req.Success += beatmaps =>
|
||||
request = new GetUserMostPlayedBeatmapsRequest(User.Value.Id, VisiblePages++ * ItemsPerPage);
|
||||
request.Success += beatmaps => Schedule(() =>
|
||||
{
|
||||
ShowMoreButton.FadeTo(beatmaps.Count == ItemsPerPage ? 1 : 0);
|
||||
ShowMoreLoading.Hide();
|
||||
@ -43,9 +44,16 @@ namespace osu.Game.Overlays.Profile.Sections.Historical
|
||||
{
|
||||
ItemsContainer.Add(new DrawableMostPlayedRow(beatmap.GetBeatmapInfo(Rulesets), beatmap.PlayCount));
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
Api.Queue(req);
|
||||
Api.Queue(request);
|
||||
}
|
||||
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
base.Dispose(isDisposing);
|
||||
request?.Cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -28,6 +28,7 @@ namespace osu.Game.Overlays.Profile.Sections
|
||||
protected readonly Bindable<User> User = new Bindable<User>();
|
||||
|
||||
protected APIAccess Api;
|
||||
protected APIRequest RetrievalRequest;
|
||||
protected RulesetStore Rulesets;
|
||||
|
||||
public PaginatedContainer(Bindable<User> user, string header, string missing)
|
||||
|
@ -16,6 +16,7 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks
|
||||
{
|
||||
private readonly bool includeWeight;
|
||||
private readonly ScoreType type;
|
||||
private GetUserScoresRequest request;
|
||||
|
||||
public PaginatedScoreContainer(ScoreType type, Bindable<User> user, string header, string missing, bool includeWeight = false)
|
||||
: base(user, header, missing)
|
||||
@ -32,9 +33,8 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks
|
||||
{
|
||||
base.ShowMore();
|
||||
|
||||
var req = new GetUserScoresRequest(User.Value.Id, type, VisiblePages++ * ItemsPerPage);
|
||||
|
||||
req.Success += scores =>
|
||||
request = new GetUserScoresRequest(User.Value.Id, type, VisiblePages++ * ItemsPerPage);
|
||||
request.Success += scores => Schedule(() =>
|
||||
{
|
||||
foreach (var s in scores)
|
||||
s.ApplyRuleset(Rulesets.GetRuleset(s.OnlineRulesetID));
|
||||
@ -66,9 +66,15 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks
|
||||
|
||||
ItemsContainer.Add(drawableScore);
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
Api.Queue(req);
|
||||
Api.Queue(request);
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
base.Dispose(isDisposing);
|
||||
request?.Cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -12,6 +12,8 @@ namespace osu.Game.Overlays.Profile.Sections.Recent
|
||||
{
|
||||
public class PaginatedRecentActivityContainer : PaginatedContainer
|
||||
{
|
||||
private GetUserRecentActivitiesRequest request;
|
||||
|
||||
public PaginatedRecentActivityContainer(Bindable<User> user, string header, string missing)
|
||||
: base(user, header, missing)
|
||||
{
|
||||
@ -22,9 +24,8 @@ namespace osu.Game.Overlays.Profile.Sections.Recent
|
||||
{
|
||||
base.ShowMore();
|
||||
|
||||
var req = new GetUserRecentActivitiesRequest(User.Value.Id, VisiblePages++ * ItemsPerPage);
|
||||
|
||||
req.Success += activities =>
|
||||
request = new GetUserRecentActivitiesRequest(User.Value.Id, VisiblePages++ * ItemsPerPage);
|
||||
request.Success += activities => Schedule(() =>
|
||||
{
|
||||
ShowMoreButton.FadeTo(activities.Count == ItemsPerPage ? 1 : 0);
|
||||
ShowMoreLoading.Hide();
|
||||
@ -41,9 +42,15 @@ namespace osu.Game.Overlays.Profile.Sections.Recent
|
||||
{
|
||||
ItemsContainer.Add(new DrawableRecentActivity(activity));
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
Api.Queue(req);
|
||||
Api.Queue(request);
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
base.Dispose(isDisposing);
|
||||
request?.Cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -13,15 +13,18 @@ namespace osu.Game.Overlays.Settings.Sections.Debug
|
||||
{
|
||||
protected override string Header => "Garbage Collector";
|
||||
|
||||
private readonly Bindable<LatencyMode> latencyMode = new Bindable<LatencyMode>();
|
||||
private Bindable<GCLatencyMode> configLatencyMode;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(FrameworkDebugConfigManager config)
|
||||
{
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new SettingsEnumDropdown<GCLatencyMode>
|
||||
new SettingsEnumDropdown<LatencyMode>
|
||||
{
|
||||
LabelText = "Active mode",
|
||||
Bindable = config.GetBindable<GCLatencyMode>(DebugSetting.ActiveGCMode)
|
||||
Bindable = latencyMode
|
||||
},
|
||||
new SettingsButton
|
||||
{
|
||||
@ -29,6 +32,18 @@ namespace osu.Game.Overlays.Settings.Sections.Debug
|
||||
Action = GC.Collect
|
||||
},
|
||||
};
|
||||
|
||||
configLatencyMode = config.GetBindable<GCLatencyMode>(DebugSetting.ActiveGCMode);
|
||||
configLatencyMode.BindValueChanged(v => latencyMode.Value = (LatencyMode)v, true);
|
||||
latencyMode.BindValueChanged(v => configLatencyMode.Value = (GCLatencyMode)v);
|
||||
}
|
||||
|
||||
private enum LatencyMode
|
||||
{
|
||||
Batch = GCLatencyMode.Batch,
|
||||
Interactive = GCLatencyMode.Interactive,
|
||||
LowLatency = GCLatencyMode.LowLatency,
|
||||
SustainedLowLatency = GCLatencyMode.SustainedLowLatency
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,12 @@
|
||||
// Copyright (c) 2007-2018 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.Drawing;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Configuration;
|
||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
|
||||
@ -15,21 +19,36 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
|
||||
private FillFlowContainer letterboxSettings;
|
||||
|
||||
private Bindable<bool> letterboxing;
|
||||
private Bindable<Size> sizeFullscreen;
|
||||
|
||||
private OsuGameBase game;
|
||||
private SettingsDropdown<Size> resolutionDropdown;
|
||||
private SettingsEnumDropdown<WindowMode> windowModeDropdown;
|
||||
|
||||
private const int transition_duration = 400;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(FrameworkConfigManager config)
|
||||
private void load(FrameworkConfigManager config, OsuGameBase game)
|
||||
{
|
||||
this.game = game;
|
||||
|
||||
letterboxing = config.GetBindable<bool>(FrameworkSetting.Letterboxing);
|
||||
sizeFullscreen = config.GetBindable<Size>(FrameworkSetting.SizeFullscreen);
|
||||
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new SettingsEnumDropdown<WindowMode>
|
||||
windowModeDropdown = new SettingsEnumDropdown<WindowMode>
|
||||
{
|
||||
LabelText = "Screen mode",
|
||||
Bindable = config.GetBindable<WindowMode>(FrameworkSetting.WindowMode),
|
||||
},
|
||||
resolutionDropdown = new SettingsDropdown<Size>
|
||||
{
|
||||
LabelText = "Resolution",
|
||||
ShowsDefaultIndicator = false,
|
||||
Items = getResolutions(),
|
||||
Bindable = sizeFullscreen
|
||||
},
|
||||
new SettingsCheckbox
|
||||
{
|
||||
LabelText = "Letterboxing",
|
||||
@ -62,15 +81,39 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
|
||||
},
|
||||
};
|
||||
|
||||
letterboxing.ValueChanged += isVisible =>
|
||||
windowModeDropdown.Bindable.BindValueChanged(windowMode =>
|
||||
{
|
||||
if (windowMode == WindowMode.Fullscreen)
|
||||
{
|
||||
resolutionDropdown.Show();
|
||||
sizeFullscreen.TriggerChange();
|
||||
}
|
||||
else
|
||||
resolutionDropdown.Hide();
|
||||
}, true);
|
||||
|
||||
letterboxing.BindValueChanged(isVisible =>
|
||||
{
|
||||
letterboxSettings.ClearTransforms();
|
||||
letterboxSettings.AutoSizeAxes = isVisible ? Axes.Y : Axes.None;
|
||||
|
||||
if (!isVisible)
|
||||
letterboxSettings.ResizeHeightTo(0, transition_duration, Easing.OutQuint);
|
||||
};
|
||||
letterboxing.TriggerChange();
|
||||
}, true);
|
||||
}
|
||||
|
||||
private IEnumerable<KeyValuePair<string, Size>> getResolutions()
|
||||
{
|
||||
var resolutions = new KeyValuePair<string, Size>("Default", new Size(9999, 9999)).Yield();
|
||||
|
||||
if (game.Window != null)
|
||||
resolutions = resolutions.Concat(game.Window.AvailableResolutions
|
||||
.Where(r => r.Width >= 800 && r.Height >= 600)
|
||||
.OrderByDescending(r => r.Width)
|
||||
.ThenByDescending(r => r.Height)
|
||||
.Select(res => new KeyValuePair<string, Size>($"{res.Width}x{res.Height}", new Size(res.Width, res.Height)))
|
||||
.Distinct()).ToList();
|
||||
return resolutions;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Skinning;
|
||||
|
||||
namespace osu.Game.Overlays.Settings.Sections.Maintenance
|
||||
{
|
||||
@ -20,7 +21,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
|
||||
protected override string Header => "General";
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(BeatmapManager beatmaps, DialogOverlay dialogOverlay)
|
||||
private void load(BeatmapManager beatmaps, SkinManager skins, DialogOverlay dialogOverlay)
|
||||
{
|
||||
Children = new Drawable[]
|
||||
{
|
||||
@ -30,7 +31,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
|
||||
Action = () =>
|
||||
{
|
||||
importButton.Enabled.Value = false;
|
||||
beatmaps.ImportFromStable().ContinueWith(t => Schedule(() => importButton.Enabled.Value = true));
|
||||
beatmaps.ImportFromStableAsync().ContinueWith(t => Schedule(() => importButton.Enabled.Value = true));
|
||||
}
|
||||
},
|
||||
deleteButton = new DangerousSettingsButton
|
||||
@ -45,6 +46,27 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
|
||||
}));
|
||||
}
|
||||
},
|
||||
importButton = new SettingsButton
|
||||
{
|
||||
Text = "Import skins from stable",
|
||||
Action = () =>
|
||||
{
|
||||
importButton.Enabled.Value = false;
|
||||
skins.ImportFromStableAsync().ContinueWith(t => Schedule(() => importButton.Enabled.Value = true));
|
||||
}
|
||||
},
|
||||
deleteButton = new DangerousSettingsButton
|
||||
{
|
||||
Text = "Delete ALL skins",
|
||||
Action = () =>
|
||||
{
|
||||
dialogOverlay?.Push(new DeleteAllBeatmapsDialog(() =>
|
||||
{
|
||||
deleteButton.Enabled.Value = false;
|
||||
Task.Run(() => skins.Delete(skins.GetAllUserSkins())).ContinueWith(t => Schedule(() => deleteButton.Enabled.Value = true));
|
||||
}));
|
||||
}
|
||||
},
|
||||
restoreButton = new SettingsButton
|
||||
{
|
||||
Text = "Restore all hidden difficulties",
|
||||
|
@ -51,10 +51,10 @@ namespace osu.Game.Overlays.Settings.Sections
|
||||
},
|
||||
};
|
||||
|
||||
skins.ItemAdded += onItemsChanged;
|
||||
skins.ItemRemoved += onItemsChanged;
|
||||
skins.ItemAdded += itemAdded;
|
||||
skins.ItemRemoved += itemRemoved;
|
||||
|
||||
reloadSkins();
|
||||
skinDropdown.Items = skins.GetAllUsableSkins().Select(s => new KeyValuePair<string, int>(s.ToString(), s.ID));
|
||||
|
||||
var skinBindable = config.GetBindable<int>(OsuSetting.Skin);
|
||||
|
||||
@ -65,9 +65,8 @@ namespace osu.Game.Overlays.Settings.Sections
|
||||
skinDropdown.Bindable = skinBindable;
|
||||
}
|
||||
|
||||
private void reloadSkins() => skinDropdown.Items = skins.GetAllUsableSkins().Select(s => new KeyValuePair<string, int>(s.ToString(), s.ID));
|
||||
|
||||
private void onItemsChanged(SkinInfo _) => Schedule(reloadSkins);
|
||||
private void itemRemoved(SkinInfo s) => skinDropdown.Items = skinDropdown.Items.Where(i => i.Value != s.ID);
|
||||
private void itemAdded(SkinInfo s) => skinDropdown.Items = skinDropdown.Items.Append(new KeyValuePair<string, int>(s.ToString(), s.ID));
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
@ -75,8 +74,8 @@ namespace osu.Game.Overlays.Settings.Sections
|
||||
|
||||
if (skins != null)
|
||||
{
|
||||
skins.ItemAdded -= onItemsChanged;
|
||||
skins.ItemRemoved -= onItemsChanged;
|
||||
skins.ItemAdded -= itemAdded;
|
||||
skins.ItemRemoved -= itemRemoved;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -73,7 +73,7 @@ namespace osu.Game.Overlays.Settings
|
||||
},
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Children = new[]
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new OsuSpriteText
|
||||
{
|
||||
|
@ -9,6 +9,7 @@ using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Framework.Input.States;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
@ -16,7 +17,7 @@ using osu.Game.Graphics.UserInterface;
|
||||
|
||||
namespace osu.Game.Overlays.Settings
|
||||
{
|
||||
public class SidebarButton : OsuButton
|
||||
public class SidebarButton : Button
|
||||
{
|
||||
private readonly SpriteIcon drawableIcon;
|
||||
private readonly SpriteText headerText;
|
||||
@ -97,7 +98,8 @@ namespace osu.Game.Overlays.Settings
|
||||
Width = 5,
|
||||
Anchor = Anchor.CentreRight,
|
||||
Origin = Anchor.CentreRight,
|
||||
}
|
||||
},
|
||||
new HoverClickSounds(HoverSampleSet.Loud),
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -83,14 +83,14 @@ namespace osu.Game.Overlays.Toolbar
|
||||
[BackgroundDependencyLoader(true)]
|
||||
private void load(OsuGame osuGame)
|
||||
{
|
||||
if (osuGame != null)
|
||||
overlayActivationMode.BindTo(osuGame.OverlayActivationMode);
|
||||
|
||||
StateChanged += visibility =>
|
||||
{
|
||||
if (overlayActivationMode == OverlayActivation.Disabled)
|
||||
State = Visibility.Hidden;
|
||||
};
|
||||
|
||||
if (osuGame != null)
|
||||
overlayActivationMode.BindTo(osuGame.OverlayActivationMode);
|
||||
}
|
||||
|
||||
public class ToolbarBackground : Container
|
||||
|
@ -73,16 +73,15 @@ namespace osu.Game.Overlays
|
||||
FadeEdgeEffectTo(0, WaveContainer.DISAPPEAR_DURATION, Easing.Out);
|
||||
}
|
||||
|
||||
public void ShowUser(long userId)
|
||||
{
|
||||
if (userId == Header.User.Id)
|
||||
return;
|
||||
|
||||
ShowUser(new User { Id = userId });
|
||||
}
|
||||
public void ShowUser(long userId) => ShowUser(new User { Id = userId });
|
||||
|
||||
public void ShowUser(User user, bool fetchOnline = true)
|
||||
{
|
||||
Show();
|
||||
|
||||
if (user.Id == Header?.User.Id)
|
||||
return;
|
||||
|
||||
userReq?.Cancel();
|
||||
Clear();
|
||||
lastSection = null;
|
||||
@ -97,6 +96,7 @@ namespace osu.Game.Overlays
|
||||
new BeatmapsSection(),
|
||||
new KudosuSection()
|
||||
};
|
||||
|
||||
tabs = new ProfileTabControl
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
@ -161,7 +161,6 @@ namespace osu.Game.Overlays
|
||||
userLoadComplete(user);
|
||||
}
|
||||
|
||||
Show();
|
||||
sectionsContainer.ScrollToTop();
|
||||
}
|
||||
|
||||
|
12
osu.Game/Rulesets/Mods/IUpdatableByPlayfield.cs
Normal file
12
osu.Game/Rulesets/Mods/IUpdatableByPlayfield.cs
Normal file
@ -0,0 +1,12 @@
|
||||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using osu.Game.Rulesets.UI;
|
||||
|
||||
namespace osu.Game.Rulesets.Mods
|
||||
{
|
||||
public interface IUpdatableByPlayfield : IApplicableMod
|
||||
{
|
||||
void Update(Playfield playfield);
|
||||
}
|
||||
}
|
@ -147,17 +147,15 @@ namespace osu.Game.Rulesets.Objects.Drawables
|
||||
/// </summary>
|
||||
public void PlaySamples() => Samples?.Play();
|
||||
|
||||
private double lastUpdateTime;
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
|
||||
if (Result != null && lastUpdateTime > Time.Current)
|
||||
if (Result != null && Result.HasResult)
|
||||
{
|
||||
var endTime = (HitObject as IHasEndTime)?.EndTime ?? HitObject.StartTime;
|
||||
|
||||
if (Result.TimeOffset + endTime < Time.Current)
|
||||
if (Result.TimeOffset + endTime > Time.Current)
|
||||
{
|
||||
OnRevertResult?.Invoke(this, Result);
|
||||
|
||||
@ -165,8 +163,6 @@ namespace osu.Game.Rulesets.Objects.Drawables
|
||||
State.Value = ArmedState.Idle;
|
||||
}
|
||||
}
|
||||
|
||||
lastUpdateTime = Time.Current;
|
||||
}
|
||||
|
||||
protected override void UpdateAfterChildren()
|
||||
|
@ -13,5 +13,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Catch
|
||||
public float X { get; set; }
|
||||
|
||||
public bool NewCombo { get; set; }
|
||||
|
||||
public int ComboOffset { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -13,21 +13,43 @@ namespace osu.Game.Rulesets.Objects.Legacy.Catch
|
||||
/// </summary>
|
||||
public class ConvertHitObjectParser : Legacy.ConvertHitObjectParser
|
||||
{
|
||||
protected override HitObject CreateHit(Vector2 position, bool newCombo)
|
||||
public ConvertHitObjectParser(double offset, int formatVersion)
|
||||
: base(offset, formatVersion)
|
||||
{
|
||||
}
|
||||
|
||||
private bool forceNewCombo;
|
||||
private int extraComboOffset;
|
||||
|
||||
protected override HitObject CreateHit(Vector2 position, bool newCombo, int comboOffset)
|
||||
{
|
||||
newCombo |= forceNewCombo;
|
||||
comboOffset += extraComboOffset;
|
||||
|
||||
forceNewCombo = false;
|
||||
extraComboOffset = 0;
|
||||
|
||||
return new ConvertHit
|
||||
{
|
||||
X = position.X,
|
||||
NewCombo = newCombo,
|
||||
ComboOffset = comboOffset
|
||||
};
|
||||
}
|
||||
|
||||
protected override HitObject CreateSlider(Vector2 position, bool newCombo, List<Vector2> controlPoints, double length, CurveType curveType, int repeatCount, List<List<SampleInfo>> repeatSamples)
|
||||
protected override HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, List<Vector2> controlPoints, double length, CurveType curveType, int repeatCount, List<List<SampleInfo>> repeatSamples)
|
||||
{
|
||||
newCombo |= forceNewCombo;
|
||||
comboOffset += extraComboOffset;
|
||||
|
||||
forceNewCombo = false;
|
||||
extraComboOffset = 0;
|
||||
|
||||
return new ConvertSlider
|
||||
{
|
||||
X = position.X,
|
||||
NewCombo = newCombo,
|
||||
NewCombo = FirstObject || newCombo,
|
||||
ComboOffset = comboOffset,
|
||||
ControlPoints = controlPoints,
|
||||
Distance = length,
|
||||
CurveType = curveType,
|
||||
@ -36,15 +58,20 @@ namespace osu.Game.Rulesets.Objects.Legacy.Catch
|
||||
};
|
||||
}
|
||||
|
||||
protected override HitObject CreateSpinner(Vector2 position, double endTime)
|
||||
protected override HitObject CreateSpinner(Vector2 position, bool newCombo, int comboOffset, double endTime)
|
||||
{
|
||||
// Convert spinners don't create the new combo themselves, but force the next non-spinner hitobject to create a new combo
|
||||
// Their combo offset is still added to that next hitobject's combo index
|
||||
forceNewCombo |= FormatVersion <= 8 || newCombo;
|
||||
extraComboOffset += comboOffset;
|
||||
|
||||
return new ConvertSpinner
|
||||
{
|
||||
EndTime = endTime
|
||||
};
|
||||
}
|
||||
|
||||
protected override HitObject CreateHold(Vector2 position, bool newCombo, double endTime)
|
||||
protected override HitObject CreateHold(Vector2 position, bool newCombo, int comboOffset, double endTime)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
@ -13,5 +13,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Catch
|
||||
public float X { get; set; }
|
||||
|
||||
public bool NewCombo { get; set; }
|
||||
|
||||
public int ComboOffset { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -8,10 +8,14 @@ namespace osu.Game.Rulesets.Objects.Legacy.Catch
|
||||
/// <summary>
|
||||
/// Legacy osu!catch Spinner-type, used for parsing Beatmaps.
|
||||
/// </summary>
|
||||
internal sealed class ConvertSpinner : HitObject, IHasEndTime
|
||||
internal sealed class ConvertSpinner : HitObject, IHasEndTime, IHasCombo
|
||||
{
|
||||
public double EndTime { get; set; }
|
||||
|
||||
public double Duration => EndTime - StartTime;
|
||||
|
||||
public bool NewCombo { get; set; }
|
||||
|
||||
public int ComboOffset { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -10,6 +10,8 @@ using System.IO;
|
||||
using osu.Game.Beatmaps.Formats;
|
||||
using osu.Game.Audio;
|
||||
using System.Linq;
|
||||
using JetBrains.Annotations;
|
||||
using osu.Framework.Logging;
|
||||
using osu.Framework.MathUtils;
|
||||
|
||||
namespace osu.Game.Rulesets.Objects.Legacy
|
||||
@ -19,12 +21,26 @@ namespace osu.Game.Rulesets.Objects.Legacy
|
||||
/// </summary>
|
||||
public abstract class ConvertHitObjectParser : HitObjectParser
|
||||
{
|
||||
public override HitObject Parse(string text)
|
||||
/// <summary>
|
||||
/// The offset to apply to all time values.
|
||||
/// </summary>
|
||||
protected readonly double Offset;
|
||||
|
||||
/// <summary>
|
||||
/// The beatmap version.
|
||||
/// </summary>
|
||||
protected readonly int FormatVersion;
|
||||
|
||||
protected bool FirstObject { get; private set; } = true;
|
||||
|
||||
protected ConvertHitObjectParser(double offset, int formatVersion)
|
||||
{
|
||||
return Parse(text, 0);
|
||||
Offset = offset;
|
||||
FormatVersion = formatVersion;
|
||||
}
|
||||
|
||||
public HitObject Parse(string text, double offset)
|
||||
[CanBeNull]
|
||||
public override HitObject Parse(string text)
|
||||
{
|
||||
try
|
||||
{
|
||||
@ -32,7 +48,11 @@ namespace osu.Game.Rulesets.Objects.Legacy
|
||||
|
||||
Vector2 pos = new Vector2((int)Convert.ToSingle(split[0], CultureInfo.InvariantCulture), (int)Convert.ToSingle(split[1], CultureInfo.InvariantCulture));
|
||||
|
||||
ConvertHitObjectType type = (ConvertHitObjectType)int.Parse(split[3]) & ~ConvertHitObjectType.ColourHax;
|
||||
ConvertHitObjectType type = (ConvertHitObjectType)int.Parse(split[3]);
|
||||
|
||||
int comboOffset = (int)(type & ConvertHitObjectType.ComboOffset) >> 4;
|
||||
type &= ~ConvertHitObjectType.ComboOffset;
|
||||
|
||||
bool combo = type.HasFlag(ConvertHitObjectType.NewCombo);
|
||||
type &= ~ConvertHitObjectType.NewCombo;
|
||||
|
||||
@ -43,7 +63,7 @@ namespace osu.Game.Rulesets.Objects.Legacy
|
||||
|
||||
if (type.HasFlag(ConvertHitObjectType.Circle))
|
||||
{
|
||||
result = CreateHit(pos, combo);
|
||||
result = CreateHit(pos, combo, comboOffset);
|
||||
|
||||
if (split.Length > 5)
|
||||
readCustomSampleBanks(split[5], bankInfo);
|
||||
@ -148,11 +168,11 @@ namespace osu.Game.Rulesets.Objects.Legacy
|
||||
for (int i = 0; i < nodes; i++)
|
||||
nodeSamples.Add(convertSoundType(nodeSoundTypes[i], nodeBankInfos[i]));
|
||||
|
||||
result = CreateSlider(pos, combo, points, length, curveType, repeatCount, nodeSamples);
|
||||
result = CreateSlider(pos, combo, comboOffset, points, length, curveType, repeatCount, nodeSamples);
|
||||
}
|
||||
else if (type.HasFlag(ConvertHitObjectType.Spinner))
|
||||
{
|
||||
result = CreateSpinner(new Vector2(512, 384) / 2, Convert.ToDouble(split[5], CultureInfo.InvariantCulture) + offset);
|
||||
result = CreateSpinner(new Vector2(512, 384) / 2, combo, comboOffset, Convert.ToDouble(split[5], CultureInfo.InvariantCulture) + Offset);
|
||||
|
||||
if (split.Length > 6)
|
||||
readCustomSampleBanks(split[6], bankInfo);
|
||||
@ -170,15 +190,20 @@ namespace osu.Game.Rulesets.Objects.Legacy
|
||||
readCustomSampleBanks(string.Join(":", ss.Skip(1)), bankInfo);
|
||||
}
|
||||
|
||||
result = CreateHold(pos, combo, endTime + offset);
|
||||
result = CreateHold(pos, combo, comboOffset, endTime + Offset);
|
||||
}
|
||||
|
||||
if (result == null)
|
||||
throw new InvalidOperationException($@"Unknown hit object type {type}.");
|
||||
{
|
||||
Logger.Log($"Unknown hit object type: {type}. Skipped.", level: LogLevel.Error);
|
||||
return null;
|
||||
}
|
||||
|
||||
result.StartTime = Convert.ToDouble(split[2], CultureInfo.InvariantCulture) + offset;
|
||||
result.StartTime = Convert.ToDouble(split[2], CultureInfo.InvariantCulture) + Offset;
|
||||
result.Samples = convertSoundType(soundType, bankInfo);
|
||||
|
||||
FirstObject = false;
|
||||
|
||||
return result;
|
||||
}
|
||||
catch (FormatException)
|
||||
@ -221,37 +246,42 @@ namespace osu.Game.Rulesets.Objects.Legacy
|
||||
/// </summary>
|
||||
/// <param name="position">The position of the hit object.</param>
|
||||
/// <param name="newCombo">Whether the hit object creates a new combo.</param>
|
||||
/// <param name="comboOffset">When starting a new combo, the offset of the new combo relative to the current one.</param>
|
||||
/// <returns>The hit object.</returns>
|
||||
protected abstract HitObject CreateHit(Vector2 position, bool newCombo);
|
||||
protected abstract HitObject CreateHit(Vector2 position, bool newCombo, int comboOffset);
|
||||
|
||||
/// <summary>
|
||||
/// Creats a legacy Slider-type hit object.
|
||||
/// </summary>
|
||||
/// <param name="position">The position of the hit object.</param>
|
||||
/// <param name="newCombo">Whether the hit object creates a new combo.</param>
|
||||
/// <param name="comboOffset">When starting a new combo, the offset of the new combo relative to the current one.</param>
|
||||
/// <param name="controlPoints">The slider control points.</param>
|
||||
/// <param name="length">The slider length.</param>
|
||||
/// <param name="curveType">The slider curve type.</param>
|
||||
/// <param name="repeatCount">The slider repeat count.</param>
|
||||
/// <param name="repeatSamples">The samples to be played when the repeat nodes are hit. This includes the head and tail of the slider.</param>
|
||||
/// <returns>The hit object.</returns>
|
||||
protected abstract HitObject CreateSlider(Vector2 position, bool newCombo, List<Vector2> controlPoints, double length, CurveType curveType, int repeatCount, List<List<SampleInfo>> repeatSamples);
|
||||
protected abstract HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, List<Vector2> controlPoints, double length, CurveType curveType, int repeatCount, List<List<SampleInfo>> repeatSamples);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a legacy Spinner-type hit object.
|
||||
/// </summary>
|
||||
/// <param name="position">The position of the hit object.</param>
|
||||
/// <param name="newCombo">Whether the hit object creates a new combo.</param>
|
||||
/// <param name="comboOffset">When starting a new combo, the offset of the new combo relative to the current one.</param>
|
||||
/// <param name="endTime">The spinner end time.</param>
|
||||
/// <returns>The hit object.</returns>
|
||||
protected abstract HitObject CreateSpinner(Vector2 position, double endTime);
|
||||
protected abstract HitObject CreateSpinner(Vector2 position, bool newCombo, int comboOffset, double endTime);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a legacy Hold-type hit object.
|
||||
/// </summary>
|
||||
/// <param name="position">The position of the hit object.</param>
|
||||
/// <param name="newCombo">Whether the hit object creates a new combo.</param>
|
||||
/// <param name="comboOffset">When starting a new combo, the offset of the new combo relative to the current one.</param>
|
||||
/// <param name="endTime">The hold end time.</param>
|
||||
protected abstract HitObject CreateHold(Vector2 position, bool newCombo, double endTime);
|
||||
protected abstract HitObject CreateHold(Vector2 position, bool newCombo, int comboOffset, double endTime);
|
||||
|
||||
private List<SampleInfo> convertSoundType(LegacySoundType type, SampleBankInfo bankInfo)
|
||||
{
|
||||
|
@ -12,7 +12,7 @@ namespace osu.Game.Rulesets.Objects.Legacy
|
||||
Slider = 1 << 1,
|
||||
NewCombo = 1 << 2,
|
||||
Spinner = 1 << 3,
|
||||
ColourHax = 112,
|
||||
ComboOffset = 1 << 4 | 1 << 5 | 1 << 6,
|
||||
Hold = 1 << 7
|
||||
}
|
||||
}
|
||||
|
@ -8,12 +8,10 @@ namespace osu.Game.Rulesets.Objects.Legacy.Mania
|
||||
/// <summary>
|
||||
/// Legacy osu!mania Hit-type, used for parsing Beatmaps.
|
||||
/// </summary>
|
||||
internal sealed class ConvertHit : HitObject, IHasXPosition, IHasCombo
|
||||
internal sealed class ConvertHit : HitObject, IHasXPosition
|
||||
{
|
||||
public float X { get; set; }
|
||||
|
||||
public bool NewCombo { get; set; }
|
||||
|
||||
protected override HitWindows CreateHitWindows() => null;
|
||||
}
|
||||
}
|
||||
|
@ -13,21 +13,24 @@ namespace osu.Game.Rulesets.Objects.Legacy.Mania
|
||||
/// </summary>
|
||||
public class ConvertHitObjectParser : Legacy.ConvertHitObjectParser
|
||||
{
|
||||
protected override HitObject CreateHit(Vector2 position, bool newCombo)
|
||||
public ConvertHitObjectParser(double offset, int formatVersion)
|
||||
: base(offset, formatVersion)
|
||||
{
|
||||
}
|
||||
|
||||
protected override HitObject CreateHit(Vector2 position, bool newCombo, int comboOffset)
|
||||
{
|
||||
return new ConvertHit
|
||||
{
|
||||
X = position.X,
|
||||
NewCombo = newCombo,
|
||||
X = position.X
|
||||
};
|
||||
}
|
||||
|
||||
protected override HitObject CreateSlider(Vector2 position, bool newCombo, List<Vector2> controlPoints, double length, CurveType curveType, int repeatCount, List<List<SampleInfo>> repeatSamples)
|
||||
protected override HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, List<Vector2> controlPoints, double length, CurveType curveType, int repeatCount, List<List<SampleInfo>> repeatSamples)
|
||||
{
|
||||
return new ConvertSlider
|
||||
{
|
||||
X = position.X,
|
||||
NewCombo = newCombo,
|
||||
ControlPoints = controlPoints,
|
||||
Distance = length,
|
||||
CurveType = curveType,
|
||||
@ -36,7 +39,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Mania
|
||||
};
|
||||
}
|
||||
|
||||
protected override HitObject CreateSpinner(Vector2 position, double endTime)
|
||||
protected override HitObject CreateSpinner(Vector2 position, bool newCombo, int comboOffset, double endTime)
|
||||
{
|
||||
return new ConvertSpinner
|
||||
{
|
||||
@ -45,7 +48,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Mania
|
||||
};
|
||||
}
|
||||
|
||||
protected override HitObject CreateHold(Vector2 position, bool newCombo, double endTime)
|
||||
protected override HitObject CreateHold(Vector2 position, bool newCombo, int comboOffset, double endTime)
|
||||
{
|
||||
return new ConvertHold
|
||||
{
|
||||
|
@ -8,12 +8,10 @@ namespace osu.Game.Rulesets.Objects.Legacy.Mania
|
||||
/// <summary>
|
||||
/// Legacy osu!mania Slider-type, used for parsing Beatmaps.
|
||||
/// </summary>
|
||||
internal sealed class ConvertSlider : Legacy.ConvertSlider, IHasXPosition, IHasCombo
|
||||
internal sealed class ConvertSlider : Legacy.ConvertSlider, IHasXPosition
|
||||
{
|
||||
public float X { get; set; }
|
||||
|
||||
public bool NewCombo { get; set; }
|
||||
|
||||
protected override HitWindows CreateHitWindows() => null;
|
||||
}
|
||||
}
|
||||
|
@ -19,6 +19,8 @@ namespace osu.Game.Rulesets.Objects.Legacy.Osu
|
||||
|
||||
public bool NewCombo { get; set; }
|
||||
|
||||
public int ComboOffset { get; set; }
|
||||
|
||||
protected override HitWindows CreateHitWindows() => null;
|
||||
}
|
||||
}
|
||||
|
@ -14,21 +14,43 @@ namespace osu.Game.Rulesets.Objects.Legacy.Osu
|
||||
/// </summary>
|
||||
public class ConvertHitObjectParser : Legacy.ConvertHitObjectParser
|
||||
{
|
||||
protected override HitObject CreateHit(Vector2 position, bool newCombo)
|
||||
public ConvertHitObjectParser(double offset, int formatVersion)
|
||||
: base(offset, formatVersion)
|
||||
{
|
||||
}
|
||||
|
||||
private bool forceNewCombo;
|
||||
private int extraComboOffset;
|
||||
|
||||
protected override HitObject CreateHit(Vector2 position, bool newCombo, int comboOffset)
|
||||
{
|
||||
newCombo |= forceNewCombo;
|
||||
comboOffset += extraComboOffset;
|
||||
|
||||
forceNewCombo = false;
|
||||
extraComboOffset = 0;
|
||||
|
||||
return new ConvertHit
|
||||
{
|
||||
Position = position,
|
||||
NewCombo = newCombo,
|
||||
NewCombo = FirstObject || newCombo,
|
||||
ComboOffset = comboOffset
|
||||
};
|
||||
}
|
||||
|
||||
protected override HitObject CreateSlider(Vector2 position, bool newCombo, List<Vector2> controlPoints, double length, CurveType curveType, int repeatCount, List<List<SampleInfo>> repeatSamples)
|
||||
protected override HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, List<Vector2> controlPoints, double length, CurveType curveType, int repeatCount, List<List<SampleInfo>> repeatSamples)
|
||||
{
|
||||
newCombo |= forceNewCombo;
|
||||
comboOffset += extraComboOffset;
|
||||
|
||||
forceNewCombo = false;
|
||||
extraComboOffset = 0;
|
||||
|
||||
return new ConvertSlider
|
||||
{
|
||||
Position = position,
|
||||
NewCombo = newCombo,
|
||||
NewCombo = FirstObject || newCombo,
|
||||
ComboOffset = comboOffset,
|
||||
ControlPoints = controlPoints,
|
||||
Distance = Math.Max(0, length),
|
||||
CurveType = curveType,
|
||||
@ -37,8 +59,13 @@ namespace osu.Game.Rulesets.Objects.Legacy.Osu
|
||||
};
|
||||
}
|
||||
|
||||
protected override HitObject CreateSpinner(Vector2 position, double endTime)
|
||||
protected override HitObject CreateSpinner(Vector2 position, bool newCombo, int comboOffset, double endTime)
|
||||
{
|
||||
// Convert spinners don't create the new combo themselves, but force the next non-spinner hitobject to create a new combo
|
||||
// Their combo offset is still added to that next hitobject's combo index
|
||||
forceNewCombo |= FormatVersion <= 8 || newCombo;
|
||||
extraComboOffset += comboOffset;
|
||||
|
||||
return new ConvertSpinner
|
||||
{
|
||||
Position = position,
|
||||
@ -46,7 +73,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Osu
|
||||
};
|
||||
}
|
||||
|
||||
protected override HitObject CreateHold(Vector2 position, bool newCombo, double endTime)
|
||||
protected override HitObject CreateHold(Vector2 position, bool newCombo, int comboOffset, double endTime)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
@ -19,6 +19,8 @@ namespace osu.Game.Rulesets.Objects.Legacy.Osu
|
||||
|
||||
public bool NewCombo { get; set; }
|
||||
|
||||
public int ComboOffset { get; set; }
|
||||
|
||||
protected override HitWindows CreateHitWindows() => null;
|
||||
}
|
||||
}
|
||||
|
@ -9,7 +9,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Osu
|
||||
/// <summary>
|
||||
/// Legacy osu! Spinner-type, used for parsing Beatmaps.
|
||||
/// </summary>
|
||||
internal sealed class ConvertSpinner : HitObject, IHasEndTime, IHasPosition
|
||||
internal sealed class ConvertSpinner : HitObject, IHasEndTime, IHasPosition, IHasCombo
|
||||
{
|
||||
public double EndTime { get; set; }
|
||||
|
||||
@ -22,5 +22,9 @@ namespace osu.Game.Rulesets.Objects.Legacy.Osu
|
||||
public float Y => Position.Y;
|
||||
|
||||
protected override HitWindows CreateHitWindows() => null;
|
||||
|
||||
public bool NewCombo { get; set; }
|
||||
|
||||
public int ComboOffset { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -1,17 +1,13 @@
|
||||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
|
||||
namespace osu.Game.Rulesets.Objects.Legacy.Taiko
|
||||
{
|
||||
/// <summary>
|
||||
/// Legacy osu!taiko Hit-type, used for parsing Beatmaps.
|
||||
/// </summary>
|
||||
internal sealed class ConvertHit : HitObject, IHasCombo
|
||||
internal sealed class ConvertHit : HitObject
|
||||
{
|
||||
public bool NewCombo { get; set; }
|
||||
|
||||
protected override HitWindows CreateHitWindows() => null;
|
||||
}
|
||||
}
|
||||
|
@ -13,19 +13,20 @@ namespace osu.Game.Rulesets.Objects.Legacy.Taiko
|
||||
/// </summary>
|
||||
public class ConvertHitObjectParser : Legacy.ConvertHitObjectParser
|
||||
{
|
||||
protected override HitObject CreateHit(Vector2 position, bool newCombo)
|
||||
public ConvertHitObjectParser(double offset, int formatVersion)
|
||||
: base(offset, formatVersion)
|
||||
{
|
||||
return new ConvertHit
|
||||
{
|
||||
NewCombo = newCombo,
|
||||
};
|
||||
}
|
||||
|
||||
protected override HitObject CreateSlider(Vector2 position, bool newCombo, List<Vector2> controlPoints, double length, CurveType curveType, int repeatCount, List<List<SampleInfo>> repeatSamples)
|
||||
protected override HitObject CreateHit(Vector2 position, bool newCombo, int comboOffset)
|
||||
{
|
||||
return new ConvertHit();
|
||||
}
|
||||
|
||||
protected override HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, List<Vector2> controlPoints, double length, CurveType curveType, int repeatCount, List<List<SampleInfo>> repeatSamples)
|
||||
{
|
||||
return new ConvertSlider
|
||||
{
|
||||
NewCombo = newCombo,
|
||||
ControlPoints = controlPoints,
|
||||
Distance = length,
|
||||
CurveType = curveType,
|
||||
@ -34,7 +35,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Taiko
|
||||
};
|
||||
}
|
||||
|
||||
protected override HitObject CreateSpinner(Vector2 position, double endTime)
|
||||
protected override HitObject CreateSpinner(Vector2 position, bool newCombo, int comboOffset, double endTime)
|
||||
{
|
||||
return new ConvertSpinner
|
||||
{
|
||||
@ -42,7 +43,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Taiko
|
||||
};
|
||||
}
|
||||
|
||||
protected override HitObject CreateHold(Vector2 position, bool newCombo, double endTime)
|
||||
protected override HitObject CreateHold(Vector2 position, bool newCombo, int comboOffset, double endTime)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
@ -1,17 +1,13 @@
|
||||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
|
||||
namespace osu.Game.Rulesets.Objects.Legacy.Taiko
|
||||
{
|
||||
/// <summary>
|
||||
/// Legacy osu!taiko Slider-type, used for parsing Beatmaps.
|
||||
/// </summary>
|
||||
internal sealed class ConvertSlider : Legacy.ConvertSlider, IHasCombo
|
||||
internal sealed class ConvertSlider : Legacy.ConvertSlider
|
||||
{
|
||||
public bool NewCombo { get; set; }
|
||||
|
||||
protected override HitWindows CreateHitWindows() => null;
|
||||
}
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user