1
0
mirror of https://github.com/ppy/osu.git synced 2024-12-15 10:42:54 +08:00

Merge branch 'master' of github.com:WebFreak001/osu into mod-fl2

This commit is contained in:
WebFreak001 2018-09-15 20:12:06 +02:00
commit 040a44d42c
237 changed files with 3617 additions and 1797 deletions

View File

@ -24,7 +24,9 @@ Clone the repository including submodules
Build and run Build and run
- Using Visual Studio 2017, Rider or Visual Studio Code (configurations are included) - 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. 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.

View File

@ -4,7 +4,11 @@
using System; using System;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using osu.Framework; using osu.Framework;
using osu.Framework.Development;
using osu.Framework.Logging;
using osu.Framework.Platform; using osu.Framework.Platform;
using osu.Game.IPC; using osu.Game.IPC;
@ -20,6 +24,8 @@ namespace osu.Desktop
using (DesktopGameHost host = Host.GetSuitableHost(@"osu", true)) using (DesktopGameHost host = Host.GetSuitableHost(@"osu", true))
{ {
host.ExceptionThrown += handleException;
if (!host.IsPrimaryInstance) if (!host.IsPrimaryInstance)
{ {
var importer = new ArchiveImportIPCChannel(host); var importer = new ArchiveImportIPCChannel(host);
@ -45,5 +51,24 @@ namespace osu.Desktop
return 0; 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;
}
} }
} }

View File

@ -27,9 +27,9 @@
</ItemGroup> </ItemGroup>
<ItemGroup Label="Package References"> <ItemGroup Label="Package References">
<PackageReference Include="System.IO.Packaging" Version="4.5.0" /> <PackageReference Include="System.IO.Packaging" Version="4.5.0" />
<PackageReference Include="ppy.squirrel.windows" Version="1.8.0.3" /> <PackageReference Include="ppy.squirrel.windows" Version="1.8.0.6" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.1" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.3" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="2.1.1" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="2.1.3" />
</ItemGroup> </ItemGroup>
<ItemGroup Label="Resources"> <ItemGroup Label="Resources">
<EmbeddedResource Include="lazer.ico" /> <EmbeddedResource Include="lazer.ico" />

View File

@ -41,6 +41,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
RepeatCount = curveData.RepeatCount, RepeatCount = curveData.RepeatCount,
X = (positionData?.X ?? 0) / CatchPlayfield.BASE_WIDTH, X = (positionData?.X ?? 0) / CatchPlayfield.BASE_WIDTH,
NewCombo = comboData?.NewCombo ?? false, NewCombo = comboData?.NewCombo ?? false,
ComboOffset = comboData?.ComboOffset ?? 0,
LegacyLastTickOffset = legacyOffset?.LegacyLastTickOffset ?? 0 LegacyLastTickOffset = legacyOffset?.LegacyLastTickOffset ?? 0
}; };
} }
@ -51,7 +52,8 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
StartTime = obj.StartTime, StartTime = obj.StartTime,
Samples = obj.Samples, Samples = obj.Samples,
Duration = endTime.Duration, Duration = endTime.Duration,
NewCombo = comboData?.NewCombo ?? false NewCombo = comboData?.NewCombo ?? false,
ComboOffset = comboData?.ComboOffset ?? 0,
}; };
} }
else else
@ -61,6 +63,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
StartTime = obj.StartTime, StartTime = obj.StartTime,
Samples = obj.Samples, Samples = obj.Samples,
NewCombo = comboData?.NewCombo ?? false, NewCombo = comboData?.NewCombo ?? false,
ComboOffset = comboData?.ComboOffset ?? 0,
X = (positionData?.X ?? 0) / CatchPlayfield.BASE_WIDTH X = (positionData?.X ?? 0) / CatchPlayfield.BASE_WIDTH
}; };
} }

View File

@ -74,42 +74,42 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
private void initialiseHyperDash(List<CatchHitObject> objects) private void initialiseHyperDash(List<CatchHitObject> objects)
{ {
// todo: add difficulty adjust. List<CatchHitObject> objectWithDroplets = new List<CatchHitObject>();
double halfCatcherWidth = CatcherArea.CATCHER_SIZE * (objects.FirstOrDefault()?.Scale ?? 1) / CatchPlayfield.BASE_WIDTH / 2;
foreach (var currentObject in objects)
{
if (currentObject is Fruit)
objectWithDroplets.Add(currentObject);
if (currentObject is JuiceStream)
foreach (var currentJuiceElement in currentObject.NestedHitObjects)
if (!(currentJuiceElement is TinyDroplet))
objectWithDroplets.Add((CatchHitObject)currentJuiceElement);
}
objectWithDroplets.Sort((h1, h2) => h1.StartTime.CompareTo(h2.StartTime));
double halfCatcherWidth = CatcherArea.GetCatcherSize(Beatmap.BeatmapInfo.BaseDifficulty) / 2;
int lastDirection = 0; int lastDirection = 0;
double lastExcess = halfCatcherWidth; double lastExcess = halfCatcherWidth;
int objCount = objects.Count; for (int i = 0; i < objectWithDroplets.Count - 1; i++)
for (int i = 0; i < objCount - 1; i++)
{ {
CatchHitObject currentObject = objects[i]; CatchHitObject currentObject = objectWithDroplets[i];
CatchHitObject nextObject = objectWithDroplets[i + 1];
// not needed?
// if (currentObject is TinyDroplet) continue;
CatchHitObject nextObject = objects[i + 1];
// while (nextObject is TinyDroplet)
// {
// if (++i == objCount - 1) break;
// nextObject = objects[i + 1];
// }
int thisDirection = nextObject.X > currentObject.X ? 1 : -1; int thisDirection = nextObject.X > currentObject.X ? 1 : -1;
double timeToNext = nextObject.StartTime - ((currentObject as IHasEndTime)?.EndTime ?? currentObject.StartTime) - 4; double timeToNext = nextObject.StartTime - currentObject.StartTime;
double distanceToNext = Math.Abs(nextObject.X - currentObject.X) - (lastDirection == thisDirection ? lastExcess : halfCatcherWidth); double distanceToNext = Math.Abs(nextObject.X - currentObject.X) - (lastDirection == thisDirection ? lastExcess : halfCatcherWidth);
float distanceToHyper = (float)(timeToNext * CatcherArea.Catcher.BASE_SPEED - distanceToNext);
if (timeToNext * CatcherArea.Catcher.BASE_SPEED < distanceToNext) if (distanceToHyper < 0)
{ {
currentObject.HyperDashTarget = nextObject; currentObject.HyperDashTarget = nextObject;
lastExcess = halfCatcherWidth; lastExcess = halfCatcherWidth;
} }
else else
{ {
//currentObject.DistanceToHyperDash = timeToNext - distanceToNext; currentObject.DistanceToHyperDash = distanceToHyper;
lastExcess = MathHelper.Clamp(timeToNext - distanceToNext, 0, halfCatcherWidth); lastExcess = MathHelper.Clamp(distanceToHyper, 0, halfCatcherWidth);
} }
lastDirection = thisDirection; lastDirection = thisDirection;

View File

@ -1,6 +1,7 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Catch.Judgements namespace osu.Game.Rulesets.Catch.Judgements
@ -9,8 +10,6 @@ namespace osu.Game.Rulesets.Catch.Judgements
{ {
public override bool AffectsCombo => false; public override bool AffectsCombo => false;
public override bool ShouldExplode => true;
protected override int NumericResultFor(HitResult result) protected override int NumericResultFor(HitResult result)
{ {
switch (result) switch (result)
@ -32,5 +31,7 @@ namespace osu.Game.Rulesets.Catch.Judgements
return 8; return 8;
} }
} }
public override bool ShouldExplodeFor(JudgementResult result) => true;
} }
} }

View File

@ -23,21 +23,10 @@ namespace osu.Game.Rulesets.Catch.Judgements
} }
/// <summary> /// <summary>
/// The base health increase for the result achieved. /// Retrieves the numeric health increase of a <see cref="HitResult"/>.
/// </summary> /// </summary>
public float HealthIncrease => HealthIncreaseFor(Result); /// <param name="result">The <see cref="HitResult"/> to find the numeric health increase for.</param>
/// <returns>The numeric health increase of <paramref name="result"/>.</returns>
/// <summary>
/// Whether fruit on the platter should explode or drop.
/// Note that this is only checked if the owning object is also <see cref="IHasComboInformation.LastInCombo" />
/// </summary>
public virtual bool ShouldExplode => IsHit;
/// <summary>
/// Convert a <see cref="HitResult"/> to a base health increase.
/// </summary>
/// <param name="result">The value to convert.</param>
/// <returns>The base health increase.</returns>
protected virtual float HealthIncreaseFor(HitResult result) protected virtual float HealthIncreaseFor(HitResult result)
{ {
switch (result) switch (result)
@ -48,5 +37,18 @@ namespace osu.Game.Rulesets.Catch.Judgements
return 10.2f; return 10.2f;
} }
} }
/// <summary>
/// Retrieves the numeric health increase of a <see cref="JudgementResult"/>.
/// </summary>
/// <param name="result">The <see cref="JudgementResult"/> to find the numeric health increase for.</param>
/// <returns>The numeric health increase of <paramref name="result"/>.</returns>
public float HealthIncreaseFor(JudgementResult result) => HealthIncreaseFor(result.Type);
/// <summary>
/// Whether fruit on the platter should explode or drop.
/// Note that this is only checked if the owning object is also <see cref="IHasComboInformation.LastInCombo" />
/// </summary>
public virtual bool ShouldExplodeFor(JudgementResult result) => result.IsHit;
} }
} }

View File

@ -1,10 +1,15 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Rulesets.Catch.Judgements;
using osu.Game.Rulesets.Judgements;
namespace osu.Game.Rulesets.Catch.Objects namespace osu.Game.Rulesets.Catch.Objects
{ {
public class Banana : Fruit public class Banana : Fruit
{ {
public override FruitVisualRepresentation VisualRepresentation => FruitVisualRepresentation.Banana; public override FruitVisualRepresentation VisualRepresentation => FruitVisualRepresentation.Banana;
public override Judgement CreateJudgement() => new CatchBananaJudgement();
} }
} }

View File

@ -20,12 +20,16 @@ namespace osu.Game.Rulesets.Catch.Objects
public virtual bool NewCombo { get; set; } public virtual bool NewCombo { get; set; }
public int ComboOffset { get; set; }
public int IndexInCurrentCombo { get; set; } public int IndexInCurrentCombo { get; set; }
public int ComboIndex { get; set; } public int ComboIndex { get; set; }
/// <summary> /// <summary>
/// The distance for a fruit to to next hyper if it's not a hyper. /// Difference between the distance to the next object
/// and the distance that would have triggered a hyper dash.
/// A value close to 0 indicates a difficult jump (for difficulty calculation).
/// </summary> /// </summary>
public float DistanceToHyperDash { get; set; } public float DistanceToHyperDash { get; set; }

View File

@ -1,8 +1,6 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Rulesets.Catch.Judgements;
namespace osu.Game.Rulesets.Catch.Objects.Drawable namespace osu.Game.Rulesets.Catch.Objects.Drawable
{ {
public class DrawableBanana : DrawableFruit public class DrawableBanana : DrawableFruit
@ -11,7 +9,5 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
: base(h) : base(h)
{ {
} }
protected override CatchJudgement CreateJudgement() => new CatchBananaJudgement();
} }
} }

View File

@ -26,8 +26,6 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
AddNested(getVisualRepresentation?.Invoke(b)); AddNested(getVisualRepresentation?.Invoke(b));
} }
protected override bool ProvidesJudgement => false;
protected override void AddNested(DrawableHitObject h) protected override void AddNested(DrawableHitObject h)
{ {
((DrawableCatchHitObject)h).CheckPosition = o => CheckPosition?.Invoke(o) ?? false; ((DrawableCatchHitObject)h).CheckPosition = o => CheckPosition?.Invoke(o) ?? false;

View File

@ -5,7 +5,6 @@ using System;
using OpenTK; using OpenTK;
using OpenTK.Graphics; using OpenTK.Graphics;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Game.Rulesets.Catch.Judgements;
using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
@ -53,20 +52,14 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
public Func<CatchHitObject, bool> CheckPosition; public Func<CatchHitObject, bool> CheckPosition;
protected override void CheckForJudgements(bool userTriggered, double timeOffset) protected override void CheckForResult(bool userTriggered, double timeOffset)
{ {
if (CheckPosition == null) return; if (CheckPosition == null) return;
if (timeOffset >= 0) if (timeOffset >= 0 && Result != null)
{ ApplyResult(r => r.Type = CheckPosition.Invoke(HitObject) ? HitResult.Perfect : HitResult.Miss);
var judgement = CreateJudgement();
judgement.Result = CheckPosition.Invoke(HitObject) ? HitResult.Perfect : HitResult.Miss;
AddJudgement(judgement);
}
} }
protected virtual CatchJudgement CreateJudgement() => new CatchJudgement();
protected override void SkinChanged(ISkinSource skin, bool allowFallback) protected override void SkinChanged(ISkinSource skin, bool allowFallback)
{ {
base.SkinChanged(skin, allowFallback); base.SkinChanged(skin, allowFallback);

View File

@ -6,7 +6,6 @@ using osu.Framework.Graphics;
using osu.Game.Rulesets.Catch.Objects.Drawable.Pieces; using osu.Game.Rulesets.Catch.Objects.Drawable.Pieces;
using OpenTK; using OpenTK;
using OpenTK.Graphics; using OpenTK.Graphics;
using osu.Game.Rulesets.Catch.Judgements;
namespace osu.Game.Rulesets.Catch.Objects.Drawable namespace osu.Game.Rulesets.Catch.Objects.Drawable
{ {
@ -24,8 +23,6 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
Masking = false; Masking = false;
} }
protected override CatchJudgement CreateJudgement() => new CatchDropletJudgement();
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load() private void load()
{ {

View File

@ -26,8 +26,6 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
AddNested(getVisualRepresentation?.Invoke(o)); AddNested(getVisualRepresentation?.Invoke(o));
} }
protected override bool ProvidesJudgement => false;
protected override void AddNested(DrawableHitObject h) protected override void AddNested(DrawableHitObject h)
{ {
var catchObject = (DrawableCatchHitObject)h; var catchObject = (DrawableCatchHitObject)h;

View File

@ -2,18 +2,15 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using OpenTK; using OpenTK;
using osu.Game.Rulesets.Catch.Judgements;
namespace osu.Game.Rulesets.Catch.Objects.Drawable namespace osu.Game.Rulesets.Catch.Objects.Drawable
{ {
public class DrawableTinyDroplet : DrawableDroplet public class DrawableTinyDroplet : DrawableDroplet
{ {
public DrawableTinyDroplet(Droplet h) public DrawableTinyDroplet(TinyDroplet h)
: base(h) : base(h)
{ {
Size = new Vector2((float)CatchHitObject.OBJECT_RADIUS) / 8; Size = new Vector2((float)CatchHitObject.OBJECT_RADIUS) / 8;
} }
protected override CatchJudgement CreateJudgement() => new CatchTinyDropletJudgement();
} }
} }

View File

@ -1,9 +1,13 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Rulesets.Catch.Judgements;
using osu.Game.Rulesets.Judgements;
namespace osu.Game.Rulesets.Catch.Objects namespace osu.Game.Rulesets.Catch.Objects
{ {
public class Droplet : CatchHitObject public class Droplet : CatchHitObject
{ {
public override Judgement CreateJudgement() => new CatchDropletJudgement();
} }
} }

View File

@ -1,9 +1,13 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Rulesets.Catch.Judgements;
using osu.Game.Rulesets.Judgements;
namespace osu.Game.Rulesets.Catch.Objects namespace osu.Game.Rulesets.Catch.Objects
{ {
public class Fruit : CatchHitObject public class Fruit : CatchHitObject
{ {
public override Judgement CreateJudgement() => new CatchJudgement();
} }
} }

View File

@ -1,9 +1,13 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Rulesets.Catch.Judgements;
using osu.Game.Rulesets.Judgements;
namespace osu.Game.Rulesets.Catch.Objects namespace osu.Game.Rulesets.Catch.Objects
{ {
public class TinyDroplet : Droplet public class TinyDroplet : Droplet
{ {
public override Judgement CreateJudgement() => new CatchTinyDropletJudgement();
} }
} }

View File

@ -2,7 +2,6 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System; using System;
using System.Linq;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Rulesets.Catch.Judgements; using osu.Game.Rulesets.Catch.Judgements;
using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.Objects;
@ -21,55 +20,28 @@ namespace osu.Game.Rulesets.Catch.Scoring
private float hpDrainRate; private float hpDrainRate;
protected override void SimulateAutoplay(Beatmap<CatchHitObject> beatmap) protected override void ApplyBeatmap(Beatmap<CatchHitObject> beatmap)
{ {
hpDrainRate = beatmap.BeatmapInfo.BaseDifficulty.DrainRate; base.ApplyBeatmap(beatmap);
foreach (var obj in beatmap.HitObjects) hpDrainRate = beatmap.BeatmapInfo.BaseDifficulty.DrainRate;
{
switch (obj)
{
case JuiceStream stream:
foreach (var nestedObject in stream.NestedHitObjects)
switch (nestedObject)
{
case TinyDroplet _:
AddJudgement(new CatchTinyDropletJudgement { Result = HitResult.Perfect });
break;
case Droplet _:
AddJudgement(new CatchDropletJudgement { Result = HitResult.Perfect });
break;
case Fruit _:
AddJudgement(new CatchJudgement { Result = HitResult.Perfect });
break;
}
break;
case BananaShower shower:
foreach (var _ in shower.NestedHitObjects.Cast<CatchHitObject>())
AddJudgement(new CatchBananaJudgement { Result = HitResult.Perfect });
break;
case Fruit _:
AddJudgement(new CatchJudgement { Result = HitResult.Perfect });
break;
}
}
} }
private const double harshness = 0.01; private const double harshness = 0.01;
protected override void OnNewJudgement(Judgement judgement) protected override void ApplyResult(JudgementResult result)
{ {
base.OnNewJudgement(judgement); base.ApplyResult(result);
if (judgement.Result == HitResult.Miss) if (result.Type == HitResult.Miss)
{ {
if (!judgement.IsBonus) if (!result.Judgement.IsBonus)
Health.Value -= hpDrainRate * (harshness * 2); Health.Value -= hpDrainRate * (harshness * 2);
return; return;
} }
if (judgement is CatchJudgement catchJudgement) if (result.Judgement is CatchJudgement catchJudgement)
Health.Value += Math.Max(catchJudgement.HealthIncrease - hpDrainRate, 0) * harshness; Health.Value += Math.Max(catchJudgement.HealthIncreaseFor(result) - hpDrainRate, 0) * harshness;
} }
} }
} }

View File

@ -22,6 +22,8 @@ namespace osu.Game.Rulesets.Catch.UI
private readonly CatcherArea catcherArea; private readonly CatcherArea catcherArea;
protected override bool UserScrollSpeedAdjustment => false;
public CatchPlayfield(BeatmapDifficulty difficulty, Func<CatchHitObject, DrawableHitObject<CatchHitObject>> getVisualRepresentation) public CatchPlayfield(BeatmapDifficulty difficulty, Func<CatchHitObject, DrawableHitObject<CatchHitObject>> getVisualRepresentation)
: base(BASE_WIDTH) : base(BASE_WIDTH)
{ {
@ -59,7 +61,7 @@ namespace osu.Game.Rulesets.Catch.UI
public override void Add(DrawableHitObject h) public override void Add(DrawableHitObject h)
{ {
h.OnJudgement += onJudgement; h.OnNewResult += onNewResult;
base.Add(h); base.Add(h);
@ -67,6 +69,7 @@ namespace osu.Game.Rulesets.Catch.UI
fruit.CheckPosition = CheckIfWeCanCatch; fruit.CheckPosition = CheckIfWeCanCatch;
} }
private void onJudgement(DrawableHitObject judgedObject, Judgement judgement) => catcherArea.OnJudgement((DrawableCatchHitObject)judgedObject, judgement); private void onNewResult(DrawableHitObject judgedObject, JudgementResult result)
=> catcherArea.OnResult((DrawableCatchHitObject)judgedObject, result);
} }
} }

View File

@ -48,7 +48,7 @@ namespace osu.Game.Rulesets.Catch.UI
private DrawableCatchHitObject lastPlateableFruit; private DrawableCatchHitObject lastPlateableFruit;
public void OnJudgement(DrawableCatchHitObject fruit, Judgement judgement) public void OnResult(DrawableCatchHitObject fruit, JudgementResult result)
{ {
void runAfterLoaded(Action action) void runAfterLoaded(Action action)
{ {
@ -63,7 +63,7 @@ namespace osu.Game.Rulesets.Catch.UI
lastPlateableFruit.OnLoadComplete = _ => action(); lastPlateableFruit.OnLoadComplete = _ => action();
} }
if (judgement.IsHit && fruit.CanBePlated) if (result.IsHit && fruit.CanBePlated)
{ {
var caughtFruit = (DrawableCatchHitObject)GetVisualRepresentation?.Invoke(fruit.HitObject); var caughtFruit = (DrawableCatchHitObject)GetVisualRepresentation?.Invoke(fruit.HitObject);
@ -86,7 +86,7 @@ namespace osu.Game.Rulesets.Catch.UI
if (fruit.HitObject.LastInCombo) if (fruit.HitObject.LastInCombo)
{ {
if (((CatchJudgement)judgement).ShouldExplode) if (((CatchJudgement)result.Judgement).ShouldExplodeFor(result))
runAfterLoaded(() => MovableCatcher.Explode()); runAfterLoaded(() => MovableCatcher.Explode());
else else
MovableCatcher.Drop(); MovableCatcher.Drop();
@ -107,6 +107,11 @@ namespace osu.Game.Rulesets.Catch.UI
public bool AttemptCatch(CatchHitObject obj) => MovableCatcher.AttemptCatch(obj); public bool AttemptCatch(CatchHitObject obj) => MovableCatcher.AttemptCatch(obj);
public static float GetCatcherSize(BeatmapDifficulty difficulty)
{
return CATCHER_SIZE / CatchPlayfield.BASE_WIDTH * (1.0f - 0.7f * (difficulty.CircleSize - 5) / 5);
}
public class Catcher : Container, IKeyBindingHandler<CatchAction> public class Catcher : Container, IKeyBindingHandler<CatchAction>
{ {
/// <summary> /// <summary>
@ -407,9 +412,7 @@ namespace osu.Game.Rulesets.Catch.UI
/// </summary> /// </summary>
public void Explode() public void Explode()
{ {
var fruit = caughtFruit.ToArray(); foreach (var f in caughtFruit.ToArray())
foreach (var f in fruit)
Explode(f); Explode(f);
} }
@ -422,15 +425,15 @@ namespace osu.Game.Rulesets.Catch.UI
fruit.Anchor = Anchor.TopLeft; fruit.Anchor = Anchor.TopLeft;
fruit.Position = caughtFruit.ToSpaceOfOtherDrawable(fruit.DrawPosition, ExplodingFruitTarget); 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); ExplodingFruitTarget.Add(fruit);
} }
fruit.MoveToY(fruit.Y - 50, 250, Easing.OutSine) fruit.MoveToY(fruit.Y - 50, 250, Easing.OutSine).Then().MoveToY(fruit.Y + 50, 500, Easing.InSine);
.Then()
.MoveToY(fruit.Y + 50, 500, Easing.InSine);
fruit.MoveToX(fruit.X + originalX * 6, 1000); fruit.MoveToX(fruit.X + originalX * 6, 1000);
fruit.FadeOut(750); fruit.FadeOut(750);

View File

@ -14,24 +14,20 @@ namespace osu.Game.Rulesets.Mania.Tests
/// </summary> /// </summary>
public class ScrollingTestContainer : Container public class ScrollingTestContainer : Container
{ {
private readonly ScrollingDirection direction; [Cached(Type = typeof(IScrollingInfo))]
private readonly TestScrollingInfo scrollingInfo = new TestScrollingInfo();
public ScrollingTestContainer(ScrollingDirection direction) public ScrollingTestContainer(ScrollingDirection direction)
{ {
this.direction = direction; scrollingInfo.Direction.Value = direction;
} }
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) public void Flip() => scrollingInfo.Direction.Value = scrollingInfo.Direction.Value == ScrollingDirection.Up ? ScrollingDirection.Down : ScrollingDirection.Up;
{ }
var dependencies = new DependencyContainer(base.CreateChildDependencies(parent));
dependencies.CacheAs<IScrollingInfo>(new ScrollingInfo { Direction = { Value = direction }});
return dependencies;
}
private class ScrollingInfo : IScrollingInfo public class TestScrollingInfo : IScrollingInfo
{ {
public readonly Bindable<ScrollingDirection> Direction = new Bindable<ScrollingDirection>(); public readonly Bindable<ScrollingDirection> Direction = new Bindable<ScrollingDirection>();
IBindable<ScrollingDirection> IScrollingInfo.Direction => Direction; IBindable<ScrollingDirection> IScrollingInfo.Direction => Direction;
}
} }
} }

View File

@ -0,0 +1,32 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Configuration;
using osu.Game.Rulesets.Mania.Configuration;
using osu.Game.Rulesets.Mania.UI;
using osu.Game.Tests.Visual;
namespace osu.Game.Rulesets.Mania.Tests
{
[TestFixture]
public class TestCaseEditor : EditorTestCase
{
private readonly Bindable<ManiaScrollingDirection> direction = new Bindable<ManiaScrollingDirection>();
public TestCaseEditor()
: base(new ManiaRuleset())
{
AddStep("upwards scroll", () => direction.Value = ManiaScrollingDirection.Up);
AddStep("downwards scroll", () => direction.Value = ManiaScrollingDirection.Down);
}
[BackgroundDependencyLoader]
private void load(RulesetConfigCache configCache)
{
var config = (ManiaConfigManager)configCache.GetConfigFor(Ruleset.Value.CreateInstance());
config.BindWith(ManiaSetting.ScrollDirection, direction);
}
}
}

View File

@ -46,15 +46,20 @@ namespace osu.Game.Rulesets.Mania.Tests
Spacing = new Vector2(20), Spacing = new Vector2(20),
Children = new[] Children = new[]
{ {
createNoteDisplay(ScrollingDirection.Down), createNoteDisplay(ScrollingDirection.Down, 1, out var note1),
createNoteDisplay(ScrollingDirection.Up), createNoteDisplay(ScrollingDirection.Up, 2, out var note2),
createHoldNoteDisplay(ScrollingDirection.Down), createHoldNoteDisplay(ScrollingDirection.Down, 1, out var holdNote1),
createHoldNoteDisplay(ScrollingDirection.Up), createHoldNoteDisplay(ScrollingDirection.Up, 2, out var holdNote2),
} }
}; };
AddAssert("note 1 facing downwards", () => verifyAnchors(note1, Anchor.y2));
AddAssert("note 2 facing upwards", () => verifyAnchors(note2, Anchor.y0));
AddAssert("hold note 1 facing downwards", () => verifyAnchors(holdNote1, Anchor.y2));
AddAssert("hold note 2 facing upwards", () => verifyAnchors(holdNote2, Anchor.y0));
} }
private Drawable createNoteDisplay(ScrollingDirection direction) private Drawable createNoteDisplay(ScrollingDirection direction, int identifier, out DrawableNote hitObject)
{ {
var note = new Note { StartTime = 999999999 }; var note = new Note { StartTime = 999999999 };
note.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); note.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
@ -62,24 +67,24 @@ namespace osu.Game.Rulesets.Mania.Tests
return new ScrollingTestContainer(direction) return new ScrollingTestContainer(direction)
{ {
AutoSizeAxes = Axes.Both, AutoSizeAxes = Axes.Both,
Child = new NoteContainer(direction, $"note, scrolling {direction.ToString().ToLowerInvariant()}") Child = new NoteContainer(direction, $"note {identifier}, scrolling {direction.ToString().ToLowerInvariant()}")
{ {
Child = new DrawableNote(note) { AccentColour = Color4.OrangeRed } Child = hitObject = new DrawableNote(note) { AccentColour = Color4.OrangeRed }
} }
}; };
} }
private Drawable createHoldNoteDisplay(ScrollingDirection direction) private Drawable createHoldNoteDisplay(ScrollingDirection direction, int identifier, out DrawableHoldNote hitObject)
{ {
var note = new HoldNote { StartTime = 999999999, Duration = 1000 }; var note = new HoldNote { StartTime = 999999999, Duration = 5000 };
note.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); note.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
return new ScrollingTestContainer(direction) return new ScrollingTestContainer(direction)
{ {
AutoSizeAxes = Axes.Both, AutoSizeAxes = Axes.Both,
Child = new NoteContainer(direction, $"hold note, scrolling {direction.ToString().ToLowerInvariant()}") Child = new NoteContainer(direction, $"hold note {identifier}, scrolling {direction.ToString().ToLowerInvariant()}")
{ {
Child = new DrawableHoldNote(note) Child = hitObject = new DrawableHoldNote(note)
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
AccentColour = Color4.OrangeRed, AccentColour = Color4.OrangeRed,
@ -88,6 +93,12 @@ namespace osu.Game.Rulesets.Mania.Tests
}; };
} }
private bool verifyAnchors(DrawableHitObject hitObject, Anchor expectedAnchor)
=> hitObject.Anchor.HasFlag(expectedAnchor) && hitObject.Origin.HasFlag(expectedAnchor);
private bool verifyAnchors(DrawableHoldNote holdNote, Anchor expectedAnchor)
=> verifyAnchors((DrawableHitObject)holdNote, expectedAnchor) && holdNote.NestedHitObjects.All(n => verifyAnchors(n, expectedAnchor));
private class NoteContainer : Container private class NoteContainer : Container
{ {
private readonly Container content; private readonly Container content;

View File

@ -2,6 +2,7 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
@ -24,6 +25,8 @@ namespace osu.Game.Rulesets.Mania.Tests
private readonly List<ManiaStage> stages = new List<ManiaStage>(); private readonly List<ManiaStage> stages = new List<ManiaStage>();
private FillFlowContainer<ScrollingTestContainer> fill;
public TestCaseStage() public TestCaseStage()
: base(columns) : base(columns)
{ {
@ -32,7 +35,7 @@ namespace osu.Game.Rulesets.Mania.Tests
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load() private void load()
{ {
Child = new FillFlowContainer Child = fill = new FillFlowContainer<ScrollingTestContainer>
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
@ -54,8 +57,22 @@ namespace osu.Game.Rulesets.Mania.Tests
AddStep("hold note", createHoldNote); AddStep("hold note", createHoldNote);
AddStep("minor bar line", () => createBarLine(false)); AddStep("minor bar line", () => createBarLine(false));
AddStep("major bar line", () => createBarLine(true)); AddStep("major bar line", () => createBarLine(true));
AddAssert("check note anchors", () => notesInStageAreAnchored(stages[0], Anchor.TopCentre));
AddAssert("check note anchors", () => notesInStageAreAnchored(stages[1], Anchor.BottomCentre));
AddStep("flip direction", () =>
{
foreach (var c in fill.Children)
c.Flip();
});
AddAssert("check note anchors", () => notesInStageAreAnchored(stages[0], Anchor.BottomCentre));
AddAssert("check note anchors", () => notesInStageAreAnchored(stages[1], Anchor.TopCentre));
} }
private bool notesInStageAreAnchored(ManiaStage stage, Anchor anchor) => stage.Columns.SelectMany(c => c.AllHitObjects).All(o => o.Anchor == anchor);
private void createNote() private void createNote()
{ {
foreach (var stage in stages) foreach (var stage in stages)
@ -101,7 +118,7 @@ namespace osu.Game.Rulesets.Mania.Tests
} }
} }
private Drawable createStage(ScrollingDirection direction, ManiaAction action) private ScrollingTestContainer createStage(ScrollingDirection direction, ManiaAction action)
{ {
var specialAction = ManiaAction.Special1; var specialAction = ManiaAction.Special1;

View File

@ -173,19 +173,18 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
var pattern = new Pattern(); var pattern = new Pattern();
int usableColumns = TotalColumns - RandomStart - PreviousPattern.ColumnWithObjects; 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++) for (int i = 0; i < Math.Min(usableColumns, noteCount); i++)
{ {
while (pattern.ColumnHasObject(nextColumn) || PreviousPattern.ColumnHasObject(nextColumn)) //find available column // Find available column
nextColumn = Random.Next(RandomStart, TotalColumns); nextColumn = FindAvailableColumn(nextColumn, pattern, PreviousPattern);
addToPattern(pattern, nextColumn, startTime, EndTime); addToPattern(pattern, nextColumn, startTime, EndTime);
} }
// This is can't be combined with the above loop due to RNG // This is can't be combined with the above loop due to RNG
for (int i = 0; i < noteCount - usableColumns; i++) for (int i = 0; i < noteCount - usableColumns; i++)
{ {
while (pattern.ColumnHasObject(nextColumn)) nextColumn = FindAvailableColumn(nextColumn, pattern);
nextColumn = Random.Next(RandomStart, TotalColumns);
addToPattern(pattern, nextColumn, startTime, EndTime); 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); int nextColumn = GetColumn((HitObject as IHasXPosition)?.X ?? 0, true);
if (convertType.HasFlag(PatternType.ForceNotStack) && PreviousPattern.ColumnWithObjects < TotalColumns) if (convertType.HasFlag(PatternType.ForceNotStack) && PreviousPattern.ColumnWithObjects < TotalColumns)
{ nextColumn = FindAvailableColumn(nextColumn, PreviousPattern);
while (PreviousPattern.ColumnHasObject(nextColumn))
nextColumn = Random.Next(RandomStart, TotalColumns);
}
int lastColumn = nextColumn; int lastColumn = nextColumn;
for (int i = 0; i < noteCount; i++) for (int i = 0; i < noteCount; i++)
{ {
addToPattern(pattern, nextColumn, startTime, startTime); addToPattern(pattern, nextColumn, startTime, startTime);
while (nextColumn == lastColumn) nextColumn = FindAvailableColumn(nextColumn, validation: c => c != lastColumn);
nextColumn = Random.Next(RandomStart, TotalColumns);
lastColumn = nextColumn; lastColumn = nextColumn;
startTime += SegmentDuration; startTime += SegmentDuration;
} }
@ -313,7 +307,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
if (TotalColumns > 2) if (TotalColumns > 2)
addToPattern(pattern, nextColumn, startTime, startTime); addToPattern(pattern, nextColumn, startTime, startTime);
nextColumn = Random.Next(RandomStart, TotalColumns); nextColumn = GetRandomColumn();
startTime += SegmentDuration; startTime += SegmentDuration;
} }
@ -392,16 +386,11 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
int nextColumn = GetColumn((HitObject as IHasXPosition)?.X ?? 0, true); int nextColumn = GetColumn((HitObject as IHasXPosition)?.X ?? 0, true);
if (convertType.HasFlag(PatternType.ForceNotStack) && PreviousPattern.ColumnWithObjects < TotalColumns) if (convertType.HasFlag(PatternType.ForceNotStack) && PreviousPattern.ColumnWithObjects < TotalColumns)
{ nextColumn = FindAvailableColumn(nextColumn, PreviousPattern);
while (PreviousPattern.ColumnHasObject(nextColumn))
nextColumn = Random.Next(RandomStart, TotalColumns);
}
for (int i = 0; i < columnRepeat; i++) for (int i = 0; i < columnRepeat; i++)
{ {
while (pattern.ColumnHasObject(nextColumn)) nextColumn = FindAvailableColumn(nextColumn, pattern);
nextColumn = Random.Next(RandomStart, TotalColumns);
addToPattern(pattern, nextColumn, startTime, EndTime); addToPattern(pattern, nextColumn, startTime, EndTime);
startTime += SegmentDuration; startTime += SegmentDuration;
} }
@ -426,15 +415,12 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
int holdColumn = GetColumn((HitObject as IHasXPosition)?.X ?? 0, true); int holdColumn = GetColumn((HitObject as IHasXPosition)?.X ?? 0, true);
if (convertType.HasFlag(PatternType.ForceNotStack) && PreviousPattern.ColumnWithObjects < TotalColumns) if (convertType.HasFlag(PatternType.ForceNotStack) && PreviousPattern.ColumnWithObjects < TotalColumns)
{ holdColumn = FindAvailableColumn(holdColumn, PreviousPattern);
while (PreviousPattern.ColumnHasObject(holdColumn))
holdColumn = Random.Next(RandomStart, TotalColumns);
}
// Create the hold note // Create the hold note
addToPattern(pattern, holdColumn, startTime, EndTime); addToPattern(pattern, holdColumn, startTime, EndTime);
int nextColumn = Random.Next(RandomStart, TotalColumns); int nextColumn = GetRandomColumn();
int noteCount; int noteCount;
if (ConversionDifficulty > 6.5) if (ConversionDifficulty > 6.5)
noteCount = GetRandomNoteCount(0.63, 0); noteCount = GetRandomNoteCount(0.63, 0);
@ -455,8 +441,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
{ {
for (int j = 0; j < noteCount; j++) for (int j = 0; j < noteCount; j++)
{ {
while (rowPattern.ColumnHasObject(nextColumn) || nextColumn == holdColumn) nextColumn = FindAvailableColumn(nextColumn, validation: c => c != holdColumn, patterns: rowPattern);
nextColumn = Random.Next(RandomStart, TotalColumns);
addToPattern(rowPattern, nextColumn, startTime, startTime); addToPattern(rowPattern, nextColumn, startTime, startTime);
} }
} }

View File

@ -39,32 +39,17 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
addToPattern(pattern, 0, generateHold); addToPattern(pattern, 0, generateHold);
break; break;
case 8: case 8:
addToPattern(pattern, getNextRandomColumn(RandomStart), generateHold); addToPattern(pattern, FindAvailableColumn(GetRandomColumn(), PreviousPattern), generateHold);
break; break;
default: default:
if (TotalColumns > 0) if (TotalColumns > 0)
addToPattern(pattern, getNextRandomColumn(0), generateHold); addToPattern(pattern, GetRandomColumn(), generateHold);
break; break;
} }
return pattern; 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> /// <summary>
/// Constructs and adds a note to a pattern. /// Constructs and adds a note to a pattern.
/// </summary> /// </summary>

View File

@ -25,9 +25,6 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
PatternType lastStair, IBeatmap originalBeatmap) PatternType lastStair, IBeatmap originalBeatmap)
: base(random, hitObject, beatmap, previousPattern, 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; StairType = lastStair;
TimingControlPoint timingPoint = beatmap.ControlPointInfo.TimingPointAt(hitObject.StartTime); 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); int nextColumn = GetColumn((HitObject as IHasXPosition)?.X ?? 0, true);
for (int i = 0; i < noteCount; i++) for (int i = 0; i < noteCount; i++)
{ {
while (pattern.ColumnHasObject(nextColumn) || PreviousPattern.ColumnHasObject(nextColumn) && !allowStacking) nextColumn = allowStacking
{ ? FindAvailableColumn(nextColumn, nextColumn: getNextColumn, patterns: pattern)
if (convertType.HasFlag(PatternType.Gathered)) : FindAvailableColumn(nextColumn, nextColumn: getNextColumn, patterns: new[] { pattern, PreviousPattern });
{
nextColumn++;
if (nextColumn == TotalColumns)
nextColumn = RandomStart;
}
else
nextColumn = Random.Next(RandomStart, TotalColumns);
}
addToPattern(pattern, nextColumn); addToPattern(pattern, nextColumn);
} }
return pattern; return pattern;
int getNextColumn(int last)
{
if (convertType.HasFlag(PatternType.Gathered))
{
last++;
if (last == TotalColumns)
last = RandomStart;
}
else
last = GetRandomColumn();
return last;
}
} }
/// <summary> /// <summary>
@ -286,17 +288,19 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
/// <returns>The <see cref="Pattern"/> containing the hit objects.</returns> /// <returns>The <see cref="Pattern"/> containing the hit objects.</returns>
private Pattern generateRandomPatternWithMirrored(double centreProbability, double p2, double p3) 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(); var pattern = new Pattern();
bool addToCentre; bool addToCentre;
int noteCount = getRandomNoteCountMirrored(centreProbability, p2, p3, out addToCentre); int noteCount = getRandomNoteCountMirrored(centreProbability, p2, p3, out addToCentre);
int columnLimit = (TotalColumns % 2 == 0 ? TotalColumns : TotalColumns - 1) / 2; 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++) for (int i = 0; i < noteCount; i++)
{ {
while (pattern.ColumnHasObject(nextColumn)) nextColumn = FindAvailableColumn(nextColumn, upperBound: columnLimit, patterns: pattern);
nextColumn = Random.Next(RandomStart, columnLimit);
// Add normal note // Add normal note
addToPattern(pattern, nextColumn); addToPattern(pattern, nextColumn);
@ -368,9 +372,6 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
{ {
addToCentre = false; addToCentre = false;
if (convertType.HasFlag(PatternType.ForceNotStack))
return getRandomNoteCount(1 / 2f + p2 / 2, p2, (p2 + p3) / 2, p3);
switch (TotalColumns) switch (TotalColumns)
{ {
case 2: case 2:

View File

@ -3,6 +3,7 @@
using System; using System;
using System.Linq; using System.Linq;
using JetBrains.Annotations;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Rulesets.Mania.MathUtils; using osu.Game.Rulesets.Mania.MathUtils;
using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects;
@ -90,6 +91,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
} }
private double? conversionDifficulty; private double? conversionDifficulty;
/// <summary> /// <summary>
/// A difficulty factor used for various conversion methods from osu!stable. /// A difficulty factor used for various conversion methods from osu!stable.
/// </summary> /// </summary>
@ -116,5 +118,82 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
return conversionDifficulty.Value; 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.")
{
}
}
} }
} }

View File

@ -0,0 +1,84 @@
// 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.Configuration;
using osu.Framework.Graphics;
using osu.Game.Graphics;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Mania.Objects.Drawables;
using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces;
using osu.Game.Rulesets.Mania.UI;
using osu.Game.Rulesets.UI.Scrolling;
using OpenTK;
using OpenTK.Graphics;
namespace osu.Game.Rulesets.Mania.Edit.Layers.Selection.Overlays
{
public class HoldNoteMask : HitObjectMask
{
public new DrawableHoldNote HitObject => (DrawableHoldNote)base.HitObject;
private readonly IBindable<ScrollingDirection> direction = new Bindable<ScrollingDirection>();
private readonly BodyPiece body;
public HoldNoteMask(DrawableHoldNote hold)
: base(hold)
{
InternalChildren = new Drawable[]
{
new HoldNoteNoteMask(hold.Head),
new HoldNoteNoteMask(hold.Tail),
body = new BodyPiece
{
AccentColour = Color4.Transparent
},
};
}
[BackgroundDependencyLoader]
private void load(OsuColour colours, IScrollingInfo scrollingInfo)
{
body.BorderColour = colours.Yellow;
direction.BindTo(scrollingInfo.Direction);
}
protected override void Update()
{
base.Update();
Size = HitObject.DrawSize + new Vector2(0, HitObject.Tail.DrawHeight);
Position = Parent.ToLocalSpace(HitObject.ScreenSpaceDrawQuad.TopLeft);
// This is a side-effect of not matching the hitobject's anchors/origins, which is kinda hard to do
// When scrolling upwards our origin is already at the top of the head note (which is the intended location),
// but when scrolling downwards our origin is at the _bottom_ of the tail note (where we need to be at the _top_ of the tail note)
if (direction.Value == ScrollingDirection.Down)
Y -= HitObject.Tail.DrawHeight;
}
private class HoldNoteNoteMask : NoteMask
{
public HoldNoteNoteMask(DrawableNote note)
: base(note)
{
Select();
}
protected override void Update()
{
base.Update();
Anchor = HitObject.Anchor;
Origin = HitObject.Origin;
Position = HitObject.DrawPosition;
}
// Todo: This is temporary, since the note masks don't do anything special yet. In the future they will handle input.
public override bool HandleMouseInput => false;
}
}
}

View 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.Allocation;
using osu.Game.Graphics;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Mania.Objects.Drawables;
using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces;
namespace osu.Game.Rulesets.Mania.Edit.Layers.Selection.Overlays
{
public class NoteMask : HitObjectMask
{
public NoteMask(DrawableNote note)
: base(note)
{
Scale = note.Scale;
CornerRadius = 5;
Masking = true;
AddInternal(new NotePiece());
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
Colour = colours.Yellow;
}
protected override void Update()
{
base.Update();
Size = HitObject.DrawSize;
Position = Parent.ToLocalSpace(HitObject.ScreenSpaceDrawQuad.TopLeft);
}
}
}

View File

@ -0,0 +1,17 @@
// 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.Mania.Beatmaps;
using osu.Game.Rulesets.Mania.UI;
using System.Collections.Generic;
namespace osu.Game.Rulesets.Mania.Edit
{
public class ManiaEditPlayfield : ManiaPlayfield
{
public ManiaEditPlayfield(List<StageDefinition> stages)
: base(stages)
{
}
}
}

View File

@ -0,0 +1,27 @@
// 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 OpenTK;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Mania.UI;
using osu.Game.Rulesets.UI;
namespace osu.Game.Rulesets.Mania.Edit
{
public class ManiaEditRulesetContainer : ManiaRulesetContainer
{
public ManiaEditRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap)
: base(ruleset, beatmap)
{
}
protected override Playfield CreatePlayfield() => new ManiaEditPlayfield(Beatmap.Stages)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
};
protected override Vector2 PlayfieldArea => Vector2.One;
}
}

View File

@ -0,0 +1,56 @@
// 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.Beatmaps;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Edit.Tools;
using osu.Game.Rulesets.Mania.Edit.Layers.Selection.Overlays;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.Objects.Drawables;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.UI;
using System.Collections.Generic;
using osu.Framework.Allocation;
using osu.Game.Rulesets.Mania.Configuration;
using osu.Game.Rulesets.Mania.UI;
namespace osu.Game.Rulesets.Mania.Edit
{
public class ManiaHitObjectComposer : HitObjectComposer
{
protected new ManiaConfigManager Config => (ManiaConfigManager)base.Config;
public ManiaHitObjectComposer(Ruleset ruleset)
: base(ruleset)
{
}
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
{
var dependencies = new DependencyContainer(base.CreateChildDependencies(parent));
dependencies.CacheAs<IScrollingInfo>(new ManiaScrollingInfo(Config));
return dependencies;
}
protected override RulesetContainer CreateRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap) => new ManiaEditRulesetContainer(ruleset, beatmap);
protected override IReadOnlyList<ICompositionTool> CompositionTools => new ICompositionTool[]
{
new HitObjectCompositionTool<Note>("Note"),
new HitObjectCompositionTool<HoldNote>("Hold"),
};
public override HitObjectMask CreateMaskFor(DrawableHitObject hitObject)
{
switch (hitObject)
{
case DrawableNote note:
return new NoteMask(note);
case DrawableHoldNote holdNote:
return new HoldNoteMask(holdNote);
}
return base.CreateMaskFor(hitObject);
}
}
}

View File

@ -1,27 +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.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Mania.Judgements
{
public class HoldNoteTailJudgement : ManiaJudgement
{
/// <summary>
/// Whether the hold note has been released too early and shouldn't give full score for the release.
/// </summary>
public bool HasBroken;
protected override int NumericResultFor(HitResult result)
{
switch (result)
{
default:
return base.NumericResultFor(result);
case HitResult.Great:
case HitResult.Perfect:
return base.NumericResultFor(HasBroken ? HitResult.Good : result);
}
}
}
}

View File

@ -19,9 +19,11 @@ using osu.Game.Configuration;
using osu.Game.Overlays.Settings; using osu.Game.Overlays.Settings;
using osu.Game.Rulesets.Configuration; using osu.Game.Rulesets.Configuration;
using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Mania.Configuration; using osu.Game.Rulesets.Mania.Configuration;
using osu.Game.Rulesets.Mania.Difficulty; using osu.Game.Rulesets.Mania.Difficulty;
using osu.Game.Rulesets.Mania.Edit;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Mania namespace osu.Game.Rulesets.Mania
@ -32,6 +34,8 @@ namespace osu.Game.Rulesets.Mania
public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new ManiaBeatmapConverter(beatmap); public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new ManiaBeatmapConverter(beatmap);
public override PerformanceCalculator CreatePerformanceCalculator(WorkingBeatmap beatmap, Score score) => new ManiaPerformanceCalculator(this, beatmap, score); public override PerformanceCalculator CreatePerformanceCalculator(WorkingBeatmap beatmap, Score score) => new ManiaPerformanceCalculator(this, beatmap, score);
public override HitObjectComposer CreateHitObjectComposer() => new ManiaHitObjectComposer(this);
public override IEnumerable<Mod> ConvertLegacyMods(LegacyMods mods) public override IEnumerable<Mod> ConvertLegacyMods(LegacyMods mods)
{ {
if (mods.HasFlag(LegacyMods.Nightcore)) if (mods.HasFlag(LegacyMods.Nightcore))

View File

@ -7,7 +7,6 @@ using osu.Framework.Graphics;
using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces; using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces;
using OpenTK.Graphics; using OpenTK.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Mania.Judgements;
using osu.Framework.Input.Bindings; using osu.Framework.Input.Bindings;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Rulesets.UI.Scrolling;
@ -19,10 +18,10 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
/// </summary> /// </summary>
public class DrawableHoldNote : DrawableManiaHitObject<HoldNote>, IKeyBindingHandler<ManiaAction> public class DrawableHoldNote : DrawableManiaHitObject<HoldNote>, IKeyBindingHandler<ManiaAction>
{ {
public override bool DisplayJudgement => false; public override bool DisplayResult => false;
private readonly DrawableNote head; public readonly DrawableNote Head;
private readonly DrawableNote tail; public readonly DrawableNote Tail;
private readonly BodyPiece bodyPiece; private readonly BodyPiece bodyPiece;
@ -57,12 +56,12 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
HoldStartTime = () => holdStartTime HoldStartTime = () => holdStartTime
}) })
}, },
head = new DrawableHeadNote(this) Head = new DrawableHeadNote(this)
{ {
Anchor = Anchor.TopCentre, Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre Origin = Anchor.TopCentre
}, },
tail = new DrawableTailNote(this) Tail = new DrawableTailNote(this)
{ {
Anchor = Anchor.TopCentre, Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre Origin = Anchor.TopCentre
@ -72,8 +71,8 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
foreach (var tick in tickContainer) foreach (var tick in tickContainer)
AddNested(tick); AddNested(tick);
AddNested(head); AddNested(Head);
AddNested(tail); AddNested(Tail);
} }
protected override void OnDirectionChanged(ScrollingDirection direction) protected override void OnDirectionChanged(ScrollingDirection direction)
@ -91,16 +90,16 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
base.AccentColour = value; base.AccentColour = value;
bodyPiece.AccentColour = value; bodyPiece.AccentColour = value;
head.AccentColour = value; Head.AccentColour = value;
tail.AccentColour = value; Tail.AccentColour = value;
tickContainer.ForEach(t => t.AccentColour = value); tickContainer.ForEach(t => t.AccentColour = value);
} }
} }
protected override void CheckForJudgements(bool userTriggered, double timeOffset) protected override void CheckForResult(bool userTriggered, double timeOffset)
{ {
if (tail.AllJudged) if (Tail.AllJudged)
AddJudgement(new HoldNoteJudgement { Result = HitResult.Perfect }); ApplyResult(r => r.Type = HitResult.Perfect);
} }
protected override void Update() protected override void Update()
@ -108,8 +107,20 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
base.Update(); base.Update();
// Make the body piece not lie under the head note // Make the body piece not lie under the head note
bodyPiece.Y = (Direction.Value == ScrollingDirection.Up ? 1 : -1) * head.Height / 2; bodyPiece.Y = (Direction.Value == ScrollingDirection.Up ? 1 : -1) * Head.Height / 2;
bodyPiece.Height = DrawHeight - head.Height / 2 + tail.Height / 2; 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) public bool OnPressed(ManiaAction action)
@ -124,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 // 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 // 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. // user has pressed during the hit windows of the head note.
holdStartTime = Time.Current; BeginHold();
return true; return true;
} }
@ -138,10 +148,10 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
if (action != Action.Value) if (action != Action.Value)
return false; return false;
holdStartTime = null; EndHold();
// If the key has been released too early, the user should not receive full score for the release // If the key has been released too early, the user should not receive full score for the release
if (!tail.IsHit) if (!Tail.IsHit)
hasBroken = true; hasBroken = true;
return true; return true;
@ -166,12 +176,12 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
return false; return false;
// If the key has been released too early, the user should not receive full score for the release // If the key has been released too early, the user should not receive full score for the release
if (Judgements.Any(j => j.Result == HitResult.Miss)) if (Result.Type == HitResult.Miss)
holdNote.hasBroken = true; holdNote.hasBroken = true;
// The head note also handles early hits before the body, but we want accurate early hits to count as the body being held // 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 // 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; return true;
} }
@ -197,7 +207,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
this.holdNote = holdNote; this.holdNote = holdNote;
} }
protected override void CheckForJudgements(bool userTriggered, double timeOffset) protected override void CheckForResult(bool userTriggered, double timeOffset)
{ {
// Factor in the release lenience // Factor in the release lenience
timeOffset /= release_window_lenience; timeOffset /= release_window_lenience;
@ -205,13 +215,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
if (!userTriggered) if (!userTriggered)
{ {
if (!HitObject.HitWindows.CanBeHit(timeOffset)) if (!HitObject.HitWindows.CanBeHit(timeOffset))
{ ApplyResult(r => r.Type = HitResult.Miss);
AddJudgement(new HoldNoteTailJudgement
{
Result = HitResult.Miss,
HasBroken = holdNote.hasBroken
});
}
return; return;
} }
@ -220,10 +224,12 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
if (result == HitResult.None) if (result == HitResult.None)
return; return;
AddJudgement(new HoldNoteTailJudgement ApplyResult(r =>
{ {
Result = result, if (holdNote.hasBroken && (result == HitResult.Perfect || result == HitResult.Perfect))
HasBroken = holdNote.hasBroken result = HitResult.Good;
r.Type = result;
}); });
} }
@ -238,7 +244,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
if (action != Action.Value) if (action != Action.Value)
return false; return false;
UpdateJudgement(true); UpdateResult(true);
// Handled by the hold note, which will set holding = false // Handled by the hold note, which will set holding = false
return false; return false;

View File

@ -7,7 +7,6 @@ using OpenTK.Graphics;
using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Mania.Judgements;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
@ -72,29 +71,17 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
} }
} }
protected override void CheckForJudgements(bool userTriggered, double timeOffset) protected override void CheckForResult(bool userTriggered, double timeOffset)
{ {
if (!userTriggered)
return;
if (Time.Current < HitObject.StartTime) if (Time.Current < HitObject.StartTime)
return; return;
if (HoldStartTime?.Invoke() > HitObject.StartTime) var startTime = HoldStartTime?.Invoke();
return;
AddJudgement(new HoldNoteTickJudgement { Result = HitResult.Perfect }); if (startTime == null || startTime > HitObject.StartTime)
} ApplyResult(r => r.Type = HitResult.Miss);
else
protected override void Update() ApplyResult(r => r.Type = HitResult.Perfect);
{
if (AllJudged)
return;
if (HoldStartTime?.Invoke() == null)
return;
UpdateJudgement(true);
} }
} }
} }

View File

@ -6,7 +6,6 @@ using OpenTK.Graphics;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Input.Bindings; using osu.Framework.Input.Bindings;
using osu.Game.Rulesets.Mania.Judgements;
using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces; using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Rulesets.UI.Scrolling;
@ -56,12 +55,12 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
} }
} }
protected override void CheckForJudgements(bool userTriggered, double timeOffset) protected override void CheckForResult(bool userTriggered, double timeOffset)
{ {
if (!userTriggered) if (!userTriggered)
{ {
if (!HitObject.HitWindows.CanBeHit(timeOffset)) if (!HitObject.HitWindows.CanBeHit(timeOffset))
AddJudgement(new ManiaJudgement { Result = HitResult.Miss }); ApplyResult(r => r.Type = HitResult.Miss);
return; return;
} }
@ -69,7 +68,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
if (result == HitResult.None) if (result == HitResult.None)
return; return;
AddJudgement(new ManiaJudgement { Result = result }); ApplyResult(r => r.Type = result);
} }
public virtual bool OnPressed(ManiaAction action) public virtual bool OnPressed(ManiaAction action)
@ -77,7 +76,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
if (action != Action.Value) if (action != Action.Value)
return false; return false;
return UpdateJudgement(true); return UpdateResult(true);
} }
public virtual bool OnReleased(ManiaAction action) => false; public virtual bool OnReleased(ManiaAction action) => false;

View File

@ -32,6 +32,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces
background = new Box { RelativeSizeAxes = Axes.Both }, background = new Box { RelativeSizeAxes = Axes.Both },
foreground = new BufferedContainer foreground = new BufferedContainer
{ {
Blending = BlendingMode.Additive,
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
CacheDrawnFrameBuffer = true, CacheDrawnFrameBuffer = true,
Children = new Drawable[] Children = new Drawable[]
@ -73,6 +74,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces
} }
private Color4 accentColour; private Color4 accentColour;
public Color4 AccentColour public Color4 AccentColour
{ {
get { return 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(); private Cached subtractionCache = new Cached();
public override bool Invalidate(Invalidation invalidation = Invalidation.All, Drawable source = null, bool shallPropagate = true) 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() private void updateAccentColour()
{ {
if (!IsLoaded) if (!IsLoaded)
return; return;
foreground.Colour = AccentColour.Opacity(0.9f); foreground.Colour = AccentColour.Opacity(0.5f);
background.Colour = AccentColour.Opacity(0.6f); 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(); subtractionCache.Invalidate();
} }

View File

@ -3,6 +3,8 @@
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Mania.Judgements;
using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Objects.Types;
namespace osu.Game.Rulesets.Mania.Objects namespace osu.Game.Rulesets.Mania.Objects
@ -55,7 +57,7 @@ namespace osu.Game.Rulesets.Mania.Objects
/// <summary> /// <summary>
/// The tail note of the hold. /// The tail note of the hold.
/// </summary> /// </summary>
public readonly Note Tail = new Note(); public readonly TailNote Tail = new TailNote();
/// <summary> /// <summary>
/// The time between ticks of this hold. /// The time between ticks of this hold.
@ -68,9 +70,6 @@ namespace osu.Game.Rulesets.Mania.Objects
TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(StartTime); TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(StartTime);
tickSpacing = timingPoint.BeatLength / difficulty.SliderTickRate; tickSpacing = timingPoint.BeatLength / difficulty.SliderTickRate;
Head.ApplyDefaults(controlPointInfo, difficulty);
Tail.ApplyDefaults(controlPointInfo, difficulty);
} }
protected override void CreateNestedHitObjects() protected override void CreateNestedHitObjects()
@ -78,6 +77,9 @@ namespace osu.Game.Rulesets.Mania.Objects
base.CreateNestedHitObjects(); base.CreateNestedHitObjects();
createTicks(); createTicks();
AddNested(Head);
AddNested(Tail);
} }
private void createTicks() private void createTicks()
@ -94,5 +96,7 @@ namespace osu.Game.Rulesets.Mania.Objects
}); });
} }
} }
public override Judgement CreateJudgement() => new HoldNoteJudgement();
} }
} }

View File

@ -1,6 +1,9 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Mania.Judgements;
namespace osu.Game.Rulesets.Mania.Objects namespace osu.Game.Rulesets.Mania.Objects
{ {
/// <summary> /// <summary>
@ -8,5 +11,6 @@ namespace osu.Game.Rulesets.Mania.Objects
/// </summary> /// </summary>
public class HoldNoteTick : ManiaHitObject public class HoldNoteTick : ManiaHitObject
{ {
public override Judgement CreateJudgement() => new HoldNoteTickJudgement();
} }
} }

View File

@ -1,6 +1,9 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Mania.Judgements;
namespace osu.Game.Rulesets.Mania.Objects namespace osu.Game.Rulesets.Mania.Objects
{ {
/// <summary> /// <summary>
@ -8,5 +11,6 @@ namespace osu.Game.Rulesets.Mania.Objects
/// </summary> /// </summary>
public class Note : ManiaHitObject public class Note : ManiaHitObject
{ {
public override Judgement CreateJudgement() => new ManiaJudgement();
} }
} }

View File

@ -0,0 +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.Judgements;
using osu.Game.Rulesets.Mania.Judgements;
namespace osu.Game.Rulesets.Mania.Objects
{
public class TailNote : Note
{
public override Judgement CreateJudgement() => new ManiaJudgement();
}
}

View File

@ -1,7 +1,6 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Linq;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Mania.Judgements; using osu.Game.Rulesets.Mania.Judgements;
@ -97,31 +96,20 @@ namespace osu.Game.Rulesets.Mania.Scoring
{ {
} }
protected override void SimulateAutoplay(Beatmap<ManiaHitObject> beatmap) protected override void ApplyBeatmap(Beatmap<ManiaHitObject> beatmap)
{ {
base.ApplyBeatmap(beatmap);
BeatmapDifficulty difficulty = beatmap.BeatmapInfo.BaseDifficulty; BeatmapDifficulty difficulty = beatmap.BeatmapInfo.BaseDifficulty;
hpMultiplier = BeatmapDifficulty.DifficultyRange(difficulty.DrainRate, hp_multiplier_min, hp_multiplier_mid, hp_multiplier_max); hpMultiplier = BeatmapDifficulty.DifficultyRange(difficulty.DrainRate, hp_multiplier_min, hp_multiplier_mid, hp_multiplier_max);
hpMissMultiplier = BeatmapDifficulty.DifficultyRange(difficulty.DrainRate, hp_multiplier_miss_min, hp_multiplier_miss_mid, hp_multiplier_miss_max); hpMissMultiplier = BeatmapDifficulty.DifficultyRange(difficulty.DrainRate, hp_multiplier_miss_min, hp_multiplier_miss_mid, hp_multiplier_miss_max);
}
protected override void SimulateAutoplay(Beatmap<ManiaHitObject> beatmap)
{
while (true) while (true)
{ {
foreach (var obj in beatmap.HitObjects) base.SimulateAutoplay(beatmap);
{
var holdNote = obj as HoldNote;
if (holdNote != null)
{
// Head
AddJudgement(new ManiaJudgement { Result = HitResult.Perfect });
// Ticks
int tickCount = holdNote.NestedHitObjects.OfType<HoldNoteTick>().Count();
for (int i = 0; i < tickCount; i++)
AddJudgement(new HoldNoteTickJudgement { Result = HitResult.Perfect });
}
AddJudgement(new ManiaJudgement { Result = HitResult.Perfect });
}
if (!HasFailed) if (!HasFailed)
break; break;
@ -133,20 +121,20 @@ namespace osu.Game.Rulesets.Mania.Scoring
} }
} }
protected override void OnNewJudgement(Judgement judgement) protected override void ApplyResult(JudgementResult result)
{ {
base.OnNewJudgement(judgement); base.ApplyResult(result);
bool isTick = judgement is HoldNoteTickJudgement; bool isTick = result.Judgement is HoldNoteTickJudgement;
if (isTick) if (isTick)
{ {
if (judgement.IsHit) if (result.IsHit)
Health.Value += hpMultiplier * hp_increase_tick; Health.Value += hpMultiplier * hp_increase_tick;
} }
else else
{ {
switch (judgement.Result) switch (result.Type)
{ {
case HitResult.Miss: case HitResult.Miss:
Health.Value += hpMissMultiplier * hp_increase_miss; Health.Value += hpMissMultiplier * hp_increase_miss;

View File

@ -131,14 +131,14 @@ namespace osu.Game.Rulesets.Mania.UI
public override void Add(DrawableHitObject hitObject) public override void Add(DrawableHitObject hitObject)
{ {
hitObject.AccentColour = AccentColour; hitObject.AccentColour = AccentColour;
hitObject.OnJudgement += OnJudgement; hitObject.OnNewResult += OnNewResult;
HitObjects.Add(hitObject); HitObjects.Add(hitObject);
} }
internal void OnJudgement(DrawableHitObject judgedObject, Judgement judgement) internal void OnNewResult(DrawableHitObject judgedObject, JudgementResult result)
{ {
if (!judgement.IsHit || !judgedObject.DisplayJudgement || !DisplayJudgements) if (!result.IsHit || !judgedObject.DisplayResult || !DisplayJudgements)
return; return;
explosionContainer.Add(new HitExplosion(judgedObject) explosionContainer.Add(new HitExplosion(judgedObject)

View File

@ -8,10 +8,10 @@ using osu.Game.Rulesets.Objects.Drawables;
namespace osu.Game.Rulesets.Mania.UI namespace osu.Game.Rulesets.Mania.UI
{ {
internal class DrawableManiaJudgement : DrawableJudgement public class DrawableManiaJudgement : DrawableJudgement
{ {
public DrawableManiaJudgement(Judgement judgement, DrawableHitObject judgedObject) public DrawableManiaJudgement(JudgementResult result, DrawableHitObject judgedObject)
: base(judgement, judgedObject) : base(result, judgedObject)
{ {
} }
@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Mania.UI
this.FadeInFromZero(50, Easing.OutQuint); this.FadeInFromZero(50, Easing.OutQuint);
if (Judgement.IsHit) if (Result.IsHit)
{ {
this.ScaleTo(0.8f); this.ScaleTo(0.8f);
this.ScaleTo(1, 250, Easing.OutElastic); this.ScaleTo(1, 250, Easing.OutElastic);

View File

@ -1,22 +1,20 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Game.Rulesets.Mania.Objects;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using osu.Framework.Allocation;
using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Mania.Configuration; using osu.Game.Rulesets.Mania.Configuration;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Objects.Drawables;
namespace osu.Game.Rulesets.Mania.UI namespace osu.Game.Rulesets.Mania.UI
{ {
public class ManiaPlayfield : ManiaScrollingPlayfield public class ManiaPlayfield : ManiaScrollingPlayfield
{ {
public List<Column> Columns => stages.SelectMany(x => x.Columns).ToList();
private readonly List<ManiaStage> stages = new List<ManiaStage>(); private readonly List<ManiaStage> stages = new List<ManiaStage>();
public ManiaPlayfield(List<StageDefinition> stageDefinitions) public ManiaPlayfield(List<StageDefinition> stageDefinitions)
@ -28,11 +26,11 @@ namespace osu.Game.Rulesets.Mania.UI
throw new ArgumentException("Can't have zero or fewer stages."); throw new ArgumentException("Can't have zero or fewer stages.");
GridContainer playfieldGrid; GridContainer playfieldGrid;
InternalChild = playfieldGrid = new GridContainer AddInternal(playfieldGrid = new GridContainer
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Content = new[] { new Drawable[stageDefinitions.Count] } Content = new[] { new Drawable[stageDefinitions.Count] }
}; });
var normalColumnAction = ManiaAction.Key1; var normalColumnAction = ManiaAction.Key1;
var specialColumnAction = ManiaAction.Special1; var specialColumnAction = ManiaAction.Special1;

View File

@ -4,7 +4,6 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Configuration;
using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Input; using osu.Framework.Input;
@ -35,8 +34,7 @@ namespace osu.Game.Rulesets.Mania.UI
public IEnumerable<BarLine> BarLines; public IEnumerable<BarLine> BarLines;
private readonly Bindable<ManiaScrollingDirection> configDirection = new Bindable<ManiaScrollingDirection>(); protected new ManiaConfigManager Config => (ManiaConfigManager)base.Config;
private ScrollingInfo scrollingInfo;
public ManiaRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap) public ManiaRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap)
: base(ruleset, beatmap) : base(ruleset, beatmap)
@ -73,9 +71,6 @@ namespace osu.Game.Rulesets.Mania.UI
private void load() private void load()
{ {
BarLines.ForEach(Playfield.Add); BarLines.ForEach(Playfield.Add);
((ManiaConfigManager)Config).BindWith(ManiaSetting.ScrollDirection, configDirection);
configDirection.BindValueChanged(d => scrollingInfo.Direction.Value = (ScrollingDirection)d, true);
} }
private DependencyContainer dependencies; private DependencyContainer dependencies;
@ -83,11 +78,14 @@ namespace osu.Game.Rulesets.Mania.UI
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
{ {
dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); dependencies = new DependencyContainer(base.CreateChildDependencies(parent));
dependencies.CacheAs<IScrollingInfo>(scrollingInfo = new ScrollingInfo());
if (dependencies.Get<ManiaScrollingInfo>() == null)
dependencies.CacheAs<IScrollingInfo>(new ManiaScrollingInfo(Config));
return dependencies; return dependencies;
} }
protected sealed override Playfield CreatePlayfield() => new ManiaPlayfield(Beatmap.Stages) protected override Playfield CreatePlayfield() => new ManiaPlayfield(Beatmap.Stages)
{ {
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
Origin = Anchor.Centre, Origin = Anchor.Centre,
@ -115,11 +113,5 @@ namespace osu.Game.Rulesets.Mania.UI
protected override Vector2 PlayfieldArea => new Vector2(1, 0.8f); protected override Vector2 PlayfieldArea => new Vector2(1, 0.8f);
protected override ReplayInputHandler CreateReplayInputHandler(Replay replay) => new ManiaFramedReplayInputHandler(replay); protected override ReplayInputHandler CreateReplayInputHandler(Replay replay) => new ManiaFramedReplayInputHandler(replay);
private class ScrollingInfo : IScrollingInfo
{
public readonly Bindable<ScrollingDirection> Direction = new Bindable<ScrollingDirection>();
IBindable<ScrollingDirection> IScrollingInfo.Direction => Direction;
}
} }
} }

View 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.Configuration;
using osu.Game.Rulesets.Mania.Configuration;
using osu.Game.Rulesets.UI.Scrolling;
namespace osu.Game.Rulesets.Mania.UI
{
public class ManiaScrollingInfo : IScrollingInfo
{
private readonly Bindable<ManiaScrollingDirection> configDirection = new Bindable<ManiaScrollingDirection>();
public readonly Bindable<ScrollingDirection> Direction = new Bindable<ScrollingDirection>();
IBindable<ScrollingDirection> IScrollingInfo.Direction => Direction;
public ManiaScrollingInfo(ManiaConfigManager config)
{
config.BindWith(ManiaSetting.ScrollDirection, configDirection);
configDirection.BindValueChanged(v => Direction.Value = (ScrollingDirection)v, true);
}
}
}

View File

@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Mania.UI
/// <summary> /// <summary>
/// A collection of <see cref="Column"/>s. /// A collection of <see cref="Column"/>s.
/// </summary> /// </summary>
internal class ManiaStage : ManiaScrollingPlayfield public class ManiaStage : ManiaScrollingPlayfield
{ {
public const float HIT_TARGET_POSITION = 50; public const float HIT_TARGET_POSITION = 50;
@ -156,18 +156,18 @@ namespace osu.Game.Rulesets.Mania.UI
var maniaObject = (ManiaHitObject)h.HitObject; var maniaObject = (ManiaHitObject)h.HitObject;
int columnIndex = maniaObject.Column - firstColumnIndex; int columnIndex = maniaObject.Column - firstColumnIndex;
Columns.ElementAt(columnIndex).Add(h); Columns.ElementAt(columnIndex).Add(h);
h.OnJudgement += OnJudgement; h.OnNewResult += OnNewResult;
} }
public void Add(BarLine barline) => base.Add(new DrawableBarLine(barline)); public void Add(BarLine barline) => base.Add(new DrawableBarLine(barline));
internal void OnJudgement(DrawableHitObject judgedObject, Judgement judgement) internal void OnNewResult(DrawableHitObject judgedObject, JudgementResult result)
{ {
if (!judgedObject.DisplayJudgement || !DisplayJudgements) if (!judgedObject.DisplayResult || !DisplayJudgements)
return; return;
judgements.Clear(); judgements.Clear();
judgements.Add(new DrawableManiaJudgement(judgement, judgedObject) judgements.Add(new DrawableManiaJudgement(result, judgedObject)
{ {
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
Origin = Anchor.Centre, Origin = Anchor.Centre,

View 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:
";
}
}

View File

@ -5,12 +5,10 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects.Drawables;
using osu.Game.Tests.Visual; using osu.Game.Tests.Visual;
using OpenTK; using OpenTK;
using osu.Game.Rulesets.Osu.Judgements;
using System.Collections.Generic; using System.Collections.Generic;
using System; using System;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
@ -87,7 +85,7 @@ namespace osu.Game.Rulesets.Osu.Tests
} }
} }
private class TestDrawableHitCircle : DrawableHitCircle protected class TestDrawableHitCircle : DrawableHitCircle
{ {
private readonly bool auto; private readonly bool auto;
@ -96,19 +94,17 @@ namespace osu.Game.Rulesets.Osu.Tests
this.auto = auto; this.auto = auto;
} }
protected override void CheckForJudgements(bool userTriggered, double timeOffset) public void TriggerJudgement() => UpdateResult(true);
protected override void CheckForResult(bool userTriggered, double timeOffset)
{ {
if (auto && !userTriggered && timeOffset > 0) if (auto && !userTriggered && timeOffset > 0)
{ {
// force success // force success
AddJudgement(new OsuJudgement ApplyResult(r => r.Type = HitResult.Great);
{
Result = HitResult.Great
});
State.Value = ArmedState.Hit;
} }
else else
base.CheckForJudgements(userTriggered, timeOffset); base.CheckForResult(userTriggered, timeOffset);
} }
} }
} }

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

View File

@ -304,13 +304,13 @@ namespace osu.Game.Rulesets.Osu.Tests
foreach (var mod in Mods.OfType<IApplicableToDrawableHitObjects>()) foreach (var mod in Mods.OfType<IApplicableToDrawableHitObjects>())
mod.ApplyToDrawableHitObjects(new[] { drawable }); mod.ApplyToDrawableHitObjects(new[] { drawable });
drawable.OnJudgement += onJudgement; drawable.OnNewResult += onNewResult;
Add(drawable); Add(drawable);
} }
private float judgementOffsetDirection = 1; private float judgementOffsetDirection = 1;
private void onJudgement(DrawableHitObject judgedObject, Judgement judgement) private void onNewResult(DrawableHitObject judgedObject, JudgementResult result)
{ {
var osuObject = judgedObject as DrawableOsuHitObject; var osuObject = judgedObject as DrawableOsuHitObject;
if (osuObject == null) if (osuObject == null)
@ -321,8 +321,8 @@ namespace osu.Game.Rulesets.Osu.Tests
{ {
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
Origin = Anchor.Centre, Origin = Anchor.Centre,
Text = judgement.IsHit ? "Hit!" : "Miss!", Text = result.IsHit ? "Hit!" : "Miss!",
Colour = judgement.IsHit ? Color4.Green : Color4.Red, Colour = result.IsHit ? Color4.Green : Color4.Red,
TextSize = 30, TextSize = 30,
Position = osuObject.HitObject.StackedEndPosition + judgementOffsetDirection * new Vector2(0, 45) Position = osuObject.HitObject.StackedEndPosition + judgementOffsetDirection * new Vector2(0, 45)
}); });

View File

@ -72,7 +72,7 @@ namespace osu.Game.Rulesets.Osu.Tests
this.auto = auto; this.auto = auto;
} }
protected override void CheckForJudgements(bool userTriggered, double timeOffset) protected override void CheckForResult(bool userTriggered, double timeOffset)
{ {
if (auto && !userTriggered && Time.Current > Spinner.StartTime + Spinner.Duration / 2 && Progress < 1) if (auto && !userTriggered && Time.Current > Spinner.StartTime + Spinner.Duration / 2 && Progress < 1)
{ {
@ -81,7 +81,7 @@ namespace osu.Game.Rulesets.Osu.Tests
auto = false; auto = false;
} }
base.CheckForJudgements(userTriggered, timeOffset); base.CheckForResult(userTriggered, timeOffset);
} }
} }
} }

View File

@ -12,7 +12,7 @@ using osu.Game.Rulesets.Osu.UI;
namespace osu.Game.Rulesets.Osu.Beatmaps namespace osu.Game.Rulesets.Osu.Beatmaps
{ {
internal class OsuBeatmapConverter : BeatmapConverter<OsuHitObject> public class OsuBeatmapConverter : BeatmapConverter<OsuHitObject>
{ {
public OsuBeatmapConverter(IBeatmap beatmap) public OsuBeatmapConverter(IBeatmap beatmap)
: base(beatmap) : base(beatmap)
@ -42,6 +42,7 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
RepeatCount = curveData.RepeatCount, RepeatCount = curveData.RepeatCount,
Position = positionData?.Position ?? Vector2.Zero, Position = positionData?.Position ?? Vector2.Zero,
NewCombo = comboData?.NewCombo ?? false, NewCombo = comboData?.NewCombo ?? false,
ComboOffset = comboData?.ComboOffset ?? 0,
LegacyLastTickOffset = legacyOffset?.LegacyLastTickOffset LegacyLastTickOffset = legacyOffset?.LegacyLastTickOffset
}; };
} }
@ -52,7 +53,9 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
StartTime = original.StartTime, StartTime = original.StartTime,
Samples = original.Samples, Samples = original.Samples,
EndTime = endTimeData.EndTime, 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 else
@ -62,7 +65,8 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
StartTime = original.StartTime, StartTime = original.StartTime,
Samples = original.Samples, Samples = original.Samples,
Position = positionData?.Position ?? Vector2.Zero, Position = positionData?.Position ?? Vector2.Zero,
NewCombo = comboData?.NewCombo ?? false NewCombo = comboData?.NewCombo ?? false,
ComboOffset = comboData?.ComboOffset ?? 0,
}; };
} }
} }

View File

@ -8,16 +8,16 @@ using osu.Game.Rulesets.Osu.Objects;
namespace osu.Game.Rulesets.Osu.Beatmaps namespace osu.Game.Rulesets.Osu.Beatmaps
{ {
internal class OsuBeatmapProcessor : BeatmapProcessor public class OsuBeatmapProcessor : BeatmapProcessor
{ {
public OsuBeatmapProcessor(IBeatmap beatmap) public OsuBeatmapProcessor(IBeatmap beatmap)
: base(beatmap) : base(beatmap)
{ {
} }
public override void PreProcess() public override void PostProcess()
{ {
base.PreProcess(); base.PostProcess();
applyStacking((Beatmap<OsuHitObject>)Beatmap); applyStacking((Beatmap<OsuHitObject>)Beatmap);
} }

View File

@ -0,0 +1,17 @@
// 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.ComponentModel;
namespace osu.Game.Rulesets.Osu.Judgements
{
public enum ComboResult
{
[Description(@"")]
None,
[Description(@"Good")]
Good,
[Description(@"Amazing")]
Perfect
}
}

View File

@ -2,7 +2,6 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Osu.Objects.Drawables;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Osu.Judgements namespace osu.Game.Rulesets.Osu.Judgements
@ -25,7 +24,5 @@ namespace osu.Game.Rulesets.Osu.Judgements
return 300; return 300;
} }
} }
public ComboResult Combo;
} }
} }

View File

@ -0,0 +1,17 @@
// 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.Judgements;
namespace osu.Game.Rulesets.Osu.Judgements
{
public class OsuJudgementResult : JudgementResult
{
public ComboResult ComboType;
public OsuJudgementResult(Judgement judgement)
: base(judgement)
{
}
}
}

View File

@ -2,14 +2,89 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System; using System;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using osu.Framework.Input.States;
using osu.Game.Rulesets.Mods; 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 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 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 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;
}
} }
} }

View File

@ -0,0 +1,53 @@
// 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 System.Collections.Generic;
using osu.Framework.Graphics;
using osu.Game.Graphics;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Osu.Objects;
using OpenTK;
namespace osu.Game.Rulesets.Osu.Mods
{
internal class OsuModTransform : Mod, IApplicableToDrawableHitObjects
{
public override string Name => "Transform";
public override string ShortenedName => "TR";
public override FontAwesome Icon => FontAwesome.fa_arrows;
public override ModType Type => ModType.Fun;
public override string Description => "Everything rotates. EVERYTHING.";
public override double ScoreMultiplier => 1;
public override Type[] IncompatibleMods => new[] { typeof(OsuModWiggle) };
private float theta;
public void ApplyToDrawableHitObjects(IEnumerable<DrawableHitObject> drawables)
{
foreach (var drawable in drawables)
{
var hitObject = (OsuHitObject) drawable.HitObject;
float appearDistance = (float)(hitObject.TimePreempt - hitObject.TimeFadeIn) / 2;
Vector2 originalPosition = drawable.Position;
Vector2 appearOffset = new Vector2((float)Math.Cos(theta), (float)Math.Sin(theta)) * appearDistance;
//the - 1 and + 1 prevents the hit objects to appear in the wrong position.
double appearTime = hitObject.StartTime - hitObject.TimePreempt - 1;
double moveDuration = hitObject.TimePreempt + 1;
using (drawable.BeginAbsoluteSequence(appearTime, true))
{
drawable
.MoveToOffset(appearOffset)
.MoveTo(originalPosition, moveDuration, Easing.InOutSine);
}
theta += (float) hitObject.TimeFadeIn / 1000;
}
}
}
}

View File

@ -0,0 +1,67 @@
// 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 System.Collections.Generic;
using osu.Framework.Graphics;
using osu.Game.Graphics;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Osu.Objects;
using OpenTK;
namespace osu.Game.Rulesets.Osu.Mods
{
internal class OsuModWiggle : Mod, IApplicableToDrawableHitObjects
{
public override string Name => "Wiggle";
public override string ShortenedName => "WG";
public override FontAwesome Icon => FontAwesome.fa_certificate;
public override ModType Type => ModType.Fun;
public override string Description => "They just won't stay still...";
public override double ScoreMultiplier => 1;
public override Type[] IncompatibleMods => new[] { typeof(OsuModTransform) };
private const int wiggle_duration = 90; // (ms) Higher = fewer wiggles
private const int wiggle_strength = 10; // Higher = stronger wiggles
public void ApplyToDrawableHitObjects(IEnumerable<DrawableHitObject> drawables)
{
foreach (var drawable in drawables)
drawable.ApplyCustomUpdateState += drawableOnApplyCustomUpdateState;
}
private void drawableOnApplyCustomUpdateState(DrawableHitObject drawable, ArmedState state)
{
var osuObject = (OsuHitObject)drawable.HitObject;
Vector2 origin = drawable.Position;
Random objRand = new Random((int)osuObject.StartTime);
// Wiggle all objects during TimePreempt
int amountWiggles = (int)osuObject.TimePreempt / wiggle_duration;
void wiggle()
{
float nextAngle = (float)(objRand.NextDouble() * 2 * Math.PI);
float nextDist = (float)(objRand.NextDouble() * wiggle_strength);
drawable.MoveTo(new Vector2((float)(nextDist * Math.Cos(nextAngle) + origin.X), (float)(nextDist * Math.Sin(nextAngle) + origin.Y)), wiggle_duration);
}
for (int i = 0; i < amountWiggles; i++)
using (drawable.BeginAbsoluteSequence(osuObject.StartTime - osuObject.TimePreempt + i * wiggle_duration, true))
wiggle();
// Keep wiggling sliders and spinners for their duration
if (!(osuObject is IHasEndTime endTime))
return;
amountWiggles = (int)(endTime.Duration / wiggle_duration);
for (int i = 0; i < amountWiggles; i++)
using (drawable.BeginAbsoluteSequence(osuObject.StartTime + i * wiggle_duration, true))
wiggle();
}
}
}

View File

@ -6,7 +6,6 @@ using osu.Framework.Graphics;
using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces; using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces;
using OpenTK; using OpenTK;
using osu.Game.Rulesets.Osu.Judgements;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
using OpenTK.Graphics; using OpenTK.Graphics;
@ -40,7 +39,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
if (AllJudged) if (AllJudged)
return false; return false;
UpdateJudgement(true); UpdateResult(true);
return true; return true;
}, },
}, },
@ -77,23 +76,24 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
} }
} }
protected override void CheckForJudgements(bool userTriggered, double timeOffset) protected override void CheckForResult(bool userTriggered, double timeOffset)
{ {
if (!userTriggered) if (!userTriggered)
{ {
if (!HitObject.HitWindows.CanBeHit(timeOffset)) if (!HitObject.HitWindows.CanBeHit(timeOffset))
AddJudgement(new OsuJudgement { Result = HitResult.Miss }); ApplyResult(r => r.Type = HitResult.Miss);
return; return;
} }
var result = HitObject.HitWindows.ResultFor(timeOffset); var result = HitObject.HitWindows.ResultFor(timeOffset);
if (result == HitResult.None) if (result == HitResult.None)
return;
AddJudgement(new OsuJudgement
{ {
Result = result, Shake(Math.Abs(timeOffset) - HitObject.HitWindows.HalfWindowFor(HitResult.Miss));
}); return;
}
ApplyResult(r => r.Type = result);
} }
protected override void UpdatePreemptState() protected override void UpdatePreemptState()

View File

@ -2,27 +2,37 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System; using System;
using System.ComponentModel;
using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Drawables;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using System.Linq; using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Osu.Judgements;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
using osu.Game.Skinning; using osu.Game.Skinning;
using OpenTK.Graphics; using OpenTK.Graphics;
using osu.Game.Graphics.Containers;
namespace osu.Game.Rulesets.Osu.Objects.Drawables namespace osu.Game.Rulesets.Osu.Objects.Drawables
{ {
public class DrawableOsuHitObject : DrawableHitObject<OsuHitObject> 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) protected DrawableOsuHitObject(OsuHitObject hitObject)
: base(hitObject) : base(hitObject)
{ {
base.AddInternal(shakeContainer = new ShakeContainer { RelativeSizeAxes = Axes.Both });
Alpha = 0; 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) protected sealed override void UpdateState(ArmedState state)
{ {
double transformTime = HitObject.StartTime - HitObject.TimePreempt; double transformTime = HitObject.StartTime - HitObject.TimePreempt;
@ -34,7 +44,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
{ {
UpdatePreemptState(); UpdatePreemptState();
var judgementOffset = Math.Min(HitObject.HitWindows.HalfWindowFor(HitResult.Miss), Judgements.FirstOrDefault()?.TimeOffset ?? 0); var judgementOffset = Math.Min(HitObject.HitWindows.HalfWindowFor(HitResult.Miss), Result?.TimeOffset ?? 0);
using (BeginDelayedSequence(HitObject.TimePreempt + judgementOffset, true)) using (BeginDelayedSequence(HitObject.TimePreempt + judgementOffset, true))
UpdateCurrentState(state); UpdateCurrentState(state);
@ -57,20 +67,19 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
// Todo: At some point we need to move these to DrawableHitObject after ensuring that all other Rulesets apply // Todo: At some point we need to move these to DrawableHitObject after ensuring that all other Rulesets apply
// transforms in the same way and don't rely on them not being cleared // transforms in the same way and don't rely on them not being cleared
public override void ClearTransformsAfter(double time, bool propagateChildren = false, string targetMember = null) { } public override void ClearTransformsAfter(double time, bool propagateChildren = false, string targetMember = null)
public override void ApplyTransformsAt(double time, bool propagateChildren = false) { } {
}
public override void ApplyTransformsAt(double time, bool propagateChildren = false)
{
}
private OsuInputManager osuActionInputManager; private OsuInputManager osuActionInputManager;
internal OsuInputManager OsuActionInputManager => osuActionInputManager ?? (osuActionInputManager = GetContainingInputManager() as OsuInputManager); internal OsuInputManager OsuActionInputManager => osuActionInputManager ?? (osuActionInputManager = GetContainingInputManager() as OsuInputManager);
}
public enum ComboResult protected virtual void Shake(double maximumLength) => shakeContainer.Shake(maximumLength);
{
[Description(@"")] protected override JudgementResult CreateResult(Judgement judgement) => new OsuJudgementResult(judgement);
None,
[Description(@"Good")]
Good,
[Description(@"Amazing")]
Perfect
} }
} }

View File

@ -11,14 +11,14 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
{ {
public class DrawableOsuJudgement : DrawableJudgement public class DrawableOsuJudgement : DrawableJudgement
{ {
public DrawableOsuJudgement(Judgement judgement, DrawableHitObject judgedObject) public DrawableOsuJudgement(JudgementResult result, DrawableHitObject judgedObject)
: base(judgement, judgedObject) : base(result, judgedObject)
{ {
} }
protected override void LoadComplete() protected override void LoadComplete()
{ {
if (Judgement.Result != HitResult.Miss) if (Result.Type != HitResult.Miss)
JudgementText?.TransformSpacingTo(new Vector2(14, 0), 1800, Easing.OutQuint); JudgementText?.TransformSpacingTo(new Vector2(14, 0), 1800, Easing.OutQuint);
base.LoadComplete(); base.LoadComplete();

View File

@ -8,7 +8,6 @@ using osu.Framework.MathUtils;
using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Drawables;
using OpenTK; using OpenTK;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Rulesets.Osu.Judgements;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
using osu.Game.Skinning; using osu.Game.Skinning;
@ -42,10 +41,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
}; };
} }
protected override void CheckForJudgements(bool userTriggered, double timeOffset) protected override void CheckForResult(bool userTriggered, double timeOffset)
{ {
if (repeatPoint.StartTime <= Time.Current) if (repeatPoint.StartTime <= Time.Current)
AddJudgement(new OsuJudgement { Result = drawableSlider.Tracking ? HitResult.Great : HitResult.Miss }); ApplyResult(r => r.Type = drawableSlider.Tracking ? HitResult.Great : HitResult.Miss);
} }
protected override void UpdatePreemptState() protected override void UpdatePreemptState()

View File

@ -9,7 +9,6 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Osu.Judgements;
using osu.Game.Configuration; using osu.Game.Configuration;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
using OpenTK.Graphics; using OpenTK.Graphics;
@ -45,14 +44,17 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
}, },
ticks = new Container<DrawableSliderTick> { RelativeSizeAxes = Axes.Both }, ticks = new Container<DrawableSliderTick> { RelativeSizeAxes = Axes.Both },
repeatPoints = new Container<DrawableRepeatPoint> { RelativeSizeAxes = Axes.Both }, repeatPoints = new Container<DrawableRepeatPoint> { RelativeSizeAxes = Axes.Both },
Ball = new SliderBall(s) Ball = new SliderBall(s, this)
{ {
BypassAutoSizeAxes = Axes.Both, BypassAutoSizeAxes = Axes.Both,
Scale = new Vector2(s.Scale), Scale = new Vector2(s.Scale),
AlwaysPresent = true, AlwaysPresent = true,
Alpha = 0 Alpha = 0
}, },
HeadCircle = new DrawableSliderHead(s, s.HeadCircle), HeadCircle = new DrawableSliderHead(s, s.HeadCircle)
{
OnShake = Shake
},
TailCircle = new DrawableSliderTail(s, s.TailCircle) TailCircle = new DrawableSliderTail(s, s.TailCircle)
}; };
@ -132,23 +134,27 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
} }
} }
protected override void CheckForJudgements(bool userTriggered, double timeOffset) protected override void CheckForResult(bool userTriggered, double timeOffset)
{ {
if (!userTriggered && Time.Current >= slider.EndTime) if (userTriggered || Time.Current < slider.EndTime)
return;
ApplyResult(r =>
{ {
var judgementsCount = NestedHitObjects.Count(); var judgementsCount = NestedHitObjects.Count();
var judgementsHit = NestedHitObjects.Count(h => h.IsHit); var judgementsHit = NestedHitObjects.Count(h => h.IsHit);
var hitFraction = (double)judgementsHit / judgementsCount; var hitFraction = (double)judgementsHit / judgementsCount;
if (hitFraction == 1 && HeadCircle.Judgements.Any(j => j.Result == HitResult.Great))
AddJudgement(new OsuJudgement { Result = HitResult.Great }); if (hitFraction == 1 && HeadCircle.Result.Type == HitResult.Great)
else if (hitFraction >= 0.5 && HeadCircle.Judgements.Any(j => j.Result >= HitResult.Good)) r.Type = HitResult.Great;
AddJudgement(new OsuJudgement { Result = HitResult.Good }); else if (hitFraction >= 0.5 && HeadCircle.Result.Type >= HitResult.Good)
r.Type = HitResult.Good;
else if (hitFraction > 0) else if (hitFraction > 0)
AddJudgement(new OsuJudgement { Result = HitResult.Meh }); r.Type = HitResult.Meh;
else else
AddJudgement(new OsuJudgement { Result = HitResult.Miss }); r.Type = HitResult.Miss;
} });
} }
protected override void UpdateCurrentState(ArmedState state) protected override void UpdateCurrentState(ArmedState state)

View File

@ -1,6 +1,7 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Objects.Types;
using OpenTK; using OpenTK;
@ -28,5 +29,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
if (!IsHit) if (!IsHit)
Position = slider.CurvePositionAt(completionProgress); Position = slider.CurvePositionAt(completionProgress);
} }
public Action<double> OnShake;
protected override void Shake(double maximumLength) => OnShake?.Invoke(maximumLength);
} }
} }

View File

@ -2,7 +2,6 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Game.Rulesets.Osu.Judgements;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Osu.Objects.Drawables namespace osu.Game.Rulesets.Osu.Objects.Drawables
@ -12,11 +11,11 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
/// <summary> /// <summary>
/// The judgement text is provided by the <see cref="DrawableSlider"/>. /// The judgement text is provided by the <see cref="DrawableSlider"/>.
/// </summary> /// </summary>
public override bool DisplayJudgement => false; public override bool DisplayResult => false;
public bool Tracking { get; set; } public bool Tracking { get; set; }
public DrawableSliderTail(Slider slider, HitCircle hitCircle) public DrawableSliderTail(Slider slider, SliderTailCircle hitCircle)
: base(hitCircle) : base(hitCircle)
{ {
Origin = Anchor.Centre; Origin = Anchor.Centre;
@ -29,10 +28,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
Position = HitObject.Position - slider.Position; Position = HitObject.Position - slider.Position;
} }
protected override void CheckForJudgements(bool userTriggered, double timeOffset) protected override void CheckForResult(bool userTriggered, double timeOffset)
{ {
if (!userTriggered && timeOffset >= 0) if (!userTriggered && timeOffset >= 0)
AddJudgement(new OsuSliderTailJudgement { Result = Tracking ? HitResult.Great : HitResult.Miss }); ApplyResult(r => r.Type = Tracking ? HitResult.Great : HitResult.Miss);
} }
} }
} }

View File

@ -6,7 +6,6 @@ using osu.Game.Rulesets.Objects.Drawables;
using OpenTK; using OpenTK;
using OpenTK.Graphics; using OpenTK.Graphics;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Game.Rulesets.Osu.Judgements;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
using osu.Game.Skinning; using osu.Game.Skinning;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
@ -19,7 +18,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
public bool Tracking { get; set; } public bool Tracking { get; set; }
public override bool DisplayJudgement => false; public override bool DisplayResult => false;
public DrawableSliderTick(SliderTick sliderTick) : base(sliderTick) public DrawableSliderTick(SliderTick sliderTick) : base(sliderTick)
{ {
@ -48,10 +47,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
}; };
} }
protected override void CheckForJudgements(bool userTriggered, double timeOffset) protected override void CheckForResult(bool userTriggered, double timeOffset)
{ {
if (timeOffset >= 0) if (timeOffset >= 0)
AddJudgement(new OsuJudgement { Result = Tracking ? HitResult.Great : HitResult.Miss }); ApplyResult(r => r.Type = Tracking ? HitResult.Great : HitResult.Miss);
} }
protected override void UpdatePreemptState() protected override void UpdatePreemptState()

View File

@ -11,7 +11,6 @@ using OpenTK.Graphics;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Game.Rulesets.Osu.Judgements;
using osu.Game.Screens.Ranking; using osu.Game.Screens.Ranking;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
@ -117,7 +116,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
public float Progress => MathHelper.Clamp(Disc.RotationAbsolute / 360 / Spinner.SpinsRequired, 0, 1); public float Progress => MathHelper.Clamp(Disc.RotationAbsolute / 360 / Spinner.SpinsRequired, 0, 1);
protected override void CheckForJudgements(bool userTriggered, double timeOffset) protected override void CheckForResult(bool userTriggered, double timeOffset)
{ {
if (Time.Current < HitObject.StartTime) return; if (Time.Current < HitObject.StartTime) return;
@ -136,17 +135,20 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
glow.FadeColour(completeColour, duration); glow.FadeColour(completeColour, duration);
} }
if (!userTriggered && Time.Current >= Spinner.EndTime) if (userTriggered || Time.Current < Spinner.EndTime)
return;
ApplyResult(r =>
{ {
if (Progress >= 1) if (Progress >= 1)
AddJudgement(new OsuJudgement { Result = HitResult.Great }); r.Type = HitResult.Great;
else if (Progress > .9) else if (Progress > .9)
AddJudgement(new OsuJudgement { Result = HitResult.Good }); r.Type = HitResult.Good;
else if (Progress > .75) else if (Progress > .75)
AddJudgement(new OsuJudgement { Result = HitResult.Meh }); r.Type = HitResult.Meh;
else if (Time.Current >= Spinner.EndTime) else if (Time.Current >= Spinner.EndTime)
AddJudgement(new OsuJudgement { Result = HitResult.Miss }); r.Type = HitResult.Miss;
} });
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]

View File

@ -8,7 +8,6 @@ using osu.Framework.Graphics.Shapes;
using osu.Framework.Input.EventArgs; using osu.Framework.Input.EventArgs;
using osu.Framework.Input.States; using osu.Framework.Input.States;
using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Objects.Types;
using OpenTK;
using OpenTK.Graphics; using OpenTK.Graphics;
using osu.Game.Skinning; using osu.Game.Skinning;
@ -37,9 +36,11 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
private readonly Slider slider; private readonly Slider slider;
public readonly Drawable FollowCircle; public readonly Drawable FollowCircle;
private Drawable drawableBall; private Drawable drawableBall;
private readonly DrawableSlider drawableSlider;
public SliderBall(Slider slider) public SliderBall(Slider slider, DrawableSlider drawableSlider = null)
{ {
this.drawableSlider = drawableSlider;
this.slider = slider; this.slider = slider;
Masking = true; Masking = true;
AutoSizeAxes = Axes.Both; AutoSizeAxes = Axes.Both;
@ -121,9 +122,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
return base.OnMouseMove(state); 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) 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 // 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. // Make sure to use the base version of ReceiveMouseInputAt so that we correctly check the position.
Tracking = canCurrentlyTrack Tracking = canCurrentlyTrack
&& lastState != null && lastState != null
&& base.ReceiveMouseInputAt(lastState.Mouse.NativeState.Position) && ReceiveMouseInputAt(lastState.Mouse.NativeState.Position)
&& ((Parent as DrawableSlider)?.OsuActionInputManager?.PressedActions.Any(x => x == OsuAction.LeftButton || x == OsuAction.RightButton) ?? false); && (drawableSlider?.OsuActionInputManager?.PressedActions.Any(x => x == OsuAction.LeftButton || x == OsuAction.RightButton) ?? false);
} }
} }

View File

@ -7,14 +7,15 @@ using osu.Framework.Allocation;
using osu.Framework.Configuration; using osu.Framework.Configuration;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.OpenGL.Textures;
using osu.Framework.Graphics.Lines; using osu.Framework.Graphics.Lines;
using osu.Framework.Graphics.Textures; using osu.Framework.Graphics.Textures;
using OpenTK;
using OpenTK.Graphics.ES30; using OpenTK.Graphics.ES30;
using OpenTK.Graphics; using OpenTK.Graphics;
using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.Primitives;
using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Objects.Types;
using OpenTK;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.PixelFormats;
namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces 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; } public double? SnakedEnd { get; private set; }
private Color4 accentColour = Color4.White; private Color4 accentColour = Color4.White;
/// <summary> /// <summary>
/// Used to colour the path. /// Used to colour the path.
/// </summary> /// </summary>
@ -61,6 +63,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
} }
private Color4 borderColour = Color4.White; private Color4 borderColour = Color4.White;
/// <summary> /// <summary>
/// Used to colour the path border. /// Used to colour the path border.
/// </summary> /// </summary>
@ -85,6 +88,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
private Vector2 topLeftOffset; private Vector2 topLeftOffset;
private readonly Slider slider; private readonly Slider slider;
public SliderBody(Slider s) public SliderBody(Slider s)
{ {
slider = s; slider = s;
@ -139,8 +143,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
var texture = new Texture(textureWidth, 1); var texture = new Texture(textureWidth, 1);
//initialise background //initialise background
var raw = new RawTexture(textureWidth, 1); var raw = new Image<Rgba32>(textureWidth, 1);
var bytes = raw.Data;
const float aa_portion = 0.02f; const float aa_portion = 0.02f;
const float border_portion = 0.128f; const float border_portion = 0.128f;
@ -155,19 +158,13 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
if (progress <= border_portion) if (progress <= border_portion)
{ {
bytes[i * 4] = (byte)(BorderColour.R * 255); raw[i, 0] = new Rgba32(BorderColour.R, BorderColour.G, BorderColour.B, Math.Min(progress / aa_portion, 1) * BorderColour.A);
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));
} }
else else
{ {
progress -= border_portion; progress -= border_portion;
raw[i, 0] = new Rgba32(AccentColour.R, AccentColour.G, AccentColour.B,
bytes[i * 4] = (byte)(AccentColour.R * 255); (opacity_at_edge - (opacity_at_edge - opacity_at_centre) * progress / gradient_portion) * AccentColour.A);
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));
} }
} }

View File

@ -1,9 +1,13 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Osu.Judgements;
namespace osu.Game.Rulesets.Osu.Objects namespace osu.Game.Rulesets.Osu.Objects
{ {
public class HitCircle : OsuHitObject public class HitCircle : OsuHitObject
{ {
public override Judgement CreateJudgement() => new OsuJudgement();
} }
} }

View File

@ -54,6 +54,8 @@ namespace osu.Game.Rulesets.Osu.Objects
public virtual bool NewCombo { get; set; } public virtual bool NewCombo { get; set; }
public int ComboOffset { get; set; }
public virtual int IndexInCurrentCombo { get; set; } public virtual int IndexInCurrentCombo { get; set; }
public virtual int ComboIndex { get; set; } public virtual int ComboIndex { get; set; }

View File

@ -4,6 +4,8 @@
using System; using System;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Osu.Judgements;
namespace osu.Game.Rulesets.Osu.Objects namespace osu.Game.Rulesets.Osu.Objects
{ {
@ -24,5 +26,7 @@ namespace osu.Game.Rulesets.Osu.Objects
if (RepeatIndex > 0) if (RepeatIndex > 0)
TimePreempt = Math.Min(SpanDuration * 2, TimePreempt); TimePreempt = Math.Min(SpanDuration * 2, TimePreempt);
} }
public override Judgement CreateJudgement() => new OsuJudgement();
} }
} }

View File

@ -10,6 +10,8 @@ using System.Linq;
using osu.Game.Audio; using osu.Game.Audio;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Osu.Judgements;
namespace osu.Game.Rulesets.Osu.Objects namespace osu.Game.Rulesets.Osu.Objects
{ {
@ -94,7 +96,7 @@ namespace osu.Game.Rulesets.Osu.Objects
public double TickDistance; public double TickDistance;
public HitCircle HeadCircle; public HitCircle HeadCircle;
public HitCircle TailCircle; public SliderTailCircle TailCircle;
protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty)
{ {
@ -133,7 +135,7 @@ namespace osu.Game.Rulesets.Osu.Objects
ComboIndex = ComboIndex, ComboIndex = ComboIndex,
}; };
TailCircle = new SliderCircle(this) TailCircle = new SliderTailCircle(this)
{ {
StartTime = EndTime, StartTime = EndTime,
Position = EndPosition, Position = EndPosition,
@ -211,5 +213,7 @@ namespace osu.Game.Rulesets.Osu.Objects
}); });
} }
} }
public override Judgement CreateJudgement() => new OsuJudgement();
} }
} }

View File

@ -0,0 +1,18 @@
// 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.Judgements;
using osu.Game.Rulesets.Osu.Judgements;
namespace osu.Game.Rulesets.Osu.Objects
{
public class SliderTailCircle : SliderCircle
{
public SliderTailCircle(Slider slider)
: base(slider)
{
}
public override Judgement CreateJudgement() => new OsuSliderTailJudgement();
}
}

View File

@ -3,6 +3,8 @@
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Osu.Judgements;
namespace osu.Game.Rulesets.Osu.Objects namespace osu.Game.Rulesets.Osu.Objects
{ {
@ -26,5 +28,7 @@ namespace osu.Game.Rulesets.Osu.Objects
TimePreempt = (StartTime - SpanStartTime) / 2 + offset; TimePreempt = (StartTime - SpanStartTime) / 2 + offset;
} }
public override Judgement CreateJudgement() => new OsuJudgement();
} }
} }

View File

@ -5,6 +5,8 @@ using System;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Objects.Types;
using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Osu.Judgements;
namespace osu.Game.Rulesets.Osu.Objects namespace osu.Game.Rulesets.Osu.Objects
{ {
@ -18,8 +20,6 @@ namespace osu.Game.Rulesets.Osu.Objects
/// </summary> /// </summary>
public int SpinsRequired { get; protected set; } = 1; public int SpinsRequired { get; protected set; } = 1;
public override bool NewCombo => true;
protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty)
{ {
base.ApplyDefaultsToSelf(controlPointInfo, difficulty); base.ApplyDefaultsToSelf(controlPointInfo, difficulty);
@ -29,5 +29,7 @@ namespace osu.Game.Rulesets.Osu.Objects
// spinning doesn't match 1:1 with stable, so let's fudge them easier for the time being. // spinning doesn't match 1:1 with stable, so let's fudge them easier for the time being.
SpinsRequired = (int)Math.Max(1, SpinsRequired * 0.6); SpinsRequired = (int)Math.Max(1, SpinsRequired * 0.6);
} }
public override Judgement CreateJudgement() => new OsuJudgement();
} }
} }

View File

@ -4,6 +4,8 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel; using System.ComponentModel;
using osu.Framework.Input.Bindings; using osu.Framework.Input.Bindings;
using osu.Framework.Input.EventArgs;
using osu.Framework.Input.States;
using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI;
namespace osu.Game.Rulesets.Osu namespace osu.Game.Rulesets.Osu
@ -12,8 +14,35 @@ namespace osu.Game.Rulesets.Osu
{ {
public IEnumerable<OsuAction> PressedActions => KeyBindingContainer.PressedActions; 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")] [Description("Left Button")]
LeftButton, LeftButton,
[Description("Right Button")] [Description("Right Button")]
RightButton RightButton
} }

View File

@ -117,6 +117,11 @@ namespace osu.Game.Rulesets.Osu
new OsuModRelax(), new OsuModRelax(),
new OsuModAutopilot(), new OsuModAutopilot(),
}; };
case ModType.Fun:
return new Mod[] {
new OsuModTransform(),
new OsuModWiggle(),
};
default: default:
return new Mod[] { }; return new Mod[] { };
} }

View File

@ -2,13 +2,11 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using osu.Framework.Extensions; using osu.Framework.Extensions;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Osu.Judgements; using osu.Game.Rulesets.Osu.Judgements;
using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI;
@ -26,28 +24,11 @@ namespace osu.Game.Rulesets.Osu.Scoring
private readonly Dictionary<HitResult, int> scoreResultCounts = new Dictionary<HitResult, int>(); private readonly Dictionary<HitResult, int> scoreResultCounts = new Dictionary<HitResult, int>();
private readonly Dictionary<ComboResult, int> comboResultCounts = new Dictionary<ComboResult, int>(); private readonly Dictionary<ComboResult, int> comboResultCounts = new Dictionary<ComboResult, int>();
protected override void SimulateAutoplay(Beatmap<OsuHitObject> beatmap) protected override void ApplyBeatmap(Beatmap<OsuHitObject> beatmap)
{ {
base.ApplyBeatmap(beatmap);
hpDrainRate = beatmap.BeatmapInfo.BaseDifficulty.DrainRate; hpDrainRate = beatmap.BeatmapInfo.BaseDifficulty.DrainRate;
foreach (var obj in beatmap.HitObjects)
{
if (obj is Slider slider)
{
// Head
AddJudgement(new OsuJudgement { Result = HitResult.Great });
// Ticks
foreach (var unused in slider.NestedHitObjects.OfType<SliderTick>())
AddJudgement(new OsuJudgement { Result = HitResult.Great });
//Repeats
foreach (var unused in slider.NestedHitObjects.OfType<RepeatPoint>())
AddJudgement(new OsuJudgement { Result = HitResult.Great });
}
AddJudgement(new OsuJudgement { Result = HitResult.Great });
}
} }
protected override void Reset(bool storeResults) protected override void Reset(bool storeResults)
@ -70,19 +51,19 @@ namespace osu.Game.Rulesets.Osu.Scoring
private const double harshness = 0.01; private const double harshness = 0.01;
protected override void OnNewJudgement(Judgement judgement) protected override void ApplyResult(JudgementResult result)
{ {
base.OnNewJudgement(judgement); base.ApplyResult(result);
var osuJudgement = (OsuJudgement)judgement; var osuResult = (OsuJudgementResult)result;
if (judgement.Result != HitResult.None) if (result.Type != HitResult.None)
{ {
scoreResultCounts[judgement.Result] = scoreResultCounts.GetOrDefault(judgement.Result) + 1; scoreResultCounts[result.Type] = scoreResultCounts.GetOrDefault(result.Type) + 1;
comboResultCounts[osuJudgement.Combo] = comboResultCounts.GetOrDefault(osuJudgement.Combo) + 1; comboResultCounts[osuResult.ComboType] = comboResultCounts.GetOrDefault(osuResult.ComboType) + 1;
} }
switch (judgement.Result) switch (result.Type)
{ {
case HitResult.Great: case HitResult.Great:
Health.Value += (10.2 - hpDrainRate) * harshness; Health.Value += (10.2 - hpDrainRate) * harshness;
@ -105,5 +86,7 @@ namespace osu.Game.Rulesets.Osu.Scoring
break; break;
} }
} }
protected override JudgementResult CreateResult(Judgement judgement) => new OsuJudgementResult(judgement);
} }
} }

View File

@ -197,7 +197,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
if (Shared.VertexBuffer == null) if (Shared.VertexBuffer == null)
Shared.VertexBuffer = new QuadVertexBuffer<TexturedTrailVertex>(max_sprites, BufferUsageHint.DynamicDraw); 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; int updateStart = -1, updateEnd = 0;
for (int i = 0; i < Parts.Length; ++i) for (int i = 0; i < Parts.Length; ++i)
@ -216,7 +216,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
Texture.DrawQuad( Texture.DrawQuad(
new Quad(pos.X - Size.X / 2, pos.Y - Size.Y / 2, Size.X, Size.Y), new Quad(pos.X - Size.X / 2, pos.Y - Size.Y / 2, Size.X, Size.Y),
DrawInfo.Colour, DrawColourInfo.Colour,
null, null,
v => Shared.VertexBuffer.Vertices[end++] = new TexturedTrailVertex v => Shared.VertexBuffer.Vertices[end++] = new TexturedTrailVertex
{ {

View File

@ -50,7 +50,7 @@ namespace osu.Game.Rulesets.Osu.UI
public override void Add(DrawableHitObject h) public override void Add(DrawableHitObject h)
{ {
h.OnJudgement += onJudgement; h.OnNewResult += onNewResult;
var c = h as IDrawableHitObjectWithProxiedApproach; var c = h as IDrawableHitObjectWithProxiedApproach;
if (c != null) if (c != null)
@ -61,15 +61,15 @@ namespace osu.Game.Rulesets.Osu.UI
public override void PostProcess() 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 onJudgement(DrawableHitObject judgedObject, Judgement judgement) private void onNewResult(DrawableHitObject judgedObject, JudgementResult result)
{ {
if (!judgedObject.DisplayJudgement || !DisplayJudgements) if (!judgedObject.DisplayResult || !DisplayJudgements)
return; return;
DrawableOsuJudgement explosion = new DrawableOsuJudgement(judgement, judgedObject) DrawableOsuJudgement explosion = new DrawableOsuJudgement(result, judgedObject)
{ {
Origin = Anchor.Centre, Origin = Anchor.Centre,
Position = ((OsuHitObject)judgedObject.HitObject).StackedEndPosition Position = ((OsuHitObject)judgedObject.HitObject).StackedEndPosition

View File

@ -8,9 +8,9 @@ using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.MathUtils; using osu.Framework.MathUtils;
using osu.Framework.Timing;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Taiko.Judgements; using osu.Game.Rulesets.Taiko.Judgements;
@ -39,8 +39,10 @@ namespace osu.Game.Rulesets.Taiko.Tests
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load() private void load()
{ {
AddStep("Hit!", () => addHitJudgement(false)); AddStep("Hit", () => addHitJudgement(false));
AddStep("Strong hit", () => addStrongHitJudgement(false));
AddStep("Kiai hit", () => addHitJudgement(true)); AddStep("Kiai hit", () => addHitJudgement(true));
AddStep("Strong kiai hit", () => addStrongHitJudgement(true));
AddStep("Miss :(", addMissJudgement); AddStep("Miss :(", addMissJudgement);
AddStep("DrumRoll", () => addDrumRoll(false)); AddStep("DrumRoll", () => addDrumRoll(false));
AddStep("Strong DrumRoll", () => addDrumRoll(true)); AddStep("Strong DrumRoll", () => addDrumRoll(true));
@ -78,15 +80,12 @@ namespace osu.Game.Rulesets.Taiko.Tests
ControlPointInfo = controlPointInfo ControlPointInfo = controlPointInfo
}); });
var rateAdjustClock = new StopwatchClock(true) { Rate = 1 };
Add(playfieldContainer = new Container Add(playfieldContainer = new Container
{ {
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
Origin = Anchor.Centre, Origin = Anchor.Centre,
RelativeSizeAxes = Axes.X, RelativeSizeAxes = Axes.X,
Height = 768, Height = 768,
Clock = new FramedClock(rateAdjustClock),
Children = new[] { rulesetContainer = new TaikoRulesetContainer(new TaikoRuleset(), beatmap) } Children = new[] { rulesetContainer = new TaikoRulesetContainer(new TaikoRuleset(), beatmap) }
}); });
} }
@ -133,28 +132,35 @@ namespace osu.Game.Rulesets.Taiko.Tests
HitResult hitResult = RNG.Next(2) == 0 ? HitResult.Good : HitResult.Great; HitResult hitResult = RNG.Next(2) == 0 ? HitResult.Good : HitResult.Great;
var cpi = new ControlPointInfo(); var cpi = new ControlPointInfo();
cpi.EffectPoints.Add(new EffectControlPoint cpi.EffectPoints.Add(new EffectControlPoint { KiaiMode = kiai });
{
KiaiMode = kiai
});
Hit hit = new Hit(); Hit hit = new Hit();
hit.ApplyDefaults(cpi, new BeatmapDifficulty()); hit.ApplyDefaults(cpi, new BeatmapDifficulty());
var h = new DrawableTestHit(hit) { X = RNG.NextSingle(hitResult == HitResult.Good ? -0.1f : -0.05f, hitResult == HitResult.Good ? 0.1f : 0.05f) }; var h = new DrawableTestHit(hit) { X = RNG.NextSingle(hitResult == HitResult.Good ? -0.1f : -0.05f, hitResult == HitResult.Good ? 0.1f : 0.05f) };
((TaikoPlayfield)rulesetContainer.Playfield).OnJudgement(h, new TaikoJudgement { Result = hitResult }); ((TaikoPlayfield)rulesetContainer.Playfield).OnNewResult(h, new JudgementResult(new TaikoJudgement()) { Type = hitResult });
}
if (RNG.Next(10) == 0) private void addStrongHitJudgement(bool kiai)
{ {
((TaikoPlayfield)rulesetContainer.Playfield).OnJudgement(h, new TaikoJudgement { Result = hitResult }); HitResult hitResult = RNG.Next(2) == 0 ? HitResult.Good : HitResult.Great;
((TaikoPlayfield)rulesetContainer.Playfield).OnJudgement(h, new TaikoStrongHitJudgement());
} var cpi = new ControlPointInfo();
cpi.EffectPoints.Add(new EffectControlPoint { KiaiMode = kiai });
Hit hit = new Hit();
hit.ApplyDefaults(cpi, new BeatmapDifficulty());
var h = new DrawableTestHit(hit) { X = RNG.NextSingle(hitResult == HitResult.Good ? -0.1f : -0.05f, hitResult == HitResult.Good ? 0.1f : 0.05f) };
((TaikoPlayfield)rulesetContainer.Playfield).OnNewResult(h, new JudgementResult(new TaikoJudgement()) { Type = hitResult });
((TaikoPlayfield)rulesetContainer.Playfield).OnNewResult(new TestStrongNestedHit(h), new JudgementResult(new TaikoStrongJudgement()) { Type = HitResult.Great });
} }
private void addMissJudgement() private void addMissJudgement()
{ {
((TaikoPlayfield)rulesetContainer.Playfield).OnJudgement(new DrawableTestHit(new Hit()), new TaikoJudgement { Result = HitResult.Miss }); ((TaikoPlayfield)rulesetContainer.Playfield).OnNewResult(new DrawableTestHit(new Hit()), new JudgementResult(new TaikoJudgement()) { Type = HitResult.Miss });
} }
private void addBarLine(bool major, double delay = scroll_time) private void addBarLine(bool major, double delay = scroll_time)
@ -204,10 +210,7 @@ namespace osu.Game.Rulesets.Taiko.Tests
h.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); h.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
if (strong) rulesetContainer.Playfield.Add(new DrawableCentreHit(h));
rulesetContainer.Playfield.Add(new DrawableCentreHitStrong(h));
else
rulesetContainer.Playfield.Add(new DrawableCentreHit(h));
} }
private void addRimHit(bool strong) private void addRimHit(bool strong)
@ -220,10 +223,17 @@ namespace osu.Game.Rulesets.Taiko.Tests
h.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); h.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
if (strong) rulesetContainer.Playfield.Add(new DrawableRimHit(h));
rulesetContainer.Playfield.Add(new DrawableRimHitStrong(h)); }
else
rulesetContainer.Playfield.Add(new DrawableRimHit(h)); private class TestStrongNestedHit : DrawableStrongNestedHit
{
public TestStrongNestedHit(DrawableHitObject mainObject)
: base(null, mainObject)
{
}
public override bool OnPressed(TaikoAction action) => false;
} }
private class DrawableTestHit : DrawableHitObject<TaikoHitObject> private class DrawableTestHit : DrawableHitObject<TaikoHitObject>

View File

@ -62,7 +62,7 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
converted.HitObjects = converted.HitObjects.GroupBy(t => t.StartTime).Select(x => converted.HitObjects = converted.HitObjects.GroupBy(t => t.StartTime).Select(x =>
{ {
TaikoHitObject first = x.First(); TaikoHitObject first = x.First();
if (x.Skip(1).Any()) if (x.Skip(1).Any() && !(first is Swell))
first.IsStrong = true; first.IsStrong = true;
return first; return first;
}).ToList(); }).ToList();
@ -168,7 +168,6 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
{ {
StartTime = obj.StartTime, StartTime = obj.StartTime,
Samples = obj.Samples, Samples = obj.Samples,
IsStrong = strong,
Duration = endTimeData.Duration, Duration = endTimeData.Duration,
RequiredHits = (int)Math.Max(1, endTimeData.Duration / 1000 * hitMultiplier) RequiredHits = (int)Math.Max(1, endTimeData.Duration / 1000 * hitMultiplier)
}; };

View File

@ -7,15 +7,10 @@ namespace osu.Game.Rulesets.Taiko.Judgements
{ {
public class TaikoIntermediateSwellJudgement : TaikoJudgement public class TaikoIntermediateSwellJudgement : TaikoJudgement
{ {
public override HitResult MaxResult => HitResult.Perfect; public override HitResult MaxResult => HitResult.Great;
public override bool AffectsCombo => false; public override bool AffectsCombo => false;
public TaikoIntermediateSwellJudgement()
{
Final = false;
}
/// <summary> /// <summary>
/// Computes the numeric result value for the combo portion of the score. /// Computes the numeric result value for the combo portion of the score.
/// </summary> /// </summary>

View File

@ -3,13 +3,8 @@
namespace osu.Game.Rulesets.Taiko.Judgements namespace osu.Game.Rulesets.Taiko.Judgements
{ {
public class TaikoStrongHitJudgement : TaikoJudgement public class TaikoStrongJudgement : TaikoJudgement
{ {
public override bool AffectsCombo => false; public override bool AffectsCombo => false;
public TaikoStrongHitJudgement()
{
Final = true;
}
} }
} }

View File

@ -9,7 +9,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
{ {
public class DrawableCentreHit : DrawableHit public class DrawableCentreHit : DrawableHit
{ {
protected override TaikoAction[] HitActions { get; } = { TaikoAction.LeftCentre, TaikoAction.RightCentre }; public override TaikoAction[] HitActions { get; } = { TaikoAction.LeftCentre, TaikoAction.RightCentre };
public DrawableCentreHit(Hit hit) public DrawableCentreHit(Hit hit)
: base(hit) : base(hit)

View File

@ -1,26 +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.Allocation;
using osu.Game.Graphics;
using osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces;
namespace osu.Game.Rulesets.Taiko.Objects.Drawables
{
public class DrawableCentreHitStrong : DrawableHitStrong
{
protected override TaikoAction[] HitActions { get; } = { TaikoAction.LeftCentre, TaikoAction.RightCentre };
public DrawableCentreHitStrong(Hit hit)
: base(hit)
{
MainPiece.Add(new CentreHitSymbolPiece());
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
MainPiece.AccentColour = colours.PinkDarker;
}
}
}

View File

@ -6,7 +6,6 @@ using osu.Framework.Allocation;
using osu.Framework.MathUtils; using osu.Framework.MathUtils;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Taiko.Judgements;
using OpenTK; using OpenTK;
using OpenTK.Graphics; using OpenTK.Graphics;
using osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces; using osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces;
@ -40,7 +39,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
foreach (var tick in drumRoll.NestedHitObjects.OfType<DrumRollTick>()) foreach (var tick in drumRoll.NestedHitObjects.OfType<DrumRollTick>())
{ {
var newTick = new DrawableDrumRollTick(tick); var newTick = new DrawableDrumRollTick(tick);
newTick.OnJudgement += onTickJudgement; newTick.OnNewResult += onNewTickResult;
AddNested(newTick); AddNested(newTick);
tickContainer.Add(newTick); tickContainer.Add(newTick);
@ -61,9 +60,9 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
colourEngaged = colours.YellowDarker; colourEngaged = colours.YellowDarker;
} }
private void onTickJudgement(DrawableHitObject obj, Judgement judgement) private void onNewTickResult(DrawableHitObject obj, JudgementResult result)
{ {
if (judgement.Result > HitResult.Miss) if (result.Type > HitResult.Miss)
rollingHits++; rollingHits++;
else else
rollingHits--; rollingHits--;
@ -74,7 +73,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
MainPiece.FadeAccent(newColour, 100); MainPiece.FadeAccent(newColour, 100);
} }
protected override void CheckForJudgements(bool userTriggered, double timeOffset) protected override void CheckForResult(bool userTriggered, double timeOffset)
{ {
if (userTriggered) if (userTriggered)
return; return;
@ -84,13 +83,9 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
int countHit = NestedHitObjects.Count(o => o.IsHit); int countHit = NestedHitObjects.Count(o => o.IsHit);
if (countHit >= HitObject.RequiredGoodHits) if (countHit >= HitObject.RequiredGoodHits)
{ ApplyResult(r => r.Type = countHit >= HitObject.RequiredGreatHits ? HitResult.Great : HitResult.Good);
AddJudgement(new TaikoJudgement { Result = countHit >= HitObject.RequiredGreatHits ? HitResult.Great : HitResult.Good });
if (HitObject.IsStrong)
AddJudgement(new TaikoStrongHitJudgement());
}
else else
AddJudgement(new TaikoJudgement { Result = HitResult.Miss }); ApplyResult(r => r.Type = HitResult.Miss);
} }
protected override void UpdateState(ArmedState state) protected override void UpdateState(ArmedState state)
@ -103,5 +98,25 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
break; break;
} }
} }
protected override DrawableStrongNestedHit CreateStrongHit(StrongHitObject hitObject) => new StrongNestedHit(hitObject, this);
private class StrongNestedHit : DrawableStrongNestedHit
{
public StrongNestedHit(StrongHitObject strong, DrawableDrumRoll drumRoll)
: base(strong, drumRoll)
{
}
protected override void CheckForResult(bool userTriggered, double timeOffset)
{
if (!MainObject.Judged)
return;
ApplyResult(r => r.Type = MainObject.IsHit ? HitResult.Great : HitResult.Miss);
}
public override bool OnPressed(TaikoAction action) => false;
}
} }
} }

View File

@ -5,7 +5,6 @@ using System;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.Taiko.Judgements;
using osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces; using osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces;
namespace osu.Game.Rulesets.Taiko.Objects.Drawables namespace osu.Game.Rulesets.Taiko.Objects.Drawables
@ -18,24 +17,26 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
FillMode = FillMode.Fit; FillMode = FillMode.Fit;
} }
public override bool DisplayJudgement => false; public override bool DisplayResult => false;
protected override TaikoPiece CreateMainPiece() => new TickPiece protected override TaikoPiece CreateMainPiece() => new TickPiece
{ {
Filled = HitObject.FirstTick Filled = HitObject.FirstTick
}; };
protected override void CheckForJudgements(bool userTriggered, double timeOffset) protected override void CheckForResult(bool userTriggered, double timeOffset)
{ {
if (!userTriggered) if (!userTriggered)
{
if (timeOffset > HitObject.HitWindow)
ApplyResult(r => r.Type = HitResult.Miss);
return;
}
if (Math.Abs(timeOffset) > HitObject.HitWindow)
return; return;
if (!(Math.Abs(timeOffset) < HitObject.HitWindow)) ApplyResult(r => r.Type = HitResult.Great);
return;
AddJudgement(new TaikoDrumRollTickJudgement { Result = HitResult.Great });
if (HitObject.IsStrong)
AddJudgement(new TaikoStrongHitJudgement());
} }
protected override void UpdateState(ArmedState state) protected override void UpdateState(ArmedState state)
@ -48,6 +49,26 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
} }
} }
public override bool OnPressed(TaikoAction action) => UpdateJudgement(true); public override bool OnPressed(TaikoAction action) => UpdateResult(true);
protected override DrawableStrongNestedHit CreateStrongHit(StrongHitObject hitObject) => new StrongNestedHit(hitObject, this);
private class StrongNestedHit : DrawableStrongNestedHit
{
public StrongNestedHit(StrongHitObject strong, DrawableDrumRollTick tick)
: base(strong, tick)
{
}
protected override void CheckForResult(bool userTriggered, double timeOffset)
{
if (!MainObject.Judged)
return;
ApplyResult(r => r.Type = MainObject.IsHit ? HitResult.Great : HitResult.Miss);
}
public override bool OnPressed(TaikoAction action) => false;
}
} }
} }

View File

@ -1,11 +1,11 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using System.Linq; using System.Linq;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.Taiko.Judgements;
using osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces; using osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces;
namespace osu.Game.Rulesets.Taiko.Objects.Drawables namespace osu.Game.Rulesets.Taiko.Objects.Drawables
@ -15,17 +15,14 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
/// <summary> /// <summary>
/// A list of keys which can result in hits for this HitObject. /// A list of keys which can result in hits for this HitObject.
/// </summary> /// </summary>
protected abstract TaikoAction[] HitActions { get; } public abstract TaikoAction[] HitActions { get; }
/// <summary> /// <summary>
/// Whether a second hit is allowed to be processed. This occurs once this hit object has been hit successfully. /// The action that caused this <see cref="DrawableHit"/> to be hit.
/// </summary> /// </summary>
protected bool SecondHitAllowed { get; private set; } public TaikoAction? HitAction { get; private set; }
/// <summary> private bool validActionPressed;
/// Whether the last key pressed is a valid hit key.
/// </summary>
private bool validKeyPressed;
protected DrawableHit(Hit hit) protected DrawableHit(Hit hit)
: base(hit) : base(hit)
@ -33,12 +30,12 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
FillMode = FillMode.Fit; FillMode = FillMode.Fit;
} }
protected override void CheckForJudgements(bool userTriggered, double timeOffset) protected override void CheckForResult(bool userTriggered, double timeOffset)
{ {
if (!userTriggered) if (!userTriggered)
{ {
if (!HitObject.HitWindows.CanBeHit(timeOffset)) if (!HitObject.HitWindows.CanBeHit(timeOffset))
AddJudgement(new TaikoJudgement { Result = HitResult.Miss }); ApplyResult(r => r.Type = HitResult.Miss);
return; return;
} }
@ -46,26 +43,33 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
if (result == HitResult.None) if (result == HitResult.None)
return; return;
if (!validKeyPressed || result == HitResult.Miss) if (!validActionPressed)
AddJudgement(new TaikoJudgement { Result = HitResult.Miss }); ApplyResult(r => r.Type = HitResult.Miss);
else else
{ ApplyResult(r => r.Type = result);
AddJudgement(new TaikoJudgement
{
Result = result,
Final = !HitObject.IsStrong
});
SecondHitAllowed = true;
}
} }
public override bool OnPressed(TaikoAction action) public override bool OnPressed(TaikoAction action)
{ {
validKeyPressed = HitActions.Contains(action); if (Judged)
return false;
validActionPressed = HitActions.Contains(action);
// Only count this as handled if the new judgement is a hit // Only count this as handled if the new judgement is a hit
return UpdateJudgement(true); var result = UpdateResult(true);
if (IsHit)
HitAction = action;
return result;
}
public override bool OnReleased(TaikoAction action)
{
if (action == HitAction)
HitAction = null;
return base.OnReleased(action);
} }
protected override void Update() protected override void Update()
@ -86,8 +90,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
switch (State.Value) switch (State.Value)
{ {
case ArmedState.Idle: case ArmedState.Idle:
SecondHitAllowed = false; validActionPressed = false;
validKeyPressed = false;
UnproxyContent(); UnproxyContent();
this.Delay(HitObject.HitWindows.HalfWindowFor(HitResult.Miss)).Expire(); this.Delay(HitObject.HitWindows.HalfWindowFor(HitResult.Miss)).Expire();
@ -123,5 +126,65 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
} }
} }
} }
protected override DrawableStrongNestedHit CreateStrongHit(StrongHitObject hitObject) => new StrongNestedHit(hitObject, this);
private class StrongNestedHit : DrawableStrongNestedHit
{
/// <summary>
/// The lenience for the second key press.
/// This does not adjust by map difficulty in ScoreV2 yet.
/// </summary>
private const double second_hit_window = 30;
public new DrawableHit MainObject => (DrawableHit)base.MainObject;
public StrongNestedHit(StrongHitObject strong, DrawableHit hit)
: base(strong, hit)
{
}
protected override void CheckForResult(bool userTriggered, double timeOffset)
{
if (!MainObject.Result.HasResult)
{
base.CheckForResult(userTriggered, timeOffset);
return;
}
if (!MainObject.Result.IsHit)
{
ApplyResult(r => r.Type = HitResult.Miss);
return;
}
if (!userTriggered)
{
if (timeOffset > second_hit_window)
ApplyResult(r => r.Type = HitResult.Miss);
return;
}
if (Math.Abs(MainObject.Result.TimeOffset - timeOffset) < second_hit_window)
ApplyResult(r => r.Type = HitResult.Great);
}
public override bool OnPressed(TaikoAction action)
{
// Don't process actions until the main hitobject is hit
if (!MainObject.IsHit)
return false;
// Don't process actions if the pressed button was released
if (MainObject.HitAction == null)
return false;
// Don't handle invalid hit action presses
if (!MainObject.HitActions.Contains(action))
return false;
return UpdateResult(true);
}
}
} }
} }

View File

@ -1,103 +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 System;
using System.Linq;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.Taiko.Judgements;
namespace osu.Game.Rulesets.Taiko.Objects.Drawables
{
public abstract class DrawableHitStrong : DrawableHit
{
/// <summary>
/// The lenience for the second key press.
/// This does not adjust by map difficulty in ScoreV2 yet.
/// </summary>
private const double second_hit_window = 30;
private double firstHitTime;
private bool firstKeyHeld;
private TaikoAction firstHitAction;
protected DrawableHitStrong(Hit hit)
: base(hit)
{
}
protected override void CheckForJudgements(bool userTriggered, double timeOffset)
{
if (!SecondHitAllowed)
{
base.CheckForJudgements(userTriggered, timeOffset);
return;
}
if (!userTriggered)
{
if (timeOffset > second_hit_window)
AddJudgement(new TaikoStrongHitJudgement { Result = HitResult.None });
return;
}
// If we get here, we're assured that the key pressed is the correct secondary key
if (Math.Abs(firstHitTime - Time.Current) < second_hit_window)
AddJudgement(new TaikoStrongHitJudgement { Result = HitResult.Great });
}
protected override void UpdateState(ArmedState state)
{
base.UpdateState(state);
switch (state)
{
case ArmedState.Idle:
firstHitTime = 0;
firstKeyHeld = false;
break;
}
}
public override bool OnReleased(TaikoAction action)
{
if (action == firstHitAction)
firstKeyHeld = false;
return base.OnReleased(action);
}
public override bool OnPressed(TaikoAction action)
{
if (AllJudged)
return false;
// Check if we've handled the first key
if (!SecondHitAllowed)
{
// First key hasn't been handled yet, attempt to handle it
bool handled = base.OnPressed(action);
if (handled)
{
firstHitTime = Time.Current;
firstHitAction = action;
firstKeyHeld = true;
}
return handled;
}
// Don't handle represses of the first key
if (firstHitAction == action)
return false;
// Don't handle invalid hit action presses
if (!HitActions.Contains(action))
return false;
// Assume the intention was to hit the strong hit with both keys only if the first key is still being held down
return firstKeyHeld && UpdateJudgement(true);
}
}
}

View File

@ -9,7 +9,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
{ {
public class DrawableRimHit : DrawableHit public class DrawableRimHit : DrawableHit
{ {
protected override TaikoAction[] HitActions { get; } = { TaikoAction.LeftRim, TaikoAction.RightRim }; public override TaikoAction[] HitActions { get; } = { TaikoAction.LeftRim, TaikoAction.RightRim };
public DrawableRimHit(Hit hit) public DrawableRimHit(Hit hit)
: base(hit) : base(hit)

View File

@ -1,26 +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.Allocation;
using osu.Game.Graphics;
using osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces;
namespace osu.Game.Rulesets.Taiko.Objects.Drawables
{
public class DrawableRimHitStrong : DrawableHitStrong
{
protected override TaikoAction[] HitActions { get; } = { TaikoAction.LeftRim, TaikoAction.RightRim };
public DrawableRimHitStrong(Hit hit)
: base(hit)
{
MainPiece.Add(new RimHitSymbolPiece());
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
MainPiece.AccentColour = colours.BlueDarker;
}
}
}

View File

@ -0,0 +1,26 @@
// 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.Drawables;
using osu.Game.Rulesets.Taiko.Judgements;
namespace osu.Game.Rulesets.Taiko.Objects.Drawables
{
/// <summary>
/// Used as a nested hitobject to provide <see cref="TaikoStrongJudgement"/>s for <see cref="DrawableTaikoHitObject"/>s.
/// </summary>
public abstract class DrawableStrongNestedHit : DrawableTaikoHitObject
{
public readonly DrawableHitObject MainObject;
protected DrawableStrongNestedHit(StrongHitObject strong, DrawableHitObject mainObject)
: base(strong)
{
MainObject = mainObject;
}
protected override void UpdateState(ArmedState state)
{
}
}
}

Some files were not shown because too many files have changed in this diff Show More