1
0
mirror of https://github.com/ppy/osu.git synced 2024-09-22 01:27:29 +08:00

Merge branch 'master' into master

This commit is contained in:
Jacob Odgård Tørring 2018-07-04 20:51:05 +02:00 committed by GitHub
commit 9248340df8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
210 changed files with 6616 additions and 2914 deletions

8
.github/pull_request_template.md vendored Normal file
View File

@ -0,0 +1,8 @@
Add any details pertaining to developers above the break.
- [ ] Depends on #PR
- Closes #ISSUE
---
Add a sentence or two describing this change in plain english. This will be displayed on the [changelog](https://osu.ppy.sh/home/changelog). A single screenshot or short gif is also welcomed.

View File

@ -1,29 +0,0 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [{
"name": "Deploy (Debug)",
"request": "launch",
"type": "mono",
"program": "${workspaceRoot}/bin/Debug/net471/osu.Desktop.Deploy.exe",
"cwd": "${workspaceRoot}",
"preLaunchTask": "Build (Debug)",
"runtimeExecutable": null,
"env": {},
"console": "internalConsole"
},
{
"name": "Deploy (Release)",
"request": "launch",
"type": "clr",
"program": "${workspaceRoot}/bin/Release/net471/osu.Desktop.Deploy.exe",
"cwd": "${workspaceRoot}",
"preLaunchTask": "Build (Release)",
"runtimeExecutable": null,
"env": {},
"console": "internalConsole"
}
]
}

View File

@ -1,64 +0,0 @@
{
// See https://go.microsoft.com/fwlink/?LinkId=733558
// for the documentation about the tasks.json format
"version": "2.0.0",
"command": "msbuild",
"type": "shell",
"suppressTaskName": true,
"args": [
"/property:GenerateFullPaths=true",
"/property:DebugType=portable",
"/verbosity:minimal",
"/m" //parallel compiling support.
],
"tasks": [{
"taskName": "Build (Debug)",
"group": {
"kind": "build",
"isDefault": true
},
"problemMatcher": [
"$msCompile"
]
},
{
"taskName": "Build (Release)",
"group": "build",
"args": [
"/property:Configuration=Release"
],
"problemMatcher": [
"$msCompile"
]
},
{
"taskName": "Clean (Debug)",
"args": [
"/target:Clean"
],
"problemMatcher": [
"$msCompile"
]
},
{
"taskName": "Clean (Release)",
"args": [
"/target:Clean",
"/property:Configuration=Release"
],
"problemMatcher": [
"$msCompile"
]
},
{
"taskName": "Clean All",
"dependsOn": [
"Clean (Debug)",
"Clean (Release)"
],
"problemMatcher": [
"$msCompile"
]
}
]
}

View File

@ -73,7 +73,7 @@ namespace osu.Desktop
} }
public StableStorage() public StableStorage()
: base(string.Empty) : base(string.Empty, null)
{ {
} }
} }

View File

@ -29,7 +29,8 @@
<PackageReference Include="Microsoft.Win32.Registry" Version="4.5.0" /> <PackageReference Include="Microsoft.Win32.Registry" Version="4.5.0" />
</ItemGroup> </ItemGroup>
<ItemGroup Label="Package References"> <ItemGroup Label="Package References">
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.0" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="2.1.1" />
<PackageReference Include="squirrel.windows" Version="1.8.0" Condition="'$(TargetFramework)' == 'net471'" /> <PackageReference Include="squirrel.windows" Version="1.8.0" Condition="'$(TargetFramework)' == 'net471'" />
</ItemGroup> </ItemGroup>
<ItemGroup Label="Resources"> <ItemGroup Label="Resources">

View File

@ -3,6 +3,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using Newtonsoft.Json;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.MathUtils; using osu.Framework.MathUtils;
using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.Objects;
@ -12,11 +13,14 @@ using osu.Game.Tests.Beatmaps;
namespace osu.Game.Rulesets.Catch.Tests namespace osu.Game.Rulesets.Catch.Tests
{ {
internal class CatchBeatmapConversionTest : BeatmapConversionTest<ConvertValue> [TestFixture]
public class CatchBeatmapConversionTest : BeatmapConversionTest<ConvertValue>
{ {
protected override string ResourceAssembly => "osu.Game.Rulesets.Catch"; protected override string ResourceAssembly => "osu.Game.Rulesets.Catch";
[TestCase("basic"), Ignore("See: https://github.com/ppy/osu/issues/2232")] [TestCase("basic")]
[TestCase("spinner")]
[TestCase("spinner-and-circles")]
public new void Test(string name) public new void Test(string name)
{ {
base.Test(name); base.Test(name);
@ -27,36 +31,52 @@ namespace osu.Game.Rulesets.Catch.Tests
if (hitObject is JuiceStream stream) if (hitObject is JuiceStream stream)
{ {
foreach (var nested in stream.NestedHitObjects) foreach (var nested in stream.NestedHitObjects)
{ yield return new ConvertValue((CatchHitObject)nested);
yield return new ConvertValue }
{ else if (hitObject is BananaShower shower)
StartTime = nested.StartTime, {
Position = ((CatchHitObject)nested).X * CatchPlayfield.BASE_WIDTH foreach (var nested in shower.NestedHitObjects)
}; yield return new ConvertValue((CatchHitObject)nested);
}
} }
else else
{ yield return new ConvertValue((CatchHitObject)hitObject);
yield return new ConvertValue
{
StartTime = hitObject.StartTime,
Position = ((CatchHitObject)hitObject).X * CatchPlayfield.BASE_WIDTH
};
}
} }
protected override Ruleset CreateRuleset() => new CatchRuleset(); protected override Ruleset CreateRuleset() => new CatchRuleset();
} }
internal struct ConvertValue : IEquatable<ConvertValue> public struct ConvertValue : IEquatable<ConvertValue>
{ {
/// <summary> /// <summary>
/// A sane value to account for osu!stable using ints everwhere. /// A sane value to account for osu!stable using ints everwhere.
/// </summary> /// </summary>
private const float conversion_lenience = 2; private const float conversion_lenience = 2;
public double StartTime; [JsonIgnore]
public float Position; public readonly CatchHitObject HitObject;
public ConvertValue(CatchHitObject hitObject)
{
HitObject = hitObject;
startTime = 0;
position = 0;
}
private double startTime;
public double StartTime
{
get => HitObject?.StartTime ?? startTime;
set => startTime = value;
}
private float position;
public float Position
{
get => HitObject?.X * CatchPlayfield.BASE_WIDTH ?? position;
set => position = value;
}
public bool Equals(ConvertValue other) public bool Equals(ConvertValue other)
=> Precision.AlmostEquals(StartTime, other.StartTime, conversion_lenience) => Precision.AlmostEquals(StartTime, other.StartTime, conversion_lenience)

View File

@ -55,7 +55,7 @@ namespace osu.Game.Rulesets.Catch.Tests
{ {
} }
public void ToggleHyperDash(bool status) => MovableCatcher.HyperDashModifier = status ? 2 : 1; public void ToggleHyperDash(bool status) => MovableCatcher.SetHyperdashState(status ? 2 : 1);
} }
} }
} }

View File

@ -55,7 +55,7 @@ namespace osu.Game.Rulesets.Catch.Tests
private DrawableFruit createDrawable(int index) private DrawableFruit createDrawable(int index)
{ {
Fruit fruit = index == 5 Fruit fruit = index == 5
? new BananaShower.Banana ? new Banana
{ {
StartTime = 1000000000000, StartTime = 1000000000000,
IndexInBeatmap = index, IndexInBeatmap = index,

View File

@ -26,22 +26,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
var positionData = obj as IHasXPosition; var positionData = obj as IHasXPosition;
var comboData = obj as IHasCombo; var comboData = obj as IHasCombo;
var endTime = obj as IHasEndTime; var endTime = obj as IHasEndTime;
var legacyOffset = obj as IHasLegacyLastTickOffset;
if (positionData == null)
{
if (endTime != null)
{
yield return new BananaShower
{
StartTime = obj.StartTime,
Samples = obj.Samples,
Duration = endTime.Duration,
NewCombo = comboData?.NewCombo ?? false
};
}
yield break;
}
if (curveData != null) if (curveData != null)
{ {
@ -54,20 +39,31 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
Distance = curveData.Distance, Distance = curveData.Distance,
RepeatSamples = curveData.RepeatSamples, RepeatSamples = curveData.RepeatSamples,
RepeatCount = curveData.RepeatCount, RepeatCount = curveData.RepeatCount,
X = positionData.X / CatchPlayfield.BASE_WIDTH, X = (positionData?.X ?? 0) / CatchPlayfield.BASE_WIDTH,
NewCombo = comboData?.NewCombo ?? false,
LegacyLastTickOffset = legacyOffset?.LegacyLastTickOffset ?? 0
};
}
else if (endTime != null)
{
yield return new BananaShower
{
StartTime = obj.StartTime,
Samples = obj.Samples,
Duration = endTime.Duration,
NewCombo = comboData?.NewCombo ?? false NewCombo = comboData?.NewCombo ?? false
}; };
yield break;
} }
else
yield return new Fruit
{ {
StartTime = obj.StartTime, yield return new Fruit
Samples = obj.Samples, {
NewCombo = comboData?.NewCombo ?? false, StartTime = obj.StartTime,
X = positionData.X / CatchPlayfield.BASE_WIDTH Samples = obj.Samples,
}; NewCombo = comboData?.NewCombo ?? false,
X = (positionData?.X ?? 0) / CatchPlayfield.BASE_WIDTH
};
}
} }
protected override Beatmap<CatchHitObject> CreateBeatmap() => new CatchBeatmap(); protected override Beatmap<CatchHitObject> CreateBeatmap() => new CatchBeatmap();

View File

@ -9,11 +9,14 @@ using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Catch.UI; using osu.Game.Rulesets.Catch.UI;
using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Objects.Types;
using OpenTK; using OpenTK;
using osu.Game.Rulesets.Catch.MathUtils;
namespace osu.Game.Rulesets.Catch.Beatmaps namespace osu.Game.Rulesets.Catch.Beatmaps
{ {
public class CatchBeatmapProcessor : BeatmapProcessor public class CatchBeatmapProcessor : BeatmapProcessor
{ {
public const int RNG_SEED = 1337;
public CatchBeatmapProcessor(IBeatmap beatmap) public CatchBeatmapProcessor(IBeatmap beatmap)
: base(beatmap) : base(beatmap)
{ {
@ -21,13 +24,52 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
public override void PostProcess() public override void PostProcess()
{ {
initialiseHyperDash((List<CatchHitObject>)Beatmap.HitObjects);
base.PostProcess(); base.PostProcess();
applyPositionOffsets();
initialiseHyperDash((List<CatchHitObject>)Beatmap.HitObjects);
int index = 0; int index = 0;
foreach (var obj in Beatmap.HitObjects.OfType<CatchHitObject>()) foreach (var obj in Beatmap.HitObjects.OfType<CatchHitObject>())
{
obj.IndexInBeatmap = index++; obj.IndexInBeatmap = index++;
if (obj.LastInCombo && obj.NestedHitObjects.LastOrDefault() is IHasComboInformation lastNested)
lastNested.LastInCombo = true;
}
}
private void applyPositionOffsets()
{
var rng = new FastRandom(RNG_SEED);
// todo: HardRock displacement should be applied here
foreach (var obj in Beatmap.HitObjects)
{
switch (obj)
{
case BananaShower bananaShower:
foreach (var banana in bananaShower.NestedHitObjects.OfType<Banana>())
{
banana.X = (float)rng.NextDouble();
rng.Next(); // osu!stable retrieved a random banana type
rng.Next(); // osu!stable retrieved a random banana rotation
rng.Next(); // osu!stable retrieved a random banana colour
}
break;
case JuiceStream juiceStream:
foreach (var nested in juiceStream.NestedHitObjects)
{
var hitObject = (CatchHitObject)nested;
if (hitObject is TinyDroplet)
hitObject.X += rng.Next(-20, 20) / CatchPlayfield.BASE_WIDTH;
else if (hitObject is Droplet)
rng.Next(); // osu!stable retrieved a random droplet rotation
hitObject.X = MathHelper.Clamp(hitObject.X, 0, 1);
}
break;
}
}
} }
private void initialiseHyperDash(List<CatchHitObject> objects) private void initialiseHyperDash(List<CatchHitObject> objects)

View File

@ -0,0 +1,19 @@
// 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.Difficulty;
using osu.Game.Rulesets.Mods;
namespace osu.Game.Rulesets.Catch.Difficulty
{
public class CatchDifficultyAttributes : DifficultyAttributes
{
public double ApproachRate;
public int MaxCombo;
public CatchDifficultyAttributes(Mod[] mods, double starRating)
: base(mods, starRating)
{
}
}
}

View File

@ -1,19 +1,146 @@
// 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.Collections.Generic;
using System.Linq;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Catch.UI;
namespace osu.Game.Rulesets.Catch.Difficulty namespace osu.Game.Rulesets.Catch.Difficulty
{ {
public class CatchDifficultyCalculator : DifficultyCalculator public class CatchDifficultyCalculator : DifficultyCalculator
{ {
/// <summary>
/// In milliseconds. For difficulty calculation we will only look at the highest strain value in each time interval of size STRAIN_STEP.
/// This is to eliminate higher influence of stream over aim by simply having more HitObjects with high strain.
/// The higher this value, the less strains there will be, indirectly giving long beatmaps an advantage.
/// </summary>
private const double strain_step = 750;
/// <summary>
/// The weighting of each strain value decays to this number * it's previous value
/// </summary>
private const double decay_weight = 0.94;
private const double star_scaling_factor = 0.145;
public CatchDifficultyCalculator(Ruleset ruleset, WorkingBeatmap beatmap) public CatchDifficultyCalculator(Ruleset ruleset, WorkingBeatmap beatmap)
: base(ruleset, beatmap) : base(ruleset, beatmap)
{ {
} }
protected override DifficultyAttributes Calculate(IBeatmap beatmap, Mod[] mods, double timeRate) => new DifficultyAttributes(mods, 0); protected override DifficultyAttributes Calculate(IBeatmap beatmap, Mod[] mods, double timeRate)
{
if (!beatmap.HitObjects.Any())
return new CatchDifficultyAttributes(mods, 0);
var catcher = new CatcherArea.Catcher(beatmap.BeatmapInfo.BaseDifficulty);
float halfCatchWidth = catcher.CatchWidth * 0.5f;
var difficultyHitObjects = new List<CatchDifficultyHitObject>();
foreach (var hitObject in beatmap.HitObjects)
{
// We want to only consider fruits that contribute to the combo. Droplets are addressed as accuracy and spinners are not relevant for "skill" calculations.
if (hitObject is Fruit)
{
difficultyHitObjects.Add(new CatchDifficultyHitObject((CatchHitObject)hitObject, halfCatchWidth));
}
if (hitObject is JuiceStream)
difficultyHitObjects.AddRange(hitObject.NestedHitObjects.OfType<CatchHitObject>().Where(o => !(o is TinyDroplet)).Select(o => new CatchDifficultyHitObject(o, halfCatchWidth)));
}
difficultyHitObjects.Sort((a, b) => a.BaseHitObject.StartTime.CompareTo(b.BaseHitObject.StartTime));
if (!calculateStrainValues(difficultyHitObjects, timeRate))
return new CatchDifficultyAttributes(mods, 0);
// this is the same as osu!, so there's potential to share the implementation... maybe
double preEmpt = BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.ApproachRate, 1800, 1200, 450) / timeRate;
double starRating = Math.Sqrt(calculateDifficulty(difficultyHitObjects, timeRate)) * star_scaling_factor;
return new CatchDifficultyAttributes(mods, starRating)
{
ApproachRate = preEmpt > 1200.0 ? -(preEmpt - 1800.0) / 120.0 : -(preEmpt - 1200.0) / 150.0 + 5.0,
MaxCombo = difficultyHitObjects.Count
};
}
private bool calculateStrainValues(List<CatchDifficultyHitObject> objects, double timeRate)
{
CatchDifficultyHitObject lastObject = null;
if (!objects.Any()) return false;
// Traverse hitObjects in pairs to calculate the strain value of NextHitObject from the strain value of CurrentHitObject and environment.
foreach (var currentObject in objects)
{
if (lastObject != null)
currentObject.CalculateStrains(lastObject, timeRate);
lastObject = currentObject;
}
return true;
}
private double calculateDifficulty(List<CatchDifficultyHitObject> objects, double timeRate)
{
// The strain step needs to be adjusted for the algorithm to be considered equal with speed changing mods
double actualStrainStep = strain_step * timeRate;
// Find the highest strain value within each strain step
var highestStrains = new List<double>();
double intervalEndTime = actualStrainStep;
double maximumStrain = 0; // We need to keep track of the maximum strain in the current interval
CatchDifficultyHitObject previousHitObject = null;
foreach (CatchDifficultyHitObject hitObject in objects)
{
// While we are beyond the current interval push the currently available maximum to our strain list
while (hitObject.BaseHitObject.StartTime > intervalEndTime)
{
highestStrains.Add(maximumStrain);
// The maximum strain of the next interval is not zero by default! We need to take the last hitObject we encountered, take its strain and apply the decay
// until the beginning of the next interval.
if (previousHitObject == null)
{
maximumStrain = 0;
}
else
{
double decay = Math.Pow(CatchDifficultyHitObject.DECAY_BASE, (intervalEndTime - previousHitObject.BaseHitObject.StartTime) / 1000);
maximumStrain = previousHitObject.Strain * decay;
}
// Go to the next time interval
intervalEndTime += actualStrainStep;
}
// Obtain maximum strain
maximumStrain = Math.Max(hitObject.Strain, maximumStrain);
previousHitObject = hitObject;
}
// Build the weighted sum over the highest strains for each interval
double difficulty = 0;
double weight = 1;
highestStrains.Sort((a, b) => b.CompareTo(a)); // Sort from highest to lowest strain.
foreach (double strain in highestStrains)
{
difficulty += weight * strain;
weight *= decay_weight;
}
return difficulty;
}
} }
} }

View File

@ -0,0 +1,130 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Catch.UI;
using OpenTK;
namespace osu.Game.Rulesets.Catch.Difficulty
{
public class CatchDifficultyHitObject
{
internal static readonly double DECAY_BASE = 0.20;
private const float normalized_hitobject_radius = 41.0f;
private const float absolute_player_positioning_error = 16f;
private readonly float playerPositioningError;
internal CatchHitObject BaseHitObject;
/// <summary>
/// Measures jump difficulty. CtB doesn't have something like button pressing speed or accuracy
/// </summary>
internal double Strain = 1;
/// <summary>
/// This is required to keep track of lazy player movement (always moving only as far as necessary)
/// Without this quick repeat sliders / weirdly shaped streams might become ridiculously overrated
/// </summary>
internal float PlayerPositionOffset;
internal float LastMovement;
internal float NormalizedPosition;
internal float ActualNormalizedPosition => NormalizedPosition + PlayerPositionOffset;
internal CatchDifficultyHitObject(CatchHitObject baseHitObject, float catcherWidthHalf)
{
BaseHitObject = baseHitObject;
// We will scale everything by this factor, so we can assume a uniform CircleSize among beatmaps.
float scalingFactor = normalized_hitobject_radius / catcherWidthHalf;
playerPositioningError = absolute_player_positioning_error; // * scalingFactor;
NormalizedPosition = baseHitObject.X * CatchPlayfield.BASE_WIDTH * scalingFactor;
}
private const double direction_change_bonus = 12.5;
internal void CalculateStrains(CatchDifficultyHitObject previousHitObject, double timeRate)
{
// Rather simple, but more specialized things are inherently inaccurate due to the big difference playstyles and opinions make.
// See Taiko feedback thread.
double timeElapsed = (BaseHitObject.StartTime - previousHitObject.BaseHitObject.StartTime) / timeRate;
double decay = Math.Pow(DECAY_BASE, timeElapsed / 1000);
// Update new position with lazy movement.
PlayerPositionOffset =
MathHelper.Clamp(
previousHitObject.ActualNormalizedPosition,
NormalizedPosition - (normalized_hitobject_radius - playerPositioningError),
NormalizedPosition + (normalized_hitobject_radius - playerPositioningError)) // Obtain new lazy position, but be stricter by allowing for an error of a certain degree of the player.
- NormalizedPosition; // Subtract HitObject position to obtain offset
LastMovement = DistanceTo(previousHitObject);
double addition = spacingWeight(LastMovement);
if (NormalizedPosition < previousHitObject.NormalizedPosition)
{
LastMovement = -LastMovement;
}
CatchHitObject previousHitCircle = previousHitObject.BaseHitObject;
double additionBonus = 0;
double sqrtTime = Math.Sqrt(Math.Max(timeElapsed, 25));
// Direction changes give an extra point!
if (Math.Abs(LastMovement) > 0.1)
{
if (Math.Abs(previousHitObject.LastMovement) > 0.1 && Math.Sign(LastMovement) != Math.Sign(previousHitObject.LastMovement))
{
double bonus = direction_change_bonus / sqrtTime;
// Weight bonus by how
double bonusFactor = Math.Min(playerPositioningError, Math.Abs(LastMovement)) / playerPositioningError;
// We want time to play a role twice here!
addition += bonus * bonusFactor;
// Bonus for tougher direction switches and "almost" hyperdashes at this point
if (previousHitCircle != null && previousHitCircle.DistanceToHyperDash <= 10.0f / CatchPlayfield.BASE_WIDTH)
{
additionBonus += 0.3 * bonusFactor;
}
}
// Base bonus for every movement, giving some weight to streams.
addition += 7.5 * Math.Min(Math.Abs(LastMovement), normalized_hitobject_radius * 2) / (normalized_hitobject_radius * 6) / sqrtTime;
}
// Bonus for "almost" hyperdashes at corner points
if (previousHitCircle != null && previousHitCircle.DistanceToHyperDash <= 10.0f / CatchPlayfield.BASE_WIDTH)
{
if (!previousHitCircle.HyperDash)
{
additionBonus += 1.0;
}
else
{
// After a hyperdash we ARE in the correct position. Always!
PlayerPositionOffset = 0;
}
addition *= 1.0 + additionBonus * ((10 - previousHitCircle.DistanceToHyperDash * CatchPlayfield.BASE_WIDTH) / 10);
}
addition *= 850.0 / Math.Max(timeElapsed, 25);
Strain = previousHitObject.Strain * decay + addition;
}
private static double spacingWeight(float distance)
{
return Math.Pow(distance, 1.3) / 500;
}
internal float DistanceTo(CatchDifficultyHitObject other)
{
return Math.Abs(ActualNormalizedPosition - other.ActualNormalizedPosition);
}
}
}

View File

@ -0,0 +1,36 @@
// 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.Catch.Judgements
{
public class CatchBananaJudgement : CatchJudgement
{
public override bool AffectsCombo => false;
public override bool ShouldExplode => true;
protected override int NumericResultFor(HitResult result)
{
switch (result)
{
default:
return 0;
case HitResult.Perfect:
return 1100;
}
}
protected override float HealthIncreaseFor(HitResult result)
{
switch (result)
{
default:
return 0;
case HitResult.Perfect:
return 8;
}
}
}
}

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 osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Catch.Judgements
{
public class CatchDropletJudgement : CatchJudgement
{
protected override int NumericResultFor(HitResult result)
{
switch (result)
{
default:
return 0;
case HitResult.Perfect:
return 30;
}
}
protected override float HealthIncreaseFor(HitResult result)
{
switch (result)
{
default:
return 0;
case HitResult.Perfect:
return 7;
}
}
}
}

View File

@ -2,11 +2,51 @@
// 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.Objects.Types;
using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Catch.Judgements namespace osu.Game.Rulesets.Catch.Judgements
{ {
public class CatchJudgement : Judgement public class CatchJudgement : Judgement
{ {
// todo: wangs public override HitResult MaxResult => HitResult.Perfect;
protected override int NumericResultFor(HitResult result)
{
switch (result)
{
default:
return 0;
case HitResult.Perfect:
return 300;
}
}
/// <summary>
/// The base health increase for the result achieved.
/// </summary>
public float HealthIncrease => HealthIncreaseFor(Result);
/// <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)
{
switch (result)
{
default:
return 0;
case HitResult.Perfect:
return 10.2f;
}
}
} }
} }

View File

@ -0,0 +1,34 @@
// 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.Catch.Judgements
{
public class CatchTinyDropletJudgement : CatchJudgement
{
public override bool AffectsCombo => false;
protected override int NumericResultFor(HitResult result)
{
switch (result)
{
default:
return 0;
case HitResult.Perfect:
return 10;
}
}
protected override float HealthIncreaseFor(HitResult result)
{
switch (result)
{
default:
return 0;
case HitResult.Perfect:
return 4;
}
}
}
}

View File

@ -0,0 +1,91 @@
// 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;
namespace osu.Game.Rulesets.Catch.MathUtils
{
/// <summary>
/// A PRNG specified in http://heliosphan.org/fastrandom.html.
/// </summary>
public class FastRandom
{
private const double int_to_real = 1.0 / (int.MaxValue + 1.0);
private const uint int_mask = 0x7FFFFFFF;
private const uint y = 842502087;
private const uint z = 3579807591;
private const uint w = 273326509;
private uint _x, _y = y, _z = z, _w = w;
public FastRandom(int seed)
{
_x = (uint)seed;
}
public FastRandom()
: this(Environment.TickCount)
{
}
/// <summary>
/// Generates a random unsigned integer within the range [<see cref="uint.MinValue"/>, <see cref="uint.MaxValue"/>).
/// </summary>
/// <returns>The random value.</returns>
public uint NextUInt()
{
uint t = _x ^ _x << 11;
_x = _y;
_y = _z;
_z = _w;
return _w = _w ^ _w >> 19 ^ t ^ t >> 8;
}
/// <summary>
/// Generates a random integer value within the range [0, <see cref="int.MaxValue"/>).
/// </summary>
/// <returns>The random value.</returns>
public int Next() => (int)(int_mask & NextUInt());
/// <summary>
/// Generates a random integer value within the range [0, <paramref name="upperBound"/>).
/// </summary>
/// <param name="upperBound">The upper bound.</param>
/// <returns>The random value.</returns>
public int Next(int upperBound) => (int)(NextDouble() * upperBound);
/// <summary>
/// Generates a random integer value within the range [<paramref name="lowerBound"/>, <paramref name="upperBound"/>).
/// </summary>
/// <param name="lowerBound">The lower bound of the range.</param>
/// <param name="upperBound">The upper bound of the range.</param>
/// <returns>The random value.</returns>
public int Next(int lowerBound, int upperBound) => (int)(lowerBound + NextDouble() * (upperBound - lowerBound));
/// <summary>
/// Generates a random double value within the range [0, 1).
/// </summary>
/// <returns>The random value.</returns>
public double NextDouble() => int_to_real * Next();
private uint bitBuffer;
private int bitIndex = 32;
/// <summary>
/// Generates a reandom boolean value. Cached such that a random value is only generated once in every 32 calls.
/// </summary>
/// <returns>The random value.</returns>
public bool NextBool()
{
if (bitIndex == 32)
{
bitBuffer = NextUInt();
bitIndex = 1;
return (bitBuffer & 1) == 1;
}
bitIndex++;
return ((bitBuffer >>= 1) & 1) == 1;
}
}
}

View File

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

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 osu.Framework.MathUtils;
using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Objects.Types;
namespace osu.Game.Rulesets.Catch.Objects namespace osu.Game.Rulesets.Catch.Objects
@ -31,18 +30,12 @@ namespace osu.Game.Rulesets.Catch.Objects
AddNested(new Banana AddNested(new Banana
{ {
Samples = Samples, Samples = Samples,
StartTime = i, StartTime = i
X = RNG.NextSingle()
}); });
} }
public double EndTime => StartTime + Duration; public double EndTime => StartTime + Duration;
public double Duration { get; set; } public double Duration { get; set; }
public class Banana : Fruit
{
public override FruitVisualRepresentation VisualRepresentation => FruitVisualRepresentation.Banana;
}
} }
} }

View File

@ -24,6 +24,11 @@ namespace osu.Game.Rulesets.Catch.Objects
public int ComboIndex { get; set; } public int ComboIndex { get; set; }
/// <summary>
/// The distance for a fruit to to next hyper if it's not a hyper.
/// </summary>
public float DistanceToHyperDash { get; set; }
/// <summary> /// <summary>
/// The next fruit starts a new combo. Used for explodey. /// The next fruit starts a new combo. Used for explodey.
/// </summary> /// </summary>

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.Catch.Judgements;
namespace osu.Game.Rulesets.Catch.Objects.Drawable
{
public class DrawableBanana : DrawableFruit
{
public DrawableBanana(Banana h)
: base(h)
{
}
protected override CatchJudgement CreateJudgement() => new CatchBananaJudgement();
}
}

View File

@ -5,9 +5,7 @@ using System;
using System.Linq; using System.Linq;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Catch.Objects.Drawable namespace osu.Game.Rulesets.Catch.Objects.Drawable
{ {
@ -24,15 +22,11 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
InternalChild = bananaContainer = new Container { RelativeSizeAxes = Axes.Both }; InternalChild = bananaContainer = new Container { RelativeSizeAxes = Axes.Both };
foreach (var b in s.NestedHitObjects.Cast<BananaShower.Banana>()) foreach (var b in s.NestedHitObjects.Cast<Banana>())
AddNested(getVisualRepresentation?.Invoke(b)); AddNested(getVisualRepresentation?.Invoke(b));
} }
protected override void CheckForJudgements(bool userTriggered, double timeOffset) protected override bool ProvidesJudgement => false;
{
if (timeOffset >= 0)
AddJudgement(new Judgement { Result = NestedHitObjects.Cast<DrawableCatchHitObject>().Any(n => n.Judgements.Any(j => j.IsHit)) ? HitResult.Perfect : HitResult.Miss });
}
protected override void AddNested(DrawableHitObject h) protected override void AddNested(DrawableHitObject h)
{ {

View File

@ -2,14 +2,14 @@
// 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 OpenTK;
using OpenTK.Graphics;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Game.Rulesets.Judgements; 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 OpenTK;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
using osu.Game.Skinning; using osu.Game.Skinning;
using OpenTK.Graphics;
namespace osu.Game.Rulesets.Catch.Objects.Drawable namespace osu.Game.Rulesets.Catch.Objects.Drawable
{ {
@ -58,9 +58,15 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
if (CheckPosition == null) return; if (CheckPosition == null) return;
if (timeOffset >= 0) if (timeOffset >= 0)
AddJudgement(new Judgement { Result = 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,6 +6,7 @@ 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
{ {
@ -23,6 +24,8 @@ 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

@ -0,0 +1,19 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using OpenTK;
using osu.Game.Rulesets.Catch.Judgements;
namespace osu.Game.Rulesets.Catch.Objects.Drawable
{
public class DrawableTinyDroplet : DrawableDroplet
{
public DrawableTinyDroplet(Droplet h)
: base(h)
{
Size = new Vector2((float)CatchHitObject.OBJECT_RADIUS) / 8;
}
protected override CatchJudgement CreateJudgement() => new CatchTinyDropletJudgement();
}
}

View File

@ -42,7 +42,6 @@ namespace osu.Game.Rulesets.Catch.Objects
protected override void CreateNestedHitObjects() protected override void CreateNestedHitObjects()
{ {
base.CreateNestedHitObjects(); base.CreateNestedHitObjects();
createTicks(); createTicks();
} }
@ -78,6 +77,13 @@ namespace osu.Game.Rulesets.Catch.Objects
double time = spanStartTime + timeProgress * spanDuration; double time = spanStartTime + timeProgress * spanDuration;
if (LegacyLastTickOffset != null)
{
// If we're the last tick, apply the legacy offset
if (span == this.SpanCount() - 1 && d + tickDistance > length)
time = Math.Max(StartTime + Duration / 2, time - LegacyLastTickOffset.Value);
}
double tinyTickInterval = time - lastDropletTime; double tinyTickInterval = time - lastDropletTime;
while (tinyTickInterval > 100) while (tinyTickInterval > 100)
tinyTickInterval /= 2; tinyTickInterval /= 2;
@ -124,9 +130,6 @@ namespace osu.Game.Rulesets.Catch.Objects
X = X + Curve.PositionAt(reversed ? 0 : 1).X / CatchPlayfield.BASE_WIDTH X = X + Curve.PositionAt(reversed ? 0 : 1).X / CatchPlayfield.BASE_WIDTH
}); });
} }
if (NestedHitObjects.LastOrDefault() is IHasComboInformation lastNested)
lastNested.LastInCombo = LastInCombo;
} }
public double EndTime => StartTime + this.SpanCount() * Curve.Distance / Velocity; public double EndTime => StartTime + this.SpanCount() * Curve.Distance / Velocity;
@ -156,5 +159,7 @@ namespace osu.Game.Rulesets.Catch.Objects
get { return Curve.CurveType; } get { return Curve.CurveType; }
set { Curve.CurveType = value; } set { Curve.CurveType = value; }
} }
public double? LegacyLastTickOffset { get; set; }
} }
} }

View File

@ -56,7 +56,7 @@ namespace osu.Game.Rulesets.Catch.Replays
return; return;
} }
if (h is BananaShower.Banana) if (h is Banana)
{ {
// auto bananas unrealistically warp to catch 100% combo. // auto bananas unrealistically warp to catch 100% combo.
Replay.Frames.Add(new CatchReplayFrame(h.StartTime, h.X)); Replay.Frames.Add(new CatchReplayFrame(h.StartTime, h.X));
@ -105,7 +105,7 @@ namespace osu.Game.Rulesets.Catch.Replays
{ {
switch (nestedObj) switch (nestedObj)
{ {
case BananaShower.Banana _: case Banana _:
case TinyDroplet _: case TinyDroplet _:
case Droplet _: case Droplet _:
case Fruit _: case Fruit _:

View File

@ -28,9 +28,9 @@ namespace osu.Game.Rulesets.Catch.Replays
} }
} }
public override List<InputState> GetPendingStates() public override List<IInput> GetPendingInputs()
{ {
if (!Position.HasValue) return new List<InputState>(); if (!Position.HasValue) return new List<IInput>();
var actions = new List<CatchAction>(); var actions = new List<CatchAction>();
@ -42,7 +42,7 @@ namespace osu.Game.Rulesets.Catch.Replays
else if (Position.Value < CurrentFrame.Position) else if (Position.Value < CurrentFrame.Position)
actions.Add(CatchAction.MoveLeft); actions.Add(CatchAction.MoveLeft);
return new List<InputState> return new List<IInput>
{ {
new CatchReplayState new CatchReplayState
{ {

View File

@ -0,0 +1,65 @@
{
"Mappings": [{
"StartTime": 2589,
"Objects": [{
"StartTime": 2589,
"Position": 256
}]
},
{
"StartTime": 2915,
"Objects": [{
"StartTime": 2915,
"Position": 65
},
{
"StartTime": 2916,
"Position": 482
}
]
},
{
"StartTime": 3078,
"Objects": [{
"StartTime": 3078,
"Position": 164
},
{
"StartTime": 3079,
"Position": 315
}
]
},
{
"StartTime": 3241,
"Objects": [{
"StartTime": 3241,
"Position": 145
},
{
"StartTime": 3242,
"Position": 159
}
]
},
{
"StartTime": 3404,
"Objects": [{
"StartTime": 3404,
"Position": 310
},
{
"StartTime": 3405,
"Position": 441
}
]
},
{
"StartTime": 5197,
"Objects": [{
"StartTime": 5197,
"Position": 256
}]
}
]
}

View File

@ -0,0 +1,24 @@
osu file format v14
[General]
StackLeniency: 0.7
Mode: 2
[Difficulty]
HPDrainRate:5
CircleSize:2
OverallDifficulty:5
ApproachRate:8
SliderMultiplier:1.4
SliderTickRate:4
[TimingPoints]
2589,326.086956521739,4,2,1,70,1,0
[HitObjects]
256,192,2589,5,0,0:0:0:0:
256,192,2915,12,0,2916,0:0:0:0:
256,192,3078,12,0,3079,0:0:0:0:
256,192,3241,12,0,3242,0:0:0:0:
256,192,3404,12,0,3405,0:0:0:0:
256,192,5197,5,0,0:0:0:0:

View File

@ -0,0 +1,74 @@
{
"Mappings": [{
"StartTime": 18500,
"Objects": [{
"StartTime": 18500,
"Position": 65
},
{
"StartTime": 18559,
"Position": 482
},
{
"StartTime": 18618,
"Position": 164
},
{
"StartTime": 18678,
"Position": 315
},
{
"StartTime": 18737,
"Position": 145
},
{
"StartTime": 18796,
"Position": 159
},
{
"StartTime": 18856,
"Position": 310
},
{
"StartTime": 18915,
"Position": 441
},
{
"StartTime": 18975,
"Position": 428
},
{
"StartTime": 19034,
"Position": 243
},
{
"StartTime": 19093,
"Position": 422
},
{
"StartTime": 19153,
"Position": 481
},
{
"StartTime": 19212,
"Position": 104
},
{
"StartTime": 19271,
"Position": 473
},
{
"StartTime": 19331,
"Position": 135
},
{
"StartTime": 19390,
"Position": 360
},
{
"StartTime": 19450,
"Position": 123
}
]
}]
}

View File

@ -0,0 +1,20 @@
osu file format v14
[General]
Mode: 2
[Difficulty]
HPDrainRate:6
CircleSize:4
OverallDifficulty:7
ApproachRate:8.3
SliderMultiplier:1.6
SliderTickRate:1
[TimingPoints]
500,500,4,2,1,50,1,0
13426,-100,4,3,1,45,0,0
14884,-100,4,2,1,50,0,0
[HitObjects]
256,192,18500,12,0,19450,0:0:0:0:

View File

@ -1,10 +1,12 @@
// 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.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;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI;
@ -17,28 +19,57 @@ namespace osu.Game.Rulesets.Catch.Scoring
{ {
} }
private float hpDrainRate;
protected override void SimulateAutoplay(Beatmap<CatchHitObject> beatmap) protected override void SimulateAutoplay(Beatmap<CatchHitObject> beatmap)
{ {
hpDrainRate = beatmap.BeatmapInfo.BaseDifficulty.DrainRate;
foreach (var obj in beatmap.HitObjects) foreach (var obj in beatmap.HitObjects)
{ {
switch (obj) switch (obj)
{ {
case JuiceStream stream: case JuiceStream stream:
foreach (var _ in stream.NestedHitObjects.Cast<CatchHitObject>()) foreach (var nestedObject in stream.NestedHitObjects)
AddJudgement(new CatchJudgement { Result = HitResult.Perfect }); 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; break;
case BananaShower shower: case BananaShower shower:
foreach (var _ in shower.NestedHitObjects.Cast<CatchHitObject>()) foreach (var _ in shower.NestedHitObjects.Cast<CatchHitObject>())
AddJudgement(new CatchJudgement { Result = HitResult.Perfect }); AddJudgement(new CatchBananaJudgement { Result = HitResult.Perfect });
AddJudgement(new CatchJudgement { Result = HitResult.Perfect });
break; break;
case Fruit _: case Fruit _:
AddJudgement(new CatchJudgement { Result = HitResult.Perfect }); AddJudgement(new CatchJudgement { Result = HitResult.Perfect });
break; break;
} }
} }
}
base.SimulateAutoplay(beatmap); private const double harshness = 0.01;
protected override void OnNewJudgement(Judgement judgement)
{
base.OnNewJudgement(judgement);
if (judgement.Result == HitResult.Miss)
{
if (!judgement.IsBonus)
Health.Value -= hpDrainRate * (harshness * 2);
return;
}
if (judgement is CatchJudgement catchJudgement)
Health.Value += Math.Max(catchJudgement.HealthIncrease - hpDrainRate, 0) * harshness;
} }
} }
} }

View File

@ -38,14 +38,16 @@ namespace osu.Game.Rulesets.Catch.UI
{ {
switch (h) switch (h)
{ {
case Banana banana:
return new DrawableBanana(banana);
case Fruit fruit: case Fruit fruit:
return new DrawableFruit(fruit); return new DrawableFruit(fruit);
case JuiceStream stream: case JuiceStream stream:
return new DrawableJuiceStream(stream, GetVisualRepresentation); return new DrawableJuiceStream(stream, GetVisualRepresentation);
case BananaShower banana: case BananaShower shower:
return new DrawableBananaShower(banana, GetVisualRepresentation); return new DrawableBananaShower(shower, GetVisualRepresentation);
case TinyDroplet tiny: case TinyDroplet tiny:
return new DrawableDroplet(tiny) { Scale = new Vector2(0.5f) }; return new DrawableTinyDroplet(tiny);
case Droplet droplet: case Droplet droplet:
return new DrawableDroplet(droplet); return new DrawableDroplet(droplet);
} }

View File

@ -11,11 +11,13 @@ using osu.Framework.Graphics.Textures;
using osu.Framework.Input.Bindings; using osu.Framework.Input.Bindings;
using osu.Framework.MathUtils; using osu.Framework.MathUtils;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Rulesets.Catch.Judgements;
using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Catch.Objects.Drawable; using osu.Game.Rulesets.Catch.Objects.Drawable;
using osu.Game.Rulesets.Catch.Replays; using osu.Game.Rulesets.Catch.Replays;
using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.UI;
using OpenTK; using OpenTK;
using OpenTK.Graphics; using OpenTK.Graphics;
@ -77,12 +79,11 @@ namespace osu.Game.Rulesets.Catch.UI
if (!fruit.StaysOnPlate) if (!fruit.StaysOnPlate)
runAfterLoaded(() => MovableCatcher.Explode(caughtFruit)); runAfterLoaded(() => MovableCatcher.Explode(caughtFruit));
} }
if (fruit.HitObject.LastInCombo) if (fruit.HitObject.LastInCombo)
{ {
if (judgement.IsHit) if (((CatchJudgement)judgement).ShouldExplode)
runAfterLoaded(() => MovableCatcher.Explode()); runAfterLoaded(() => MovableCatcher.Explode());
else else
MovableCatcher.Drop(); MovableCatcher.Drop();
@ -93,7 +94,7 @@ namespace osu.Game.Rulesets.Catch.UI
{ {
base.UpdateAfterChildren(); base.UpdateAfterChildren();
var state = GetContainingInputManager().CurrentState as CatchFramedReplayInputHandler.CatchReplayState; var state = (GetContainingInputManager().CurrentState as RulesetInputManagerInputState<CatchAction>)?.LastReplayState as CatchFramedReplayInputHandler.CatchReplayState;
if (state?.CatcherX != null) if (state?.CatcherX != null)
MovableCatcher.X = state.CatcherX.Value; MovableCatcher.X = state.CatcherX.Value;
@ -105,6 +106,11 @@ namespace osu.Game.Rulesets.Catch.UI
public class Catcher : Container, IKeyBindingHandler<CatchAction> public class Catcher : Container, IKeyBindingHandler<CatchAction>
{ {
/// <summary>
/// Width of the area that can be used to attempt catches during gameplay.
/// </summary>
internal float CatchWidth => CATCHER_SIZE * Math.Abs(Scale.X);
private Container<DrawableHitObject> caughtFruit; private Container<DrawableHitObject> caughtFruit;
public Container ExplodingFruitTarget; public Container ExplodingFruitTarget;
@ -232,63 +238,74 @@ namespace osu.Game.Rulesets.Catch.UI
/// <returns>Whether the catch is possible.</returns> /// <returns>Whether the catch is possible.</returns>
public bool AttemptCatch(CatchHitObject fruit) public bool AttemptCatch(CatchHitObject fruit)
{ {
double halfCatcherWidth = CATCHER_SIZE * Math.Abs(Scale.X) * 0.5f; float halfCatchWidth = CatchWidth * 0.5f;
// this stuff wil disappear once we move fruit to non-relative coordinate space in the future. // this stuff wil disappear once we move fruit to non-relative coordinate space in the future.
var catchObjectPosition = fruit.X * CatchPlayfield.BASE_WIDTH; var catchObjectPosition = fruit.X * CatchPlayfield.BASE_WIDTH;
var catcherPosition = Position.X * CatchPlayfield.BASE_WIDTH; var catcherPosition = Position.X * CatchPlayfield.BASE_WIDTH;
var validCatch = var validCatch =
catchObjectPosition >= catcherPosition - halfCatcherWidth && catchObjectPosition >= catcherPosition - halfCatchWidth &&
catchObjectPosition <= catcherPosition + halfCatcherWidth; catchObjectPosition <= catcherPosition + halfCatchWidth;
if (validCatch && fruit.HyperDash) if (validCatch && fruit.HyperDash)
{ {
HyperDashModifier = Math.Abs(fruit.HyperDashTarget.X - fruit.X) / Math.Abs(fruit.HyperDashTarget.StartTime - fruit.StartTime) / BASE_SPEED; var target = fruit.HyperDashTarget;
HyperDashDirection = fruit.HyperDashTarget.X - fruit.X; double timeDifference = target.StartTime - fruit.StartTime;
double positionDifference = target.X * CatchPlayfield.BASE_WIDTH - catcherPosition;
double velocity = positionDifference / Math.Max(1.0, timeDifference - 1000.0 / 60.0);
SetHyperdashState(Math.Abs(velocity), target.X);
} }
else else
HyperDashModifier = 1; {
SetHyperdashState();
}
return validCatch; return validCatch;
} }
private double hyperDashModifier = 1;
private int hyperDashDirection;
private float hyperDashTargetPosition;
/// <summary> /// <summary>
/// Whether we are hypderdashing or not. /// Whether we are hypderdashing or not.
/// </summary> /// </summary>
public bool HyperDashing => hyperDashModifier != 1; public bool HyperDashing => hyperDashModifier != 1;
private double hyperDashModifier = 1;
/// <summary> /// <summary>
/// The direction in which hyperdash is allowed. 0 allows both directions. /// Set hyperdash state.
/// </summary> /// </summary>
public double HyperDashDirection; /// <param name="modifier">The speed multiplier. If this is less or equals to 1, this catcher will be non-hyperdashing state.</param>
/// <param name="targetPosition">When this catcher crosses this position, this catcher ends hyperdashing.</param>
/// <summary> public void SetHyperdashState(double modifier = 1, float targetPosition = -1)
/// The speed modifier resultant from hyperdash. Will trigger hyperdash when not equal to 1.
/// </summary>
public double HyperDashModifier
{ {
get { return hyperDashModifier; } const float hyperdash_transition_length = 180;
set
bool previouslyHyperDashing = HyperDashing;
if (modifier <= 1 || X == targetPosition)
{ {
if (value == hyperDashModifier) return; hyperDashModifier = 1;
hyperDashModifier = value; hyperDashDirection = 0;
const float transition_length = 180; if (previouslyHyperDashing)
if (HyperDashing)
{ {
this.FadeColour(Color4.OrangeRed, transition_length, Easing.OutQuint); this.FadeColour(Color4.White, hyperdash_transition_length, Easing.OutQuint);
this.FadeTo(0.2f, transition_length, Easing.OutQuint); this.FadeTo(1, hyperdash_transition_length, Easing.OutQuint);
Trail = true;
} }
else }
else
{
hyperDashModifier = modifier;
hyperDashDirection = Math.Sign(targetPosition - X);
hyperDashTargetPosition = targetPosition;
if (!previouslyHyperDashing)
{ {
HyperDashDirection = 0; this.FadeColour(Color4.OrangeRed, hyperdash_transition_length, Easing.OutQuint);
this.FadeColour(Color4.White, transition_length, Easing.OutQuint); this.FadeTo(0.2f, hyperdash_transition_length, Easing.OutQuint);
this.FadeTo(1, transition_length, Easing.OutQuint); Trail = true;
} }
} }
} }
@ -343,12 +360,18 @@ namespace osu.Game.Rulesets.Catch.UI
var direction = Math.Sign(currentDirection); var direction = Math.Sign(currentDirection);
double dashModifier = Dashing ? 1 : 0.5; double dashModifier = Dashing ? 1 : 0.5;
double speed = BASE_SPEED * dashModifier * hyperDashModifier;
if (hyperDashModifier != 1 && (HyperDashDirection == 0 || direction == Math.Sign(HyperDashDirection)))
dashModifier = hyperDashModifier;
Scale = new Vector2(Math.Abs(Scale.X) * direction, Scale.Y); Scale = new Vector2(Math.Abs(Scale.X) * direction, Scale.Y);
X = (float)MathHelper.Clamp(X + direction * Clock.ElapsedFrameTime * BASE_SPEED * dashModifier, 0, 1); X = (float)MathHelper.Clamp(X + direction * Clock.ElapsedFrameTime * speed, 0, 1);
// Correct overshooting.
if (hyperDashDirection > 0 && hyperDashTargetPosition < X ||
hyperDashDirection < 0 && hyperDashTargetPosition > X)
{
X = hyperDashTargetPosition;
SetHyperdashState();
}
} }
/// <summary> /// <summary>

View File

@ -12,7 +12,8 @@ using osu.Game.Tests.Beatmaps;
namespace osu.Game.Rulesets.Mania.Tests namespace osu.Game.Rulesets.Mania.Tests
{ {
internal class ManiaBeatmapConversionTest : BeatmapConversionTest<ConvertValue> [TestFixture]
public class ManiaBeatmapConversionTest : BeatmapConversionTest<ConvertValue>
{ {
protected override string ResourceAssembly => "osu.Game.Rulesets.Mania"; protected override string ResourceAssembly => "osu.Game.Rulesets.Mania";
@ -36,7 +37,7 @@ namespace osu.Game.Rulesets.Mania.Tests
protected override Ruleset CreateRuleset() => new ManiaRuleset(); protected override Ruleset CreateRuleset() => new ManiaRuleset();
} }
internal struct ConvertValue : IEquatable<ConvertValue> public struct ConvertValue : IEquatable<ConvertValue>
{ {
/// <summary> /// <summary>
/// A sane value to account for osu!stable using ints everwhere. /// A sane value to account for osu!stable using ints everwhere.

View File

@ -0,0 +1,45 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Input.Bindings;
using osu.Game.Tests.Visual;
namespace osu.Game.Rulesets.Mania.Tests
{
public abstract class ManiaInputTestCase : OsuTestCase
{
private readonly Container<Drawable> content;
protected override Container<Drawable> Content => content ?? base.Content;
protected ManiaInputTestCase(int keys)
{
base.Content.Add(content = new LocalInputManager(keys));
}
private class LocalInputManager : ManiaInputManager
{
public LocalInputManager(int variant)
: base(new ManiaRuleset().RulesetInfo, variant)
{
}
protected override RulesetKeyBindingContainer CreateKeyBindingContainer(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique)
=> new LocalKeyBindingContainer(ruleset, variant, unique);
private class LocalKeyBindingContainer : RulesetKeyBindingContainer
{
public LocalKeyBindingContainer(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique)
: base(ruleset, variant, unique)
{
}
protected override void ReloadMappings()
{
KeyBindings = DefaultKeyBindings;
}
}
}
}
}

View File

@ -0,0 +1,37 @@
// 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.Containers;
using osu.Game.Rulesets.Mania.UI;
using osu.Game.Rulesets.UI.Scrolling;
namespace osu.Game.Rulesets.Mania.Tests
{
/// <summary>
/// A container which provides a <see cref="IScrollingInfo"/> to children.
/// </summary>
public class ScrollingTestContainer : Container
{
private readonly ScrollingDirection direction;
public ScrollingTestContainer(ScrollingDirection direction)
{
this.direction = direction;
}
protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent)
{
var dependencies = new DependencyContainer(base.CreateLocalDependencies(parent));
dependencies.CacheAs<IScrollingInfo>(new ScrollingInfo { Direction = { Value = direction }});
return dependencies;
}
private class ScrollingInfo : IScrollingInfo
{
public readonly Bindable<ScrollingDirection> Direction = new Bindable<ScrollingDirection>();
IBindable<ScrollingDirection> IScrollingInfo.Direction => Direction;
}
}
}

View File

@ -0,0 +1,111 @@
// 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 NUnit.Framework;
using osu.Framework.Graphics;
using osu.Framework.Allocation;
using osu.Framework.Graphics.Containers;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.Objects.Drawables;
using osu.Game.Rulesets.Mania.UI;
using osu.Game.Rulesets.Mania.UI.Components;
using osu.Game.Rulesets.UI.Scrolling;
using OpenTK;
using OpenTK.Graphics;
namespace osu.Game.Rulesets.Mania.Tests
{
[TestFixture]
public class TestCaseColumn : ManiaInputTestCase
{
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(Column),
typeof(ColumnBackground),
typeof(ColumnKeyArea),
typeof(ColumnHitObjectArea)
};
private readonly List<Column> columns = new List<Column>();
public TestCaseColumn()
: base(2)
{
}
[BackgroundDependencyLoader]
private void load()
{
Child = new FillFlowContainer
{
RelativeSizeAxes = Axes.Both,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Spacing = new Vector2(20, 0),
Children = new[]
{
createColumn(ScrollingDirection.Up, ManiaAction.Key1),
createColumn(ScrollingDirection.Down, ManiaAction.Key2)
}
};
}
protected override void LoadComplete()
{
base.LoadComplete();
AddStep("note", createNote);
AddStep("hold note", createHoldNote);
}
private void createNote()
{
for (int i = 0; i < columns.Count; i++)
{
var obj = new Note { Column = i, StartTime = Time.Current + 2000 };
obj.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
columns[i].Add(new DrawableNote(obj));
}
}
private void createHoldNote()
{
for (int i = 0; i < columns.Count; i++)
{
var obj = new HoldNote { Column = i, StartTime = Time.Current + 2000, Duration = 500 };
obj.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
columns[i].Add(new DrawableHoldNote(obj));
}
}
private Drawable createColumn(ScrollingDirection direction, ManiaAction action)
{
var column = new Column(direction)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Height = 0.85f,
AccentColour = Color4.OrangeRed,
Action = { Value = action },
VisibleTimeRange = { Value = 2000 }
};
columns.Add(column);
return new ScrollingTestContainer(direction)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
AutoSizeAxes = Axes.X,
RelativeSizeAxes = Axes.Y,
Child = column
};
}
}
}

View File

@ -1,106 +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 NUnit.Framework;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.Objects.Drawables;
using osu.Game.Tests.Visual;
using OpenTK;
using OpenTK.Graphics;
namespace osu.Game.Rulesets.Mania.Tests
{
[TestFixture]
public class TestCaseManiaHitObjects : OsuTestCase
{
public TestCaseManiaHitObjects()
{
Note note1 = new Note();
Note note2 = new Note();
HoldNote holdNote = new HoldNote { StartTime = 1000 };
note1.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
note2.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
holdNote.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
Add(new FillFlowContainer
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Y,
Direction = FillDirection.Horizontal,
Spacing = new Vector2(10, 0),
// Imagine that the containers containing the drawable notes are the "columns"
Children = new Drawable[]
{
new Container
{
Name = "Normal note column",
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Y,
Width = 50,
Children = new[]
{
new Container
{
Name = "Timing section",
RelativeSizeAxes = Axes.Both,
RelativeChildSize = new Vector2(1, 10000),
Children = new[]
{
new DrawableNote(note1, ManiaAction.Key1)
{
Y = 5000,
LifetimeStart = double.MinValue,
LifetimeEnd = double.MaxValue,
AccentColour = Color4.Red
},
new DrawableNote(note2, ManiaAction.Key1)
{
Y = 6000,
LifetimeStart = double.MinValue,
LifetimeEnd = double.MaxValue,
AccentColour = Color4.Red
}
}
}
}
},
new Container
{
Name = "Hold note column",
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Y,
Width = 50,
Children = new[]
{
new Container
{
Name = "Timing section",
RelativeSizeAxes = Axes.Both,
RelativeChildSize = new Vector2(1, 10000),
Children = new[]
{
new DrawableHoldNote(holdNote, ManiaAction.Key1)
{
Y = 5000,
Height = 1000,
LifetimeStart = double.MinValue,
LifetimeEnd = double.MaxValue,
AccentColour = Color4.Red,
}
}
}
}
}
}
});
}
}
}

View File

@ -1,185 +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.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Timing;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Configuration;
using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Mania.Configuration;
using osu.Game.Rulesets.Mania.Judgements;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.Objects.Drawables;
using osu.Game.Rulesets.Mania.UI;
using osu.Game.Rulesets.Scoring;
using osu.Game.Tests.Visual;
namespace osu.Game.Rulesets.Mania.Tests
{
[TestFixture]
public class TestCaseManiaPlayfield : OsuTestCase
{
private const double start_time = 500;
private const double duration = 500;
protected override double TimePerAction => 200;
private RulesetInfo maniaRuleset;
public TestCaseManiaPlayfield()
{
var rng = new Random(1337);
AddStep("1 column", () => createPlayfield(1));
AddStep("4 columns", () => createPlayfield(4));
AddStep("5 columns", () => createPlayfield(5));
AddStep("8 columns", () => createPlayfield(8));
AddStep("4 + 4 columns", () =>
{
var stages = new List<StageDefinition>
{
new StageDefinition { Columns = 4 },
new StageDefinition { Columns = 4 },
};
createPlayfield(stages);
});
AddStep("2 + 4 + 2 columns", () =>
{
var stages = new List<StageDefinition>
{
new StageDefinition { Columns = 2 },
new StageDefinition { Columns = 4 },
new StageDefinition { Columns = 2 },
};
createPlayfield(stages);
});
AddStep("1 + 8 + 1 columns", () =>
{
var stages = new List<StageDefinition>
{
new StageDefinition { Columns = 1 },
new StageDefinition { Columns = 8 },
new StageDefinition { Columns = 1 },
};
createPlayfield(stages);
});
AddStep("Reversed", () => createPlayfield(4, true));
AddStep("Notes with input", () => createPlayfieldWithNotes());
AddStep("Notes with input (reversed)", () => createPlayfieldWithNotes(true));
AddStep("Notes with gravity", () => createPlayfieldWithNotes());
AddStep("Notes with gravity (reversed)", () => createPlayfieldWithNotes(true));
AddStep("Hit explosion", () =>
{
var playfield = createPlayfield(4);
int col = rng.Next(0, 4);
var note = new Note { Column = col };
note.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
var drawableNote = new DrawableNote(note, ManiaAction.Key1)
{
AccentColour = playfield.Columns.ElementAt(col).AccentColour
};
playfield.OnJudgement(drawableNote, new ManiaJudgement { Result = HitResult.Perfect });
playfield.Columns[col].OnJudgement(drawableNote, new ManiaJudgement { Result = HitResult.Perfect });
});
}
[BackgroundDependencyLoader]
private void load(RulesetStore rulesets, SettingsStore settings)
{
maniaRuleset = rulesets.GetRuleset(3);
Dependencies.Cache(new ManiaConfigManager(settings, maniaRuleset, 4));
}
private ManiaPlayfield createPlayfield(int cols, bool inverted = false)
{
var stages = new List<StageDefinition>
{
new StageDefinition { Columns = cols },
};
return createPlayfield(stages, inverted);
}
private ManiaPlayfield createPlayfield(List<StageDefinition> stages, bool inverted = false)
{
Clear();
var inputManager = new ManiaInputManager(maniaRuleset, stages.Sum(g => g.Columns)) { RelativeSizeAxes = Axes.Both };
Add(inputManager);
ManiaPlayfield playfield;
inputManager.Add(playfield = new ManiaPlayfield(stages)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
});
playfield.Inverted.Value = inverted;
return playfield;
}
private void createPlayfieldWithNotes(bool inverted = false)
{
Clear();
var rateAdjustClock = new StopwatchClock(true) { Rate = 1 };
var inputManager = new ManiaInputManager(maniaRuleset, 4) { RelativeSizeAxes = Axes.Both };
Add(inputManager);
ManiaPlayfield playfield;
var stages = new List<StageDefinition>
{
new StageDefinition { Columns = 4 },
};
inputManager.Add(playfield = new ManiaPlayfield(stages)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Clock = new FramedClock(rateAdjustClock)
});
playfield.Inverted.Value = inverted;
for (double t = start_time; t <= start_time + duration; t += 100)
{
var note1 = new Note { StartTime = t, Column = 0 };
var note2 = new Note { StartTime = t, Column = 3 };
note1.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
note2.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
playfield.Add(new DrawableNote(note1, ManiaAction.Key1));
playfield.Add(new DrawableNote(note2, ManiaAction.Key4));
}
var holdNote1 = new HoldNote { StartTime = start_time, Duration = duration, Column = 1 };
var holdNote2 = new HoldNote { StartTime = start_time, Duration = duration, Column = 2 };
holdNote1.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
holdNote2.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
playfield.Add(new DrawableHoldNote(holdNote1, ManiaAction.Key2));
playfield.Add(new DrawableHoldNote(holdNote2, ManiaAction.Key3));
}
}
}

View File

@ -0,0 +1,173 @@
// 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 System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Configuration;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.Objects.Drawables;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.UI.Scrolling;
using osu.Game.Tests.Visual;
using OpenTK;
using OpenTK.Graphics;
namespace osu.Game.Rulesets.Mania.Tests
{
[TestFixture]
public class TestCaseNotes : OsuTestCase
{
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(DrawableNote),
typeof(DrawableHoldNote)
};
[BackgroundDependencyLoader]
private void load()
{
Child = new FillFlowContainer
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal,
Spacing = new Vector2(20),
Children = new[]
{
createNoteDisplay(ScrollingDirection.Down),
createNoteDisplay(ScrollingDirection.Up),
createHoldNoteDisplay(ScrollingDirection.Down),
createHoldNoteDisplay(ScrollingDirection.Up),
}
};
}
private Drawable createNoteDisplay(ScrollingDirection direction)
{
var note = new Note { StartTime = 999999999 };
note.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
return new ScrollingTestContainer(direction)
{
AutoSizeAxes = Axes.Both,
Child = new NoteContainer(direction, $"note, scrolling {direction.ToString().ToLower()}")
{
Child = new DrawableNote(note) { AccentColour = Color4.OrangeRed }
}
};
}
private Drawable createHoldNoteDisplay(ScrollingDirection direction)
{
var note = new HoldNote { StartTime = 999999999, Duration = 1000 };
note.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
return new ScrollingTestContainer(direction)
{
AutoSizeAxes = Axes.Both,
Child = new NoteContainer(direction, $"hold note, scrolling {direction.ToString().ToLower()}")
{
Child = new DrawableHoldNote(note)
{
RelativeSizeAxes = Axes.Both,
AccentColour = Color4.OrangeRed,
}
}
};
}
private class NoteContainer : Container
{
private readonly Container content;
protected override Container<Drawable> Content => content;
private readonly ScrollingDirection direction;
public NoteContainer(ScrollingDirection direction, string description)
{
this.direction = direction;
AutoSizeAxes = Axes.Both;
InternalChild = new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Spacing = new Vector2(0, 10),
Direction = FillDirection.Vertical,
Children = new Drawable[]
{
new Container
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
Width = 45,
Height = 100,
Children = new Drawable[]
{
new Box
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
RelativeSizeAxes = Axes.Both,
Width = 1.25f,
Colour = Color4.Black.Opacity(0.5f)
},
content = new Container { RelativeSizeAxes = Axes.Both }
}
},
new SpriteText
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
TextSize = 14,
Text = description
}
}
};
}
protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent)
{
var dependencies = new DependencyContainer(base.CreateLocalDependencies(parent));
dependencies.CacheAs<IBindable<ManiaAction>>(new Bindable<ManiaAction>());
return dependencies;
}
protected override void Update()
{
base.Update();
foreach (var obj in content.OfType<DrawableHitObject>())
{
if (!(obj.HitObject is IHasEndTime endTime))
continue;
foreach (var nested in obj.NestedHitObjects)
{
double finalPosition = (nested.HitObject.StartTime - obj.HitObject.StartTime) / endTime.Duration;
switch (direction)
{
case ScrollingDirection.Up:
nested.Y = (float)(finalPosition * content.DrawHeight);
break;
case ScrollingDirection.Down:
nested.Y = (float)(-finalPosition * content.DrawHeight);
break;
}
}
}
}
}
}
}

View File

@ -0,0 +1,121 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Collections.Generic;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.Objects.Drawables;
using osu.Game.Rulesets.Mania.UI;
using osu.Game.Rulesets.UI.Scrolling;
using OpenTK;
namespace osu.Game.Rulesets.Mania.Tests
{
[TestFixture]
public class TestCaseStage : ManiaInputTestCase
{
private const int columns = 4;
private readonly List<ManiaStage> stages = new List<ManiaStage>();
public TestCaseStage()
: base(columns)
{
}
[BackgroundDependencyLoader]
private void load()
{
Child = new FillFlowContainer
{
RelativeSizeAxes = Axes.Both,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Spacing = new Vector2(20, 0),
Children = new[]
{
createStage(ScrollingDirection.Up, ManiaAction.Key1),
createStage(ScrollingDirection.Down, ManiaAction.Key3)
}
};
}
protected override void LoadComplete()
{
base.LoadComplete();
AddStep("note", createNote);
AddStep("hold note", createHoldNote);
AddStep("minor bar line", () => createBarLine(false));
AddStep("major bar line", () => createBarLine(true));
}
private void createNote()
{
foreach (var stage in stages)
{
for (int i = 0; i < stage.Columns.Count; i++)
{
var obj = new Note { Column = i, StartTime = Time.Current + 2000 };
obj.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
stage.Add(new DrawableNote(obj));
}
}
}
private void createHoldNote()
{
foreach (var stage in stages)
{
for (int i = 0; i < stage.Columns.Count; i++)
{
var obj = new HoldNote { Column = i, StartTime = Time.Current + 2000, Duration = 500 };
obj.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
stage.Add(new DrawableHoldNote(obj));
}
}
}
private void createBarLine(bool major)
{
foreach (var stage in stages)
{
var obj = new BarLine
{
StartTime = Time.Current + 2000,
ControlPoint = new TimingControlPoint(),
BeatIndex = major ? 0 : 1
};
obj.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
stage.Add(obj);
}
}
private Drawable createStage(ScrollingDirection direction, ManiaAction action)
{
var specialAction = ManiaAction.Special1;
var stage = new ManiaStage(direction, 0, new StageDefinition { Columns = 2 }, ref action, ref specialAction) { VisibleTimeRange = { Value = 2000 } };
stages.Add(stage);
return new ScrollingTestContainer(direction)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Y,
AutoSizeAxes = Axes.X,
Child = stage
};
}
}
}

View File

@ -329,7 +329,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
break; break;
} }
bool isDoubleSample(SampleInfo sample) => sample.Name == SampleInfo.HIT_CLAP && sample.Name == SampleInfo.HIT_FINISH; bool isDoubleSample(SampleInfo sample) => sample.Name == SampleInfo.HIT_CLAP || sample.Name == SampleInfo.HIT_FINISH;
bool canGenerateTwoNotes = (convertType & PatternType.LowProbability) == 0; bool canGenerateTwoNotes = (convertType & PatternType.LowProbability) == 0;
canGenerateTwoNotes &= HitObject.Samples.Any(isDoubleSample) || sampleInfoListAt(HitObject.StartTime).Any(isDoubleSample); canGenerateTwoNotes &= HitObject.Samples.Any(isDoubleSample) || sampleInfoListAt(HitObject.StartTime).Any(isDoubleSample);

View File

@ -4,6 +4,7 @@
using osu.Framework.Configuration.Tracking; using osu.Framework.Configuration.Tracking;
using osu.Game.Configuration; using osu.Game.Configuration;
using osu.Game.Rulesets.Configuration; using osu.Game.Rulesets.Configuration;
using osu.Game.Rulesets.Mania.UI;
namespace osu.Game.Rulesets.Mania.Configuration namespace osu.Game.Rulesets.Mania.Configuration
{ {
@ -19,6 +20,7 @@ namespace osu.Game.Rulesets.Mania.Configuration
base.InitialiseDefaults(); base.InitialiseDefaults();
Set(ManiaSetting.ScrollTime, 1500.0, 50.0, 10000.0, 50.0); Set(ManiaSetting.ScrollTime, 1500.0, 50.0, 10000.0, 50.0);
Set(ManiaSetting.ScrollDirection, ManiaScrollingDirection.Down);
} }
public override TrackedSettings CreateTrackedSettings() => new TrackedSettings public override TrackedSettings CreateTrackedSettings() => new TrackedSettings
@ -29,6 +31,7 @@ namespace osu.Game.Rulesets.Mania.Configuration
public enum ManiaSetting public enum ManiaSetting
{ {
ScrollTime ScrollTime,
ScrollDirection
} }
} }

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.Difficulty;
using osu.Game.Rulesets.Mods;
namespace osu.Game.Rulesets.Mania.Difficulty
{
public class ManiaDifficultyAttributes : DifficultyAttributes
{
public double GreatHitWindow;
public ManiaDifficultyAttributes(Mod[] mods, double starRating)
: base(mods, starRating)
{
}
}
}

View File

@ -39,6 +39,9 @@ namespace osu.Game.Rulesets.Mania.Difficulty
protected override DifficultyAttributes Calculate(IBeatmap beatmap, Mod[] mods, double timeRate) protected override DifficultyAttributes Calculate(IBeatmap beatmap, Mod[] mods, double timeRate)
{ {
if (!beatmap.HitObjects.Any())
return new ManiaDifficultyAttributes(mods, 0);
var difficultyHitObjects = new List<ManiaHitObjectDifficulty>(); var difficultyHitObjects = new List<ManiaHitObjectDifficulty>();
int columnCount = ((ManiaBeatmap)beatmap).TotalColumns; int columnCount = ((ManiaBeatmap)beatmap).TotalColumns;
@ -50,9 +53,14 @@ namespace osu.Game.Rulesets.Mania.Difficulty
if (!calculateStrainValues(difficultyHitObjects, timeRate)) if (!calculateStrainValues(difficultyHitObjects, timeRate))
return new DifficultyAttributes(mods, 0); return new DifficultyAttributes(mods, 0);
double starRating = calculateDifficulty(difficultyHitObjects, timeRate) * star_scaling_factor; double starRating = calculateDifficulty(difficultyHitObjects, timeRate) * star_scaling_factor;
return new DifficultyAttributes(mods, starRating); return new ManiaDifficultyAttributes(mods, starRating)
{
// Todo: This int cast is temporary to achieve 1:1 results with osu!stable, and should be remoevd in the future
GreatHitWindow = (int)(beatmap.HitObjects.First().HitWindows.Great / 2) / timeRate
};
} }
private bool calculateStrainValues(List<ManiaHitObjectDifficulty> objects, double timeRate) private bool calculateStrainValues(List<ManiaHitObjectDifficulty> objects, double timeRate)

View File

@ -13,6 +13,8 @@ namespace osu.Game.Rulesets.Mania.Difficulty
{ {
public class ManiaPerformanceCalculator : PerformanceCalculator public class ManiaPerformanceCalculator : PerformanceCalculator
{ {
protected new ManiaDifficultyAttributes Attributes => (ManiaDifficultyAttributes)base.Attributes;
private Mod[] mods; private Mod[] mods;
// Score after being scaled by non-difficulty-increasing mods // Score after being scaled by non-difficulty-increasing mods
@ -105,14 +107,12 @@ namespace osu.Game.Rulesets.Mania.Difficulty
private double computeAccuracyValue(double strainValue) private double computeAccuracyValue(double strainValue)
{ {
// Todo: This int cast is temporary to achieve 1:1 results with osu!stable, and should be remoevd in the future if (Attributes.GreatHitWindow <= 0)
double hitWindowGreat = (int)(Beatmap.HitObjects.First().HitWindows.Great / 2) / TimeRate;
if (hitWindowGreat <= 0)
return 0; return 0;
// Lots of arbitrary values from testing. // Lots of arbitrary values from testing.
// Considering to use derivation from perfect accuracy in a probabilistic manner - assume normal distribution // Considering to use derivation from perfect accuracy in a probabilistic manner - assume normal distribution
double accuracyValue = Math.Max(0.0, 0.2 - (hitWindowGreat - 34) * 0.006667) double accuracyValue = Math.Max(0.0, 0.2 - (Attributes.GreatHitWindow - 34) * 0.006667)
* strainValue * strainValue
* Math.Pow(Math.Max(0.0, scaledScore - 960000) / 40000, 1.1); * Math.Pow(Math.Max(0.0, scaledScore - 960000) / 40000, 1.1);

View File

@ -8,6 +8,7 @@ namespace osu.Game.Rulesets.Mania.Judgements
public class HoldNoteJudgement : ManiaJudgement public class HoldNoteJudgement : ManiaJudgement
{ {
public override bool AffectsCombo => false; public override bool AffectsCombo => false;
protected override int NumericResultFor(HitResult result) => 0; protected override int NumericResultFor(HitResult result) => 0;
} }
} }

View File

@ -16,6 +16,7 @@ using osu.Game.Rulesets.Mania.Replays;
using osu.Game.Rulesets.Replays.Types; using osu.Game.Rulesets.Replays.Types;
using osu.Game.Beatmaps.Legacy; using osu.Game.Beatmaps.Legacy;
using osu.Game.Configuration; using osu.Game.Configuration;
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.Mania.Beatmaps; using osu.Game.Rulesets.Mania.Beatmaps;
@ -155,6 +156,8 @@ namespace osu.Game.Rulesets.Mania
public override IRulesetConfigManager CreateConfig(SettingsStore settings) => new ManiaConfigManager(settings, RulesetInfo); public override IRulesetConfigManager CreateConfig(SettingsStore settings) => new ManiaConfigManager(settings, RulesetInfo);
public override RulesetSettingsSubsection CreateSettings() => new ManiaSettingsSubsection(this);
public ManiaRuleset(RulesetInfo rulesetInfo = null) public ManiaRuleset(RulesetInfo rulesetInfo = null)
: base(rulesetInfo) : base(rulesetInfo)
{ {

View File

@ -0,0 +1,34 @@
// 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.Graphics;
using osu.Game.Overlays.Settings;
using osu.Game.Rulesets.Mania.Configuration;
using osu.Game.Rulesets.Mania.UI;
namespace osu.Game.Rulesets.Mania
{
public class ManiaSettingsSubsection : RulesetSettingsSubsection
{
protected override string Header => "osu!mania";
public ManiaSettingsSubsection(ManiaRuleset ruleset)
: base(ruleset)
{
}
[BackgroundDependencyLoader]
private void load(ManiaConfigManager config)
{
Children = new Drawable[]
{
new SettingsEnumDropdown<ManiaScrollingDirection>
{
LabelText = "Scrolling direction",
Bindable = config.GetBindable<ManiaScrollingDirection>(ManiaSetting.ScrollDirection)
}
};
}
}
}

View File

@ -10,6 +10,7 @@ using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Mania.Judgements; 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;
namespace osu.Game.Rulesets.Mania.Objects.Drawables namespace osu.Game.Rulesets.Mania.Objects.Drawables
{ {
@ -37,8 +38,8 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
private readonly Container<DrawableHoldNoteTick> tickContainer; private readonly Container<DrawableHoldNoteTick> tickContainer;
public DrawableHoldNote(HoldNote hitObject, ManiaAction action) public DrawableHoldNote(HoldNote hitObject)
: base(hitObject, action) : base(hitObject)
{ {
RelativeSizeAxes = Axes.X; RelativeSizeAxes = Axes.X;
@ -56,12 +57,12 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
HoldStartTime = () => holdStartTime HoldStartTime = () => holdStartTime
}) })
}, },
head = new DrawableHeadNote(this, action) head = new DrawableHeadNote(this)
{ {
Anchor = Anchor.TopCentre, Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre Origin = Anchor.TopCentre
}, },
tail = new DrawableTailNote(this, action) tail = new DrawableTailNote(this)
{ {
Anchor = Anchor.TopCentre, Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre Origin = Anchor.TopCentre
@ -75,6 +76,13 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
AddNested(tail); AddNested(tail);
} }
protected override void OnDirectionChanged(ScrollingDirection direction)
{
base.OnDirectionChanged(direction);
bodyPiece.Anchor = bodyPiece.Origin = direction == ScrollingDirection.Up ? Anchor.TopLeft : Anchor.BottomLeft;
}
public override Color4 AccentColour public override Color4 AccentColour
{ {
get { return base.AccentColour; } get { return base.AccentColour; }
@ -100,7 +108,7 @@ 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 = 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;
} }
@ -110,7 +118,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
if (Time.Current < HitObject.StartTime || Time.Current > HitObject.EndTime) if (Time.Current < HitObject.StartTime || Time.Current > HitObject.EndTime)
return false; return false;
if (action != Action) if (action != Action.Value)
return false; return false;
// 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
@ -127,7 +135,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
if (!holdStartTime.HasValue) if (!holdStartTime.HasValue)
return false; return false;
if (action != Action) if (action != Action.Value)
return false; return false;
holdStartTime = null; holdStartTime = null;
@ -146,8 +154,8 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
{ {
private readonly DrawableHoldNote holdNote; private readonly DrawableHoldNote holdNote;
public DrawableHeadNote(DrawableHoldNote holdNote, ManiaAction action) public DrawableHeadNote(DrawableHoldNote holdNote)
: base(holdNote.HitObject.Head, action) : base(holdNote.HitObject.Head)
{ {
this.holdNote = holdNote; this.holdNote = holdNote;
} }
@ -183,8 +191,8 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
private readonly DrawableHoldNote holdNote; private readonly DrawableHoldNote holdNote;
public DrawableTailNote(DrawableHoldNote holdNote, ManiaAction action) public DrawableTailNote(DrawableHoldNote holdNote)
: base(holdNote.HitObject.Tail, action) : base(holdNote.HitObject.Tail)
{ {
this.holdNote = holdNote; this.holdNote = holdNote;
} }
@ -227,7 +235,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
if (!holdNote.holdStartTime.HasValue) if (!holdNote.holdStartTime.HasValue)
return false; return false;
if (action != Action) if (action != Action.Value)
return false; return false;
UpdateJudgement(true); UpdateJudgement(true);

View File

@ -1,31 +1,55 @@
// 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 JetBrains.Annotations;
using osu.Framework.Allocation;
using osu.Framework.Configuration;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Game.Rulesets.Mania.UI;
using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.UI.Scrolling;
namespace osu.Game.Rulesets.Mania.Objects.Drawables namespace osu.Game.Rulesets.Mania.Objects.Drawables
{ {
public abstract class DrawableManiaHitObject<TObject> : DrawableHitObject<ManiaHitObject> public abstract class DrawableManiaHitObject : DrawableHitObject<ManiaHitObject>
where TObject : ManiaHitObject
{ {
/// <summary> /// <summary>
/// The key that will trigger input for this hit object. /// The <see cref="ManiaAction"/> which causes this <see cref="DrawableManiaHitObject{TObject}"/> to be hit.
/// </summary> /// </summary>
protected ManiaAction Action { get; } protected readonly IBindable<ManiaAction> Action = new Bindable<ManiaAction>();
public new TObject HitObject; protected readonly IBindable<ScrollingDirection> Direction = new Bindable<ScrollingDirection>();
protected DrawableManiaHitObject(TObject hitObject, ManiaAction? action = null) protected DrawableManiaHitObject(ManiaHitObject hitObject)
: base(hitObject) : base(hitObject)
{ {
Anchor = Anchor.TopCentre; }
Origin = Anchor.TopCentre;
HitObject = hitObject;
[BackgroundDependencyLoader(true)]
private void load([CanBeNull] IBindable<ManiaAction> action, [NotNull] IScrollingInfo scrollingInfo)
{
if (action != null) if (action != null)
Action = action.Value; Action.BindTo(action);
Direction.BindTo(scrollingInfo.Direction);
Direction.BindValueChanged(OnDirectionChanged, true);
}
protected virtual void OnDirectionChanged(ScrollingDirection direction)
{
Anchor = Origin = direction == ScrollingDirection.Up ? Anchor.TopCentre : Anchor.BottomCentre;
}
}
public abstract class DrawableManiaHitObject<TObject> : DrawableManiaHitObject
where TObject : ManiaHitObject
{
public new readonly TObject HitObject;
protected DrawableManiaHitObject(TObject hitObject)
: base(hitObject)
{
HitObject = hitObject;
} }
protected override void UpdateState(ArmedState state) protected override void UpdateState(ArmedState state)

View File

@ -9,6 +9,7 @@ using osu.Framework.Input.Bindings;
using osu.Game.Rulesets.Mania.Judgements; 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;
namespace osu.Game.Rulesets.Mania.Objects.Drawables namespace osu.Game.Rulesets.Mania.Objects.Drawables
{ {
@ -19,8 +20,8 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
{ {
private readonly NotePiece headPiece; private readonly NotePiece headPiece;
public DrawableNote(Note hitObject, ManiaAction action) public DrawableNote(Note hitObject)
: base(hitObject, action) : base(hitObject)
{ {
RelativeSizeAxes = Axes.X; RelativeSizeAxes = Axes.X;
AutoSizeAxes = Axes.Y; AutoSizeAxes = Axes.Y;
@ -28,14 +29,14 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
CornerRadius = 5; CornerRadius = 5;
Masking = true; Masking = true;
InternalChildren = new Drawable[] InternalChild = headPiece = new NotePiece();
{ }
headPiece = new NotePiece
{ protected override void OnDirectionChanged(ScrollingDirection direction)
Anchor = Anchor.TopCentre, {
Origin = Anchor.TopCentre base.OnDirectionChanged(direction);
}
}; headPiece.Anchor = headPiece.Origin = direction == ScrollingDirection.Up ? Anchor.TopCentre : Anchor.BottomCentre;
} }
public override Color4 AccentColour public override Color4 AccentColour
@ -73,7 +74,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
public virtual bool OnPressed(ManiaAction action) public virtual bool OnPressed(ManiaAction action)
{ {
if (action != Action) if (action != Action.Value)
return false; return false;
return UpdateJudgement(true); return UpdateJudgement(true);

View File

@ -1,12 +1,16 @@
// 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.Configuration;
using OpenTK.Graphics; 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.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Rulesets.Mania.UI;
using osu.Game.Rulesets.UI.Scrolling;
namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces
{ {
@ -18,6 +22,8 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces
public const float NOTE_HEIGHT = 10; public const float NOTE_HEIGHT = 10;
private const float head_colour_height = 6; private const float head_colour_height = 6;
private readonly IBindable<ScrollingDirection> direction = new Bindable<ScrollingDirection>();
private readonly Box colouredBox; private readonly Box colouredBox;
public NotePiece() public NotePiece()
@ -33,8 +39,6 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces
}, },
colouredBox = new Box colouredBox = new Box
{ {
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
RelativeSizeAxes = Axes.X, RelativeSizeAxes = Axes.X,
Height = head_colour_height, Height = head_colour_height,
Alpha = 0.2f Alpha = 0.2f
@ -42,6 +46,16 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces
}; };
} }
[BackgroundDependencyLoader]
private void load(IScrollingInfo scrollingInfo)
{
direction.BindTo(scrollingInfo.Direction);
direction.BindValueChanged(direction =>
{
colouredBox.Anchor = colouredBox.Origin = direction == ScrollingDirection.Up ? Anchor.TopCentre : Anchor.BottomCentre;
}, true);
}
private Color4 accentColour; private Color4 accentColour;
public Color4 AccentColour public Color4 AccentColour
{ {

View File

@ -17,6 +17,6 @@ namespace osu.Game.Rulesets.Mania.Replays
protected override bool IsImportant(ManiaReplayFrame frame) => frame.Actions.Any(); protected override bool IsImportant(ManiaReplayFrame frame) => frame.Actions.Any();
public override List<InputState> GetPendingStates() => new List<InputState> { new ReplayState<ManiaAction> { PressedActions = CurrentFrame.Actions } }; public override List<IInput> GetPendingInputs() => new List<IInput> { new ReplayState<ManiaAction> { PressedActions = CurrentFrame.Actions } };
} }
} }

View File

@ -1,50 +1,39 @@
// 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 OpenTK;
using OpenTK.Graphics; using OpenTK.Graphics;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Colour;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Drawables;
using System;
using System.Linq; using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Configuration;
using osu.Framework.Input.Bindings; using osu.Framework.Input.Bindings;
using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Mania.UI.Components;
using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Rulesets.UI.Scrolling;
namespace osu.Game.Rulesets.Mania.UI namespace osu.Game.Rulesets.Mania.UI
{ {
public class Column : ScrollingPlayfield, IKeyBindingHandler<ManiaAction>, IHasAccentColour public class Column : ManiaScrollingPlayfield, IKeyBindingHandler<ManiaAction>, IHasAccentColour
{ {
private const float key_icon_size = 10;
private const float key_icon_corner_radius = 3;
private const float key_icon_border_radius = 2;
private const float hit_target_height = 10;
private const float hit_target_bar_height = 2;
private const float column_width = 45; private const float column_width = 45;
private const float special_column_width = 70; private const float special_column_width = 70;
public ManiaAction Action; public readonly Bindable<ManiaAction> Action = new Bindable<ManiaAction>();
private readonly Box background; private readonly ColumnBackground background;
private readonly Box backgroundOverlay; private readonly ColumnKeyArea keyArea;
private readonly Container hitTargetBar; private readonly ColumnHitObjectArea hitObjectArea;
private readonly Container keyIcon;
internal readonly Container TopLevelContainer; internal readonly Container TopLevelContainer;
private readonly Container explosionContainer; private readonly Container explosionContainer;
protected override Container<Drawable> Content => content; protected override Container<Drawable> Content => hitObjectArea;
private readonly Container<Drawable> content;
public Column() public Column(ScrollingDirection direction)
: base(ScrollingDirection.Up) : base(direction)
{ {
RelativeSizeAxes = Axes.Y; RelativeSizeAxes = Axes.Y;
Width = column_width; Width = column_width;
@ -52,71 +41,21 @@ namespace osu.Game.Rulesets.Mania.UI
Masking = true; Masking = true;
CornerRadius = 5; CornerRadius = 5;
InternalChildren = new Drawable[] background = new ColumnBackground { RelativeSizeAxes = Axes.Both };
Container hitTargetContainer;
InternalChildren = new[]
{ {
background = new Box // For input purposes, the background is added at the highest depth, but is then proxied back below all other elements
{ background.CreateProxy(),
Name = "Background", hitTargetContainer = new Container
RelativeSizeAxes = Axes.Both,
Alpha = 0.3f
},
backgroundOverlay = new Box
{
Name = "Background Gradient Overlay",
RelativeSizeAxes = Axes.Both,
Height = 0.5f,
Anchor = Anchor.TopLeft,
Origin = Anchor.TopLeft,
Blending = BlendingMode.Additive,
Alpha = 0
},
new Container
{ {
Name = "Hit target + hit objects", Name = "Hit target + hit objects",
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding { Top = ManiaStage.HIT_TARGET_POSITION },
Children = new Drawable[] Children = new Drawable[]
{ {
new Container hitObjectArea = new ColumnHitObjectArea { RelativeSizeAxes = Axes.Both },
{
Name = "Hit target",
RelativeSizeAxes = Axes.X,
Height = hit_target_height,
Children = new Drawable[]
{
new Box
{
Name = "Background",
RelativeSizeAxes = Axes.Both,
Colour = Color4.Black
},
hitTargetBar = new Container
{
Name = "Bar",
RelativeSizeAxes = Axes.X,
Height = hit_target_bar_height,
Masking = true,
Children = new[]
{
new Box
{
RelativeSizeAxes = Axes.Both
}
}
}
}
},
content = new Container
{
Name = "Hit objects",
RelativeSizeAxes = Axes.Both,
},
// For column lighting, we need to capture input events before the notes
new InputTarget
{
Pressed = onPressed,
Released = onReleased
},
explosionContainer = new Container explosionContainer = new Container
{ {
Name = "Hit explosions", Name = "Hit explosions",
@ -124,46 +63,27 @@ namespace osu.Game.Rulesets.Mania.UI
} }
} }
}, },
new Container keyArea = new ColumnKeyArea
{ {
Name = "Key",
RelativeSizeAxes = Axes.X, RelativeSizeAxes = Axes.X,
Height = ManiaStage.HIT_TARGET_POSITION, Height = ManiaStage.HIT_TARGET_POSITION,
Children = new Drawable[]
{
new Box
{
Name = "Key gradient",
RelativeSizeAxes = Axes.Both,
Colour = ColourInfo.GradientVertical(Color4.Black, Color4.Black.Opacity(0)),
Alpha = 0.5f
},
keyIcon = new Container
{
Name = "Key icon",
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = new Vector2(key_icon_size),
Masking = true,
CornerRadius = key_icon_corner_radius,
BorderThickness = 2,
BorderColour = Color4.White, // Not true
Children = new[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Alpha = 0,
AlwaysPresent = true
}
}
}
}
}, },
background,
TopLevelContainer = new Container { RelativeSizeAxes = Axes.Both } TopLevelContainer = new Container { RelativeSizeAxes = Axes.Both }
}; };
TopLevelContainer.Add(explosionContainer.CreateProxy()); TopLevelContainer.Add(explosionContainer.CreateProxy());
Direction.BindValueChanged(d =>
{
hitTargetContainer.Padding = new MarginPadding
{
Top = d == ScrollingDirection.Up ? ManiaStage.HIT_TARGET_POSITION : 0,
Bottom = d == ScrollingDirection.Down ? ManiaStage.HIT_TARGET_POSITION : 0,
};
keyArea.Anchor = keyArea.Origin= d == ScrollingDirection.Up ? Anchor.TopLeft : Anchor.BottomLeft;
}, true);
} }
public override Axes RelativeSizeAxes => Axes.Y; public override Axes RelativeSizeAxes => Axes.Y;
@ -192,25 +112,19 @@ namespace osu.Game.Rulesets.Mania.UI
return; return;
accentColour = value; accentColour = value;
background.Colour = accentColour; background.AccentColour = value;
backgroundOverlay.Colour = ColourInfo.GradientVertical(accentColour.Opacity(0.6f), accentColour.Opacity(0)); keyArea.AccentColour = value;
hitObjectArea.AccentColour = value;
hitTargetBar.EdgeEffect = new EdgeEffectParameters
{
Type = EdgeEffectType.Glow,
Radius = 5,
Colour = accentColour.Opacity(0.5f),
};
keyIcon.EdgeEffect = new EdgeEffectParameters
{
Type = EdgeEffectType.Glow,
Radius = 5,
Colour = accentColour.Opacity(0.5f),
};
} }
} }
protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent)
{
var dependencies = new DependencyContainer(base.CreateLocalDependencies(parent));
dependencies.CacheAs<IBindable<ManiaAction>>(Action);
return dependencies;
}
/// <summary> /// <summary>
/// Adds a DrawableHitObject to this Playfield. /// Adds a DrawableHitObject to this Playfield.
/// </summary> /// </summary>
@ -228,48 +142,10 @@ namespace osu.Game.Rulesets.Mania.UI
if (!judgement.IsHit || !judgedObject.DisplayJudgement) if (!judgement.IsHit || !judgedObject.DisplayJudgement)
return; return;
explosionContainer.Add(new HitExplosion(judgedObject)); explosionContainer.Add(new HitExplosion(judgedObject)
}
private bool onPressed(ManiaAction action)
{
if (action == Action)
{ {
backgroundOverlay.FadeTo(1, 50, Easing.OutQuint).Then().FadeTo(0.5f, 250, Easing.OutQuint); Anchor = Direction == ScrollingDirection.Up ? Anchor.TopCentre : Anchor.BottomCentre
keyIcon.ScaleTo(1.4f, 50, Easing.OutQuint).Then().ScaleTo(1.3f, 250, Easing.OutQuint); });
}
return false;
}
private bool onReleased(ManiaAction action)
{
if (action == Action)
{
backgroundOverlay.FadeTo(0, 250, Easing.OutQuint);
keyIcon.ScaleTo(1f, 125, Easing.OutQuint);
}
return false;
}
/// <summary>
/// This is a simple container which delegates various input events that have to be captured before the notes.
/// </summary>
private class InputTarget : Container, IKeyBindingHandler<ManiaAction>
{
public Func<ManiaAction, bool> Pressed;
public Func<ManiaAction, bool> Released;
public InputTarget()
{
RelativeSizeAxes = Axes.Both;
AlwaysPresent = true;
Alpha = 0;
}
public bool OnPressed(ManiaAction action) => Pressed?.Invoke(action) ?? false;
public bool OnReleased(ManiaAction action) => Released?.Invoke(action) ?? false;
} }
public bool OnPressed(ManiaAction action) public bool OnPressed(ManiaAction action)

View File

@ -0,0 +1,108 @@
// 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.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Colour;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Input.Bindings;
using osu.Game.Graphics;
using osu.Game.Rulesets.UI.Scrolling;
using OpenTK.Graphics;
namespace osu.Game.Rulesets.Mania.UI.Components
{
public class ColumnBackground : CompositeDrawable, IKeyBindingHandler<ManiaAction>, IHasAccentColour
{
private readonly IBindable<ManiaAction> action = new Bindable<ManiaAction>();
private Box background;
private Box backgroundOverlay;
private readonly IBindable<ScrollingDirection> direction = new Bindable<ScrollingDirection>();
[BackgroundDependencyLoader]
private void load(IBindable<ManiaAction> action, IScrollingInfo scrollingInfo)
{
this.action.BindTo(action);
InternalChildren = new[]
{
background = new Box
{
Name = "Background",
RelativeSizeAxes = Axes.Both,
Alpha = 0.3f
},
backgroundOverlay = new Box
{
Name = "Background Gradient Overlay",
RelativeSizeAxes = Axes.Both,
Height = 0.5f,
Blending = BlendingMode.Additive,
Alpha = 0
}
};
direction.BindTo(scrollingInfo.Direction);
direction.BindValueChanged(direction =>
{
backgroundOverlay.Anchor = backgroundOverlay.Origin = direction == ScrollingDirection.Up ? Anchor.TopLeft : Anchor.BottomLeft;
updateColours();
}, true);
}
protected override void LoadComplete()
{
base.LoadComplete();
updateColours();
}
private Color4 accentColour;
public Color4 AccentColour
{
get => accentColour;
set
{
if (accentColour == value)
return;
accentColour = value;
updateColours();
}
}
private void updateColours()
{
if (!IsLoaded)
return;
background.Colour = AccentColour;
var brightPoint = AccentColour.Opacity(0.6f);
var dimPoint = AccentColour.Opacity(0);
backgroundOverlay.Colour = ColourInfo.GradientVertical(
direction.Value == ScrollingDirection.Up ? brightPoint : dimPoint,
direction.Value == ScrollingDirection.Up ? dimPoint : brightPoint);
}
public bool OnPressed(ManiaAction action)
{
if (action == this.action.Value)
backgroundOverlay.FadeTo(1, 50, Easing.OutQuint).Then().FadeTo(0.5f, 250, Easing.OutQuint);
return false;
}
public bool OnReleased(ManiaAction action)
{
if (action == this.action.Value)
backgroundOverlay.FadeTo(0, 250, Easing.OutQuint);
return false;
}
}
}

View File

@ -0,0 +1,99 @@
// 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.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Game.Graphics;
using osu.Game.Rulesets.UI.Scrolling;
using OpenTK.Graphics;
namespace osu.Game.Rulesets.Mania.UI.Components
{
public class ColumnHitObjectArea : Container, IHasAccentColour
{
private const float hit_target_height = 10;
private const float hit_target_bar_height = 2;
private Container<Drawable> content;
protected override Container<Drawable> Content => content;
private readonly IBindable<ScrollingDirection> direction = new Bindable<ScrollingDirection>();
private Container hitTargetLine;
[BackgroundDependencyLoader]
private void load(IScrollingInfo scrollingInfo)
{
Drawable hitTargetBar;
InternalChildren = new[]
{
hitTargetBar = new Box
{
RelativeSizeAxes = Axes.X,
Height = hit_target_height,
Colour = Color4.Black
},
hitTargetLine = new Container
{
RelativeSizeAxes = Axes.X,
Height = hit_target_bar_height,
Masking = true,
Child = new Box { RelativeSizeAxes = Axes.Both }
},
content = new Container
{
Name = "Hit objects",
RelativeSizeAxes = Axes.Both,
},
};
direction.BindTo(scrollingInfo.Direction);
direction.BindValueChanged(direction =>
{
Anchor anchor = direction == ScrollingDirection.Up ? Anchor.TopLeft : Anchor.BottomLeft;
hitTargetBar.Anchor = hitTargetBar.Origin = anchor;
hitTargetLine.Anchor = hitTargetLine.Origin = anchor;
}, true);
}
protected override void LoadComplete()
{
base.LoadComplete();
updateColours();
}
private Color4 accentColour;
public Color4 AccentColour
{
get => accentColour;
set
{
if (accentColour == value)
return;
accentColour = value;
updateColours();
}
}
private void updateColours()
{
if (!IsLoaded)
return;
hitTargetLine.EdgeEffect = new EdgeEffectParameters
{
Type = EdgeEffectType.Glow,
Radius = 5,
Colour = accentColour.Opacity(0.5f),
};
}
}
}

View File

@ -0,0 +1,123 @@
// 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.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Colour;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Input.Bindings;
using osu.Game.Graphics;
using osu.Game.Rulesets.UI.Scrolling;
using OpenTK;
using OpenTK.Graphics;
namespace osu.Game.Rulesets.Mania.UI.Components
{
public class ColumnKeyArea : CompositeDrawable, IKeyBindingHandler<ManiaAction>, IHasAccentColour
{
private const float key_icon_size = 10;
private const float key_icon_corner_radius = 3;
private readonly IBindable<ManiaAction> action = new Bindable<ManiaAction>();
private readonly IBindable<ScrollingDirection> direction = new Bindable<ScrollingDirection>();
private Container keyIcon;
[BackgroundDependencyLoader]
private void load(IBindable<ManiaAction> action, IScrollingInfo scrollingInfo)
{
this.action.BindTo(action);
Drawable gradient;
InternalChildren = new[]
{
gradient = new Box
{
Name = "Key gradient",
RelativeSizeAxes = Axes.Both,
Alpha = 0.5f
},
keyIcon = new Container
{
Name = "Key icon",
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = new Vector2(key_icon_size),
Masking = true,
CornerRadius = key_icon_corner_radius,
BorderThickness = 2,
BorderColour = Color4.White, // Not true
Children = new[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Alpha = 0,
AlwaysPresent = true
}
}
}
};
direction.BindTo(scrollingInfo.Direction);
direction.BindValueChanged(direction =>
{
gradient.Colour = ColourInfo.GradientVertical(
direction == ScrollingDirection.Up ? Color4.Black : Color4.Black.Opacity(0),
direction == ScrollingDirection.Up ? Color4.Black.Opacity(0) : Color4.Black);
}, true);
}
protected override void LoadComplete()
{
base.LoadComplete();
updateColours();
}
private Color4 accentColour;
public Color4 AccentColour
{
get => accentColour;
set
{
if (accentColour == value)
return;
accentColour = value;
updateColours();
}
}
private void updateColours()
{
if (!IsLoaded)
return;
keyIcon.EdgeEffect = new EdgeEffectParameters
{
Type = EdgeEffectType.Glow,
Radius = 5,
Colour = accentColour.Opacity(0.5f),
};
}
public bool OnPressed(ManiaAction action)
{
if (action == this.action.Value)
keyIcon.ScaleTo(1.4f, 50, Easing.OutQuint).Then().ScaleTo(1.3f, 250, Easing.OutQuint);
return false;
}
public bool OnReleased(ManiaAction action)
{
if (action == this.action.Value)
keyIcon.ScaleTo(1f, 125, Easing.OutQuint);
return false;
}
}
}

View File

@ -23,7 +23,6 @@ namespace osu.Game.Rulesets.Mania.UI
{ {
bool isTick = judgedObject is DrawableHoldNoteTick; bool isTick = judgedObject is DrawableHoldNoteTick;
Anchor = Anchor.TopCentre;
Origin = Anchor.Centre; Origin = Anchor.Centre;
RelativeSizeAxes = Axes.X; RelativeSizeAxes = Axes.X;

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.Framework.Configuration;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.UI.Scrolling;
namespace osu.Game.Rulesets.Mania.UI
{
public interface IScrollingInfo
{
/// <summary>
/// The direction <see cref="HitObject"/>s should scroll in.
/// </summary>
IBindable<ScrollingDirection> Direction { get; }
}
}

View File

@ -8,7 +8,6 @@ using System;
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.Game.Rulesets.Judgements; using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Drawables;
@ -17,18 +16,13 @@ using osu.Game.Rulesets.UI.Scrolling;
namespace osu.Game.Rulesets.Mania.UI namespace osu.Game.Rulesets.Mania.UI
{ {
public class ManiaPlayfield : ScrollingPlayfield public class ManiaPlayfield : ManiaScrollingPlayfield
{ {
/// <summary>
/// Whether this playfield should be inverted. This flips everything inside the playfield.
/// </summary>
public readonly Bindable<bool> Inverted = new Bindable<bool>(true);
public List<Column> Columns => stages.SelectMany(x => x.Columns).ToList(); 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(ScrollingDirection direction, List<StageDefinition> stageDefinitions)
: base(ScrollingDirection.Up) : base(direction)
{ {
if (stageDefinitions == null) if (stageDefinitions == null)
throw new ArgumentNullException(nameof(stageDefinitions)); throw new ArgumentNullException(nameof(stageDefinitions));
@ -36,8 +30,6 @@ namespace osu.Game.Rulesets.Mania.UI
if (stageDefinitions.Count <= 0) if (stageDefinitions.Count <= 0)
throw new ArgumentException("Can't have zero or fewer stages."); throw new ArgumentException("Can't have zero or fewer stages.");
Inverted.Value = true;
GridContainer playfieldGrid; GridContainer playfieldGrid;
InternalChild = playfieldGrid = new GridContainer InternalChild = playfieldGrid = new GridContainer
{ {
@ -50,9 +42,8 @@ namespace osu.Game.Rulesets.Mania.UI
int firstColumnIndex = 0; int firstColumnIndex = 0;
for (int i = 0; i < stageDefinitions.Count; i++) for (int i = 0; i < stageDefinitions.Count; i++)
{ {
var newStage = new ManiaStage(firstColumnIndex, stageDefinitions[i], ref normalColumnAction, ref specialColumnAction); var newStage = new ManiaStage(direction, firstColumnIndex, stageDefinitions[i], ref normalColumnAction, ref specialColumnAction);
newStage.VisibleTimeRange.BindTo(VisibleTimeRange); newStage.VisibleTimeRange.BindTo(VisibleTimeRange);
newStage.Inverted.BindTo(Inverted);
playfieldGrid.Content[0][i] = newStage; playfieldGrid.Content[0][i] = newStage;

View File

@ -4,6 +4,7 @@
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;
@ -12,6 +13,7 @@ using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Input.Handlers; using osu.Game.Input.Handlers;
using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Mania.Configuration;
using osu.Game.Rulesets.Mania.Mods; using osu.Game.Rulesets.Mania.Mods;
using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.Objects.Drawables; using osu.Game.Rulesets.Mania.Objects.Drawables;
@ -33,6 +35,9 @@ namespace osu.Game.Rulesets.Mania.UI
public IEnumerable<BarLine> BarLines; public IEnumerable<BarLine> BarLines;
private readonly Bindable<ManiaScrollingDirection> configDirection = new Bindable<ManiaScrollingDirection>();
private ScrollingInfo scrollingInfo;
public ManiaRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap) public ManiaRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap)
: base(ruleset, beatmap) : base(ruleset, beatmap)
{ {
@ -65,12 +70,24 @@ namespace osu.Game.Rulesets.Mania.UI
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load() private void load(ManiaConfigManager config)
{ {
BarLines.ForEach(Playfield.Add); BarLines.ForEach(Playfield.Add);
config.BindWith(ManiaSetting.ScrollDirection, configDirection);
configDirection.BindValueChanged(d => scrollingInfo.Direction.Value = (ScrollingDirection)d, true);
} }
protected sealed override Playfield CreatePlayfield() => new ManiaPlayfield(Beatmap.Stages) private DependencyContainer dependencies;
protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent)
{
dependencies = new DependencyContainer(base.CreateLocalDependencies(parent));
dependencies.CacheAs<IScrollingInfo>(scrollingInfo = new ScrollingInfo());
return dependencies;
}
protected sealed override Playfield CreatePlayfield() => new ManiaPlayfield(scrollingInfo.Direction, Beatmap.Stages)
{ {
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
Origin = Anchor.Centre, Origin = Anchor.Centre,
@ -84,21 +101,25 @@ namespace osu.Game.Rulesets.Mania.UI
protected override DrawableHitObject<ManiaHitObject> GetVisualRepresentation(ManiaHitObject h) protected override DrawableHitObject<ManiaHitObject> GetVisualRepresentation(ManiaHitObject h)
{ {
ManiaAction action = Playfield.Columns.ElementAt(h.Column).Action; switch (h)
{
var holdNote = h as HoldNote; case HoldNote holdNote:
if (holdNote != null) return new DrawableHoldNote(holdNote);
return new DrawableHoldNote(holdNote, action); case Note note:
return new DrawableNote(note);
var note = h as Note; default:
if (note != null) return null;
return new DrawableNote(note, action); }
return null;
} }
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,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.UI.Scrolling;
namespace osu.Game.Rulesets.Mania.UI
{
public enum ManiaScrollingDirection
{
Up = ScrollingDirection.Up,
Down = ScrollingDirection.Down
}
}

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.Framework.Allocation;
using osu.Framework.Configuration;
using osu.Game.Rulesets.UI.Scrolling;
namespace osu.Game.Rulesets.Mania.UI
{
public class ManiaScrollingPlayfield : ScrollingPlayfield
{
private readonly IBindable<ScrollingDirection> direction = new Bindable<ScrollingDirection>();
public ManiaScrollingPlayfield(ScrollingDirection direction)
: base(direction)
{
}
[BackgroundDependencyLoader]
private void load(IScrollingInfo scrollingInfo)
{
direction.BindTo(scrollingInfo.Direction);
direction.BindValueChanged(direction => Direction.Value = direction, true);
}
}
}

View File

@ -5,7 +5,6 @@ using System;
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.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
@ -24,20 +23,15 @@ 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 : ScrollingPlayfield internal class ManiaStage : ManiaScrollingPlayfield
{ {
public const float HIT_TARGET_POSITION = 50; public const float HIT_TARGET_POSITION = 50;
/// <summary>
/// Whether this playfield should be inverted. This flips everything inside the playfield.
/// </summary>
public readonly Bindable<bool> Inverted = new Bindable<bool>(true);
public IReadOnlyList<Column> Columns => columnFlow.Children; public IReadOnlyList<Column> Columns => columnFlow.Children;
private readonly FillFlowContainer<Column> columnFlow; private readonly FillFlowContainer<Column> columnFlow;
protected override Container<Drawable> Content => content; protected override Container<Drawable> Content => barLineContainer;
private readonly Container<Drawable> content; private readonly Container<Drawable> barLineContainer;
public Container<DrawableManiaJudgement> Judgements => judgements; public Container<DrawableManiaJudgement> Judgements => judgements;
private readonly JudgementContainer<DrawableManiaJudgement> judgements; private readonly JudgementContainer<DrawableManiaJudgement> judgements;
@ -49,8 +43,8 @@ namespace osu.Game.Rulesets.Mania.UI
private readonly int firstColumnIndex; private readonly int firstColumnIndex;
public ManiaStage(int firstColumnIndex, StageDefinition definition, ref ManiaAction normalColumnStartAction, ref ManiaAction specialColumnStartAction) public ManiaStage(ScrollingDirection direction, int firstColumnIndex, StageDefinition definition, ref ManiaAction normalColumnStartAction, ref ManiaAction specialColumnStartAction)
: base(ScrollingDirection.Up) : base(direction)
{ {
this.firstColumnIndex = firstColumnIndex; this.firstColumnIndex = firstColumnIndex;
@ -106,13 +100,12 @@ namespace osu.Game.Rulesets.Mania.UI
Width = 1366, // Bar lines should only be masked on the vertical axis Width = 1366, // Bar lines should only be masked on the vertical axis
BypassAutoSizeAxes = Axes.Both, BypassAutoSizeAxes = Axes.Both,
Masking = true, Masking = true,
Child = content = new Container Child = barLineContainer = new Container
{ {
Name = "Bar lines", Name = "Bar lines",
Anchor = Anchor.TopCentre, Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre, Origin = Anchor.TopCentre,
RelativeSizeAxes = Axes.Y, RelativeSizeAxes = Axes.Y,
Padding = new MarginPadding { Top = HIT_TARGET_POSITION }
} }
}, },
judgements = new JudgementContainer<DrawableManiaJudgement> judgements = new JudgementContainer<DrawableManiaJudgement>
@ -131,23 +124,23 @@ namespace osu.Game.Rulesets.Mania.UI
for (int i = 0; i < definition.Columns; i++) for (int i = 0; i < definition.Columns; i++)
{ {
var isSpecial = definition.IsSpecialColumn(i); var isSpecial = definition.IsSpecialColumn(i);
var column = new Column var column = new Column(direction)
{ {
IsSpecial = isSpecial, IsSpecial = isSpecial,
Action = isSpecial ? specialColumnStartAction++ : normalColumnStartAction++ Action = { Value = isSpecial ? specialColumnStartAction++ : normalColumnStartAction++ }
}; };
AddColumn(column); AddColumn(column);
} }
Inverted.ValueChanged += invertedChanged; Direction.BindValueChanged(d =>
Inverted.TriggerChange(); {
} barLineContainer.Padding = new MarginPadding
{
private void invertedChanged(bool newValue) Top = d == ScrollingDirection.Up ? HIT_TARGET_POSITION : 0,
{ Bottom = d == ScrollingDirection.Down ? HIT_TARGET_POSITION : 0,
Scale = new Vector2(1, newValue ? -1 : 1); };
Judgements.Scale = Scale; }, true);
} }
public void AddColumn(Column c) public void AddColumn(Column c)
@ -218,7 +211,7 @@ namespace osu.Game.Rulesets.Mania.UI
{ {
// Due to masking differences, it is not possible to get the width of the columns container automatically // Due to masking differences, it is not possible to get the width of the columns container automatically
// While masking on effectively only the Y-axis, so we need to set the width of the bar line container manually // While masking on effectively only the Y-axis, so we need to set the width of the bar line container manually
content.Width = columnFlow.Width; barLineContainer.Width = columnFlow.Width;
} }
} }
} }

View File

@ -8,17 +8,19 @@ using osu.Framework.MathUtils;
using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.UI;
using osu.Game.Tests.Beatmaps; using osu.Game.Tests.Beatmaps;
using OpenTK;
namespace osu.Game.Rulesets.Osu.Tests namespace osu.Game.Rulesets.Osu.Tests
{ {
internal class OsuBeatmapConversionTest : BeatmapConversionTest<ConvertValue> [TestFixture]
public class OsuBeatmapConversionTest : BeatmapConversionTest<ConvertValue>
{ {
protected override string ResourceAssembly => "osu.Game.Rulesets.Osu"; protected override string ResourceAssembly => "osu.Game.Rulesets.Osu";
[TestCase("basic")] [TestCase("basic")]
[TestCase("colinear-perfect-curve")] [TestCase("colinear-perfect-curve")]
[TestCase("slider-ticks")]
public new void Test(string name) public new void Test(string name)
{ {
base.Test(name); base.Test(name);
@ -26,24 +28,30 @@ namespace osu.Game.Rulesets.Osu.Tests
protected override IEnumerable<ConvertValue> CreateConvertValue(HitObject hitObject) protected override IEnumerable<ConvertValue> CreateConvertValue(HitObject hitObject)
{ {
var startPosition = (hitObject as IHasPosition)?.Position ?? new Vector2(256, 192); switch (hitObject)
var endPosition = (hitObject as Slider)?.EndPosition ?? startPosition;
yield return new ConvertValue
{ {
StartTime = hitObject.StartTime, case Slider slider:
EndTime = (hitObject as IHasEndTime)?.EndTime ?? hitObject.StartTime, foreach (var nested in slider.NestedHitObjects)
StartX = startPosition.X, yield return createConvertValue(nested);
StartY = startPosition.Y, break;
EndX = endPosition.X, default:
EndY = endPosition.Y yield return createConvertValue(hitObject);
break;
}
ConvertValue createConvertValue(HitObject obj) => new ConvertValue
{
StartTime = obj.StartTime,
EndTime = (obj as IHasEndTime)?.EndTime ?? obj.StartTime,
X = (obj as IHasPosition)?.X ?? OsuPlayfield.BASE_SIZE.X / 2,
Y = (obj as IHasPosition)?.Y ?? OsuPlayfield.BASE_SIZE.Y / 2,
}; };
} }
protected override Ruleset CreateRuleset() => new OsuRuleset(); protected override Ruleset CreateRuleset() => new OsuRuleset();
} }
internal struct ConvertValue : IEquatable<ConvertValue> public struct ConvertValue : IEquatable<ConvertValue>
{ {
/// <summary> /// <summary>
/// A sane value to account for osu!stable using ints everwhere. /// A sane value to account for osu!stable using ints everwhere.
@ -52,17 +60,13 @@ namespace osu.Game.Rulesets.Osu.Tests
public double StartTime; public double StartTime;
public double EndTime; public double EndTime;
public float StartX; public float X;
public float StartY; public float Y;
public float EndX;
public float EndY;
public bool Equals(ConvertValue other) public bool Equals(ConvertValue other)
=> Precision.AlmostEquals(StartTime, other.StartTime) => Precision.AlmostEquals(StartTime, other.StartTime, conversion_lenience)
&& Precision.AlmostEquals(EndTime, other.EndTime, conversion_lenience) && Precision.AlmostEquals(EndTime, other.EndTime, conversion_lenience)
&& Precision.AlmostEquals(StartX, other.StartX) && Precision.AlmostEquals(X, other.X, conversion_lenience)
&& Precision.AlmostEquals(StartY, other.StartY, conversion_lenience) && Precision.AlmostEquals(Y, other.Y, conversion_lenience);
&& Precision.AlmostEquals(EndX, other.EndX, conversion_lenience)
&& Precision.AlmostEquals(EndY, other.EndY, conversion_lenience);
} }
} }

View File

@ -27,6 +27,7 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
var endTimeData = original as IHasEndTime; var endTimeData = original as IHasEndTime;
var positionData = original as IHasPosition; var positionData = original as IHasPosition;
var comboData = original as IHasCombo; var comboData = original as IHasCombo;
var legacyOffset = original as IHasLegacyLastTickOffset;
if (curveData != null) if (curveData != null)
{ {
@ -40,7 +41,8 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
RepeatSamples = curveData.RepeatSamples, RepeatSamples = curveData.RepeatSamples,
RepeatCount = curveData.RepeatCount, RepeatCount = curveData.RepeatCount,
Position = positionData?.Position ?? Vector2.Zero, Position = positionData?.Position ?? Vector2.Zero,
NewCombo = comboData?.NewCombo ?? false NewCombo = comboData?.NewCombo ?? false,
LegacyLastTickOffset = legacyOffset?.LegacyLastTickOffset
}; };
} }
else if (endTimeData != null) else if (endTimeData != null)

View File

@ -15,10 +15,10 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
{ {
} }
public override void PostProcess() public override void PreProcess()
{ {
base.PreProcess();
applyStacking((Beatmap<OsuHitObject>)Beatmap); applyStacking((Beatmap<OsuHitObject>)Beatmap);
base.PostProcess();
} }
private void applyStacking(Beatmap<OsuHitObject> beatmap) private void applyStacking(Beatmap<OsuHitObject> beatmap)
@ -44,7 +44,7 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
continue; continue;
double endTime = (stackBaseObject as IHasEndTime)?.EndTime ?? stackBaseObject.StartTime; double endTime = (stackBaseObject as IHasEndTime)?.EndTime ?? stackBaseObject.StartTime;
double stackThreshold = objectN.TimePreempt * beatmap.BeatmapInfo?.StackLeniency ?? 0.7f; double stackThreshold = objectN.TimePreempt * beatmap.BeatmapInfo.StackLeniency;
if (objectN.StartTime - endTime > stackThreshold) if (objectN.StartTime - endTime > stackThreshold)
//We are no longer within stacking range of the next object. //We are no longer within stacking range of the next object.
@ -87,7 +87,7 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
OsuHitObject objectI = beatmap.HitObjects[i]; OsuHitObject objectI = beatmap.HitObjects[i];
if (objectI.StackHeight != 0 || objectI is Spinner) continue; if (objectI.StackHeight != 0 || objectI is Spinner) continue;
double stackThreshold = objectI.TimePreempt * beatmap.BeatmapInfo?.StackLeniency ?? 0.7f; double stackThreshold = objectI.TimePreempt * beatmap.BeatmapInfo.StackLeniency;
/* If this object is a hitcircle, then we enter this "special" case. /* If this object is a hitcircle, then we enter this "special" case.
* It either ends with a stack of hitcircles only, or a stack of hitcircles that are underneath a slider. * It either ends with a stack of hitcircles only, or a stack of hitcircles that are underneath a slider.

View File

@ -10,6 +10,9 @@ namespace osu.Game.Rulesets.Osu.Difficulty
{ {
public double AimStrain; public double AimStrain;
public double SpeedStrain; public double SpeedStrain;
public double ApproachRate;
public double OverallDifficulty;
public int MaxCombo;
public OsuDifficultyAttributes(Mod[] mods, double starRating) public OsuDifficultyAttributes(Mod[] mods, double starRating)
: base(mods, starRating) : base(mods, starRating)

View File

@ -25,6 +25,9 @@ namespace osu.Game.Rulesets.Osu.Difficulty
protected override DifficultyAttributes Calculate(IBeatmap beatmap, Mod[] mods, double timeRate) protected override DifficultyAttributes Calculate(IBeatmap beatmap, Mod[] mods, double timeRate)
{ {
if (!beatmap.HitObjects.Any())
return new OsuDifficultyAttributes(mods, 0);
OsuDifficultyBeatmap difficultyBeatmap = new OsuDifficultyBeatmap(beatmap.HitObjects.Cast<OsuHitObject>().ToList(), timeRate); OsuDifficultyBeatmap difficultyBeatmap = new OsuDifficultyBeatmap(beatmap.HitObjects.Cast<OsuHitObject>().ToList(), timeRate);
Skill[] skills = Skill[] skills =
{ {
@ -58,10 +61,21 @@ namespace osu.Game.Rulesets.Osu.Difficulty
double speedRating = Math.Sqrt(skills[1].DifficultyValue()) * difficulty_multiplier; double speedRating = Math.Sqrt(skills[1].DifficultyValue()) * difficulty_multiplier;
double starRating = aimRating + speedRating + Math.Abs(aimRating - speedRating) / 2; double starRating = aimRating + speedRating + Math.Abs(aimRating - speedRating) / 2;
// Todo: These int casts are temporary to achieve 1:1 results with osu!stable, and should be remoevd in the future
double hitWindowGreat = (int)(beatmap.HitObjects.First().HitWindows.Great / 2) / timeRate;
double preEmpt = (int)BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.ApproachRate, 1800, 1200, 450) / timeRate;
int maxCombo = beatmap.HitObjects.Count();
// Add the ticks + tail of the slider. 1 is subtracted because the "headcircle" would be counted twice (once for the slider itself in the line above)
maxCombo += beatmap.HitObjects.OfType<Slider>().Sum(s => s.NestedHitObjects.Count - 1);
return new OsuDifficultyAttributes(mods, starRating) return new OsuDifficultyAttributes(mods, starRating)
{ {
AimStrain = aimRating, AimStrain = aimRating,
SpeedStrain = speedRating SpeedStrain = speedRating,
ApproachRate = preEmpt > 1200 ? (1800 - preEmpt) / 120 : (1200 - preEmpt) / 150 + 5,
OverallDifficulty = (80 - hitWindowGreat) / 6,
MaxCombo = maxCombo
}; };
} }

View File

@ -22,16 +22,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty
private Mod[] mods; private Mod[] mods;
/// <summary>
/// Approach rate adjusted by mods.
/// </summary>
private double realApproachRate;
/// <summary>
/// Overall difficulty adjusted by mods.
/// </summary>
private double realOverallDifficulty;
private double accuracy; private double accuracy;
private int scoreMaxCombo; private int scoreMaxCombo;
private int countGreat; private int countGreat;
@ -63,13 +53,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty
if (mods.Any(m => !m.Ranked)) if (mods.Any(m => !m.Ranked))
return 0; return 0;
// Todo: These int casts are temporary to achieve 1:1 results with osu!stable, and should be remoevd in the future
double hitWindowGreat = (int)(Beatmap.HitObjects.First().HitWindows.Great / 2) / TimeRate;
double preEmpt = (int)BeatmapDifficulty.DifficultyRange(Beatmap.BeatmapInfo.BaseDifficulty.ApproachRate, 1800, 1200, 450) / TimeRate;
realApproachRate = preEmpt > 1200 ? (1800 - preEmpt) / 120 : (1200 - preEmpt) / 150 + 5;
realOverallDifficulty = (80 - hitWindowGreat) / 6;
// Custom multipliers for NoFail and SpunOut. // Custom multipliers for NoFail and SpunOut.
double multiplier = 1.12f; // This is being adjusted to keep the final pp value scaled around what it used to be when changing things double multiplier = 1.12f; // This is being adjusted to keep the final pp value scaled around what it used to be when changing things
@ -94,8 +77,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty
categoryRatings.Add("Aim", aimValue); categoryRatings.Add("Aim", aimValue);
categoryRatings.Add("Speed", speedValue); categoryRatings.Add("Speed", speedValue);
categoryRatings.Add("Accuracy", accuracyValue); categoryRatings.Add("Accuracy", accuracyValue);
categoryRatings.Add("OD", realOverallDifficulty); categoryRatings.Add("OD", Attributes.OverallDifficulty);
categoryRatings.Add("AR", realApproachRate); categoryRatings.Add("AR", Attributes.ApproachRate);
categoryRatings.Add("Max Combo", beatmapMaxCombo); categoryRatings.Add("Max Combo", beatmapMaxCombo);
} }
@ -120,22 +103,22 @@ namespace osu.Game.Rulesets.Osu.Difficulty
aimValue *= Math.Min(Math.Pow(scoreMaxCombo, 0.8f) / Math.Pow(beatmapMaxCombo, 0.8f), 1.0f); aimValue *= Math.Min(Math.Pow(scoreMaxCombo, 0.8f) / Math.Pow(beatmapMaxCombo, 0.8f), 1.0f);
double approachRateFactor = 1.0f; double approachRateFactor = 1.0f;
if (realApproachRate > 10.33f) if (Attributes.ApproachRate > 10.33f)
approachRateFactor += 0.45f * (realApproachRate - 10.33f); approachRateFactor += 0.45f * (Attributes.ApproachRate - 10.33f);
else if (realApproachRate < 8.0f) else if (Attributes.ApproachRate < 8.0f)
{ {
// HD is worth more with lower ar! // HD is worth more with lower ar!
if (mods.Any(h => h is OsuModHidden)) if (mods.Any(h => h is OsuModHidden))
approachRateFactor += 0.02f * (8.0f - realApproachRate); approachRateFactor += 0.02f * (8.0f - Attributes.ApproachRate);
else else
approachRateFactor += 0.01f * (8.0f - realApproachRate); approachRateFactor += 0.01f * (8.0f - Attributes.ApproachRate);
} }
aimValue *= approachRateFactor; aimValue *= approachRateFactor;
// We want to give more reward for lower AR when it comes to aim and HD. This nerfs high AR and buffs lower AR. // We want to give more reward for lower AR when it comes to aim and HD. This nerfs high AR and buffs lower AR.
if (mods.Any(h => h is OsuModHidden)) if (mods.Any(h => h is OsuModHidden))
aimValue *= 1.02 + (11.0f - realApproachRate) / 50.0; // Gives a 1.04 bonus for AR10, a 1.06 bonus for AR9, a 1.02 bonus for AR11. aimValue *= 1.02 + (11.0f - Attributes.ApproachRate) / 50.0; // Gives a 1.04 bonus for AR10, a 1.06 bonus for AR9, a 1.02 bonus for AR11.
if (mods.Any(h => h is OsuModFlashlight)) if (mods.Any(h => h is OsuModFlashlight))
{ {
@ -146,7 +129,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
// Scale the aim value with accuracy _slightly_ // Scale the aim value with accuracy _slightly_
aimValue *= 0.5f + accuracy / 2.0f; aimValue *= 0.5f + accuracy / 2.0f;
// It is important to also consider accuracy difficulty when doing that // It is important to also consider accuracy difficulty when doing that
aimValue *= 0.98f + Math.Pow(realOverallDifficulty, 2) / 2500; aimValue *= 0.98f + Math.Pow(Attributes.OverallDifficulty, 2) / 2500;
return aimValue; return aimValue;
} }
@ -172,7 +155,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
// Scale the speed value with accuracy _slightly_ // Scale the speed value with accuracy _slightly_
speedValue *= 0.5f + accuracy / 2.0f; speedValue *= 0.5f + accuracy / 2.0f;
// It is important to also consider accuracy difficulty when doing that // It is important to also consider accuracy difficulty when doing that
speedValue *= 0.98f + Math.Pow(realOverallDifficulty, 2) / 2500; speedValue *= 0.98f + Math.Pow(Attributes.OverallDifficulty, 2) / 2500;
return speedValue; return speedValue;
} }
@ -194,7 +177,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
// Lots of arbitrary values from testing. // Lots of arbitrary values from testing.
// Considering to use derivation from perfect accuracy in a probabilistic manner - assume normal distribution // Considering to use derivation from perfect accuracy in a probabilistic manner - assume normal distribution
double accuracyValue = Math.Pow(1.52163f, realOverallDifficulty) * Math.Pow(betterAccuracyPercentage, 24) * 2.83f; double accuracyValue = Math.Pow(1.52163f, Attributes.OverallDifficulty) * Math.Pow(betterAccuracyPercentage, 24) * 2.83f;
// Bonus for many hitcircles - it's harder to keep good accuracy up for longer // Bonus for many hitcircles - it's harder to keep good accuracy up for longer
accuracyValue *= Math.Min(1.15f, Math.Pow(amountHitObjectsWithAccuracy / 1000.0f, 0.3f)); accuracyValue *= Math.Min(1.15f, Math.Pow(amountHitObjectsWithAccuracy / 1000.0f, 0.3f));

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 OpenTK;
using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects.Drawables;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
@ -12,11 +11,6 @@ namespace osu.Game.Rulesets.Osu.Judgements
{ {
public override HitResult MaxResult => HitResult.Great; public override HitResult MaxResult => HitResult.Great;
/// <summary>
/// The positional hit offset.
/// </summary>
public Vector2 PositionOffset;
protected override int NumericResultFor(HitResult result) protected override int NumericResultFor(HitResult result)
{ {
switch (result) switch (result)

View File

@ -8,6 +8,7 @@ namespace osu.Game.Rulesets.Osu.Judgements
public class OsuSliderTailJudgement : OsuJudgement public class OsuSliderTailJudgement : OsuJudgement
{ {
public override bool AffectsCombo => false; public override bool AffectsCombo => false;
protected override int NumericResultFor(HitResult result) => 0; protected override int NumericResultFor(HitResult result) => 0;
} }
} }

View File

@ -93,7 +93,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
AddJudgement(new OsuJudgement AddJudgement(new OsuJudgement
{ {
Result = result, Result = result,
PositionOffset = Vector2.Zero //todo: set to correct value
}); });
} }

View File

@ -93,6 +93,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
base.AccentColour = value; base.AccentColour = value;
Body.AccentColour = AccentColour; Body.AccentColour = AccentColour;
Ball.AccentColour = AccentColour; Ball.AccentColour = AccentColour;
foreach (var drawableHitObject in NestedHitObjects)
drawableHitObject.AccentColour = AccentColour;
} }
} }
@ -133,7 +136,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
{ {
if (!userTriggered && Time.Current >= slider.EndTime) if (!userTriggered && Time.Current >= slider.EndTime)
{ {
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;

View File

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

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 OpenTK; using OpenTK;
using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Objects.Types;
using System.Collections.Generic; using System.Collections.Generic;
@ -25,6 +26,28 @@ namespace osu.Game.Rulesets.Osu.Objects
public Vector2 StackedPositionAt(double t) => StackedPosition + this.CurvePositionAt(t); public Vector2 StackedPositionAt(double t) => StackedPosition + this.CurvePositionAt(t);
public override Vector2 EndPosition => Position + this.CurvePositionAt(1); public override Vector2 EndPosition => Position + this.CurvePositionAt(1);
public override int ComboIndex
{
get => base.ComboIndex;
set
{
base.ComboIndex = value;
foreach (var n in NestedHitObjects.OfType<IHasComboInformation>())
n.ComboIndex = value;
}
}
public override int IndexInCurrentCombo
{
get => base.IndexInCurrentCombo;
set
{
base.IndexInCurrentCombo = value;
foreach (var n in NestedHitObjects.OfType<IHasComboInformation>())
n.IndexInCurrentCombo = value;
}
}
public SliderCurve Curve { get; } = new SliderCurve(); public SliderCurve Curve { get; } = new SliderCurve();
public List<Vector2> ControlPoints public List<Vector2> ControlPoints
@ -45,6 +68,8 @@ namespace osu.Game.Rulesets.Osu.Objects
set { Curve.Distance = value; } set { Curve.Distance = value; }
} }
public double? LegacyLastTickOffset { get; set; }
/// <summary> /// <summary>
/// The position of the cursor at the point of completion of this <see cref="Slider"/> if it was hit /// The position of the cursor at the point of completion of this <see cref="Slider"/> if it was hit
/// with as few movements as possible. This is set and used by difficulty calculation. /// with as few movements as possible. This is set and used by difficulty calculation.
@ -91,6 +116,9 @@ namespace osu.Game.Rulesets.Osu.Objects
createSliderEnds(); createSliderEnds();
createTicks(); createTicks();
createRepeatPoints(); createRepeatPoints();
if (LegacyLastTickOffset != null)
TailCircle.StartTime = Math.Max(StartTime + Duration / 2, TailCircle.StartTime - LegacyLastTickOffset.Value);
} }
private void createSliderEnds() private void createSliderEnds()
@ -141,7 +169,8 @@ namespace osu.Game.Rulesets.Osu.Objects
var distanceProgress = d / length; var distanceProgress = d / length;
var timeProgress = reversed ? 1 - distanceProgress : distanceProgress; var timeProgress = reversed ? 1 - distanceProgress : distanceProgress;
var firstSample = Samples.FirstOrDefault(s => s.Name == SampleInfo.HIT_NORMAL) ?? Samples.FirstOrDefault(); // TODO: remove this when guaranteed sort is present for samples (https://github.com/ppy/osu/issues/1933) var firstSample = Samples.FirstOrDefault(s => s.Name == SampleInfo.HIT_NORMAL)
?? Samples.FirstOrDefault(); // TODO: remove this when guaranteed sort is present for samples (https://github.com/ppy/osu/issues/1933)
var sampleList = new List<SampleInfo>(); var sampleList = new List<SampleInfo>();
if (firstSample != null) if (firstSample != null)

View File

@ -6,6 +6,7 @@ using osu.Game.Beatmaps;
using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using osu.Game.Rulesets.Osu.UI;
using osu.Game.Rulesets.Replays; using osu.Game.Rulesets.Replays;
using osu.Game.Users; using osu.Game.Users;
@ -18,7 +19,7 @@ namespace osu.Game.Rulesets.Osu.Replays
/// <summary> /// <summary>
/// Constants (for spinners). /// Constants (for spinners).
/// </summary> /// </summary>
protected static readonly Vector2 SPINNER_CENTRE = new Vector2(256, 192); protected static readonly Vector2 SPINNER_CENTRE = OsuPlayfield.BASE_SIZE / 2;
protected const float SPIN_RADIUS = 50; protected const float SPIN_RADIUS = 50;
/// <summary> /// <summary>

View File

@ -30,13 +30,16 @@ namespace osu.Game.Rulesets.Osu.Replays
} }
} }
public override List<InputState> GetPendingStates() public override List<IInput> GetPendingInputs()
{ {
return new List<InputState> return new List<IInput>
{ {
new MousePositionAbsoluteInput
{
Position = GamefieldToScreenSpace(Position ?? Vector2.Zero)
},
new ReplayState<OsuAction> new ReplayState<OsuAction>
{ {
Mouse = new ReplayMouseState(GamefieldToScreenSpace(Position ?? Vector2.Zero)),
PressedActions = CurrentFrame.Actions PressedActions = CurrentFrame.Actions
} }
}; };

View File

@ -1,124 +1,256 @@
{ {
"Mappings": [{ "Mappings": [{
"StartTime": 500, "StartTime": 500.0,
"Objects": [{ "Objects": [{
"StartTime": 500, "StartTime": 500.0,
"EndTime": 2500, "EndTime": 500.0,
"StartX": 96, "X": 96.0,
"StartY": 192, "Y": 192.0
"EndX": 96, }, {
"EndY": 192 "StartTime": 1000.0,
}] "EndTime": 1000.0,
}, "X": 256.0,
{ "Y": 192.0
"StartTime": 3000, }, {
"Objects": [{ "StartTime": 1500.0,
"StartTime": 3000, "EndTime": 1500.0,
"EndTime": 4000, "X": 416.0,
"StartX": 256, "Y": 192.0
"StartY": 192, }, {
"EndX": 256, "StartTime": 2000.0,
"EndY": 192 "EndTime": 2000.0,
}] "X": 256.0,
}, "Y": 192.0
{ }, {
"StartTime": 4500, "StartTime": 2464.0,
"Objects": [{ "EndTime": 2464.0,
"StartTime": 4500, "X": 96.0,
"EndTime": 5500, "Y": 192.0
"StartX": 256, }]
"StartY": 192, }, {
"EndX": 256, "StartTime": 3000.0,
"EndY": 192 "Objects": [{
}] "StartTime": 3000.0,
}, "EndTime": 4000.0,
{ "X": 256.0,
"StartTime": 6000, "Y": 192.0
"Objects": [{ }]
"StartTime": 6000, }, {
"EndTime": 6500, "StartTime": 4500.0,
"StartX": 256, "Objects": [{
"StartY": 192, "StartTime": 4500.0,
"EndX": 256, "EndTime": 5500.0,
"EndY": 192 "X": 256.0,
}] "Y": 192.0
}, }]
{ }, {
"StartTime": 7000, "StartTime": 6000.0,
"Objects": [{ "Objects": [{
"StartTime": 7000, "StartTime": 6000.0,
"EndTime": 8000, "EndTime": 6500.0,
"StartX": 256, "X": 256.0,
"StartY": 128, "Y": 192.0
"EndX": 256, }]
"EndY": 128 }, {
}] "StartTime": 7000.0,
}, "Objects": [{
{ "StartTime": 7000.0,
"StartTime": 8500, "EndTime": 7000.0,
"Objects": [{ "X": 256.0,
"StartTime": 8500, "Y": 128.0
"EndTime": 10999, }, {
"StartX": 32, "StartTime": 7250.0,
"StartY": 192, "EndTime": 7250.0,
"EndX": 508.166229, "X": 336.0,
"EndY": 153.299271 "Y": 128.0
}] }, {
}, "StartTime": 7500.0,
{ "EndTime": 7500.0,
"StartTime": 11500, "X": 256.0,
"Objects": [{ "Y": 128.0
"StartTime": 11500, }, {
"EndTime": 12000, "StartTime": 7750.0,
"StartX": 256, "EndTime": 7750.0,
"StartY": 192, "X": 336.0,
"EndX": 256, "Y": 128.0
"EndY": 192 }, {
}] "StartTime": 7964.0,
}, "EndTime": 7964.0,
{ "X": 256.0,
"StartTime": 12500, "Y": 128.0
"Objects": [{ }]
"StartTime": 12500, }, {
"EndTime": 16500, "StartTime": 8500.0,
"StartX": 512, "Objects": [{
"StartY": 320, "StartTime": 8500.0,
"EndX": 291.1977, "EndTime": 8500.0,
"EndY": 40.799427 "X": 32.0,
}] "Y": 192.0
}, }, {
{ "StartTime": 9000.0,
"StartTime": 17000, "EndTime": 9000.0,
"Objects": [{ "X": 101.81015,
"StartTime": 17000, "Y": 326.4915
"EndTime": 18000, }, {
"StartX": 256, "StartTime": 9500.0,
"StartY": 256, "EndTime": 9500.0,
"EndX": 256, "X": 237.2304,
"EndY": 256 "Y": 276.282928
}] }, {
}, "StartTime": 10000.0,
{ "EndTime": 10000.0,
"StartTime": 18500, "X": 270.339874,
"Objects": [{ "Y": 121.1423
"StartTime": 18500, }, {
"EndTime": 19450, "StartTime": 10500.0,
"StartX": 256, "EndTime": 10500.0,
"StartY": 192, "X": 401.0588,
"EndX": 256, "Y": 49.1515045
"EndY": 192 }, {
}] "StartTime": 10964.0,
}, "EndTime": 10964.0,
{ "X": 508.166229,
"StartTime": 19875, "Y": 153.299271
"Objects": [{ }]
"StartTime": 19875, }, {
"EndTime": 23874, "StartTime": 11500.0,
"StartX": 216, "Objects": [{
"StartY": 231, "StartTime": 11500.0,
"EndX": 408.720825, "EndTime": 12000.0,
"EndY": 339.810455 "X": 256.0,
}] "Y": 192.0
} }]
] }, {
"StartTime": 12500.0,
"Objects": [{
"StartTime": 12500.0,
"EndTime": 12500.0,
"X": 512.0,
"Y": 320.0
}, {
"StartTime": 13000.0,
"EndTime": 13000.0,
"X": 353.235535,
"Y": 300.154449
}, {
"StartTime": 13500.0,
"EndTime": 13500.0,
"X": 194.471069,
"Y": 280.3089
}, {
"StartTime": 14000.0,
"EndTime": 14000.0,
"X": 35.7066345,
"Y": 260.463318
}, {
"StartTime": 14500.0,
"EndTime": 14500.0,
"X": 118.370323,
"Y": 219.009277
}, {
"StartTime": 15000.0,
"EndTime": 15000.0,
"X": 271.087128,
"Y": 171.285278
}, {
"StartTime": 15500.0,
"EndTime": 15500.0,
"X": 423.803925,
"Y": 123.561279
}, {
"StartTime": 16000.0,
"EndTime": 16000.0,
"X": 446.420532,
"Y": 79.60513
}, {
"StartTime": 16464.0,
"EndTime": 16464.0,
"X": 291.1977,
"Y": 40.799427
}]
}, {
"StartTime": 17000.0,
"Objects": [{
"StartTime": 17000.0,
"EndTime": 17000.0,
"X": 256.0,
"Y": 256.0
}, {
"StartTime": 17250.0,
"EndTime": 17250.0,
"X": 176.0,
"Y": 256.0
}, {
"StartTime": 17500.0,
"EndTime": 17500.0,
"X": 256.0,
"Y": 256.0
}, {
"StartTime": 17750.0,
"EndTime": 17750.0,
"X": 176.0,
"Y": 256.0
}, {
"StartTime": 17964.0,
"EndTime": 17964.0,
"X": 256.0,
"Y": 256.0
}]
}, {
"StartTime": 18500.0,
"Objects": [{
"StartTime": 18500.0,
"EndTime": 19450.0,
"X": 256.0,
"Y": 192.0
}]
}, {
"StartTime": 19875.0,
"Objects": [{
"StartTime": 19875.0,
"EndTime": 19875.0,
"X": 216.0,
"Y": 231.0
}, {
"StartTime": 20375.0,
"EndTime": 20375.0,
"X": 317.446747,
"Y": 171.345245
}, {
"StartTime": 20875.0,
"EndTime": 20875.0,
"X": 270.3294,
"Y": 310.4395
}, {
"StartTime": 21375.0,
"EndTime": 21375.0,
"X": 119.121056,
"Y": 322.8657
}, {
"StartTime": 21875.0,
"EndTime": 21875.0,
"X": 124.28746,
"Y": 165.224731
}, {
"StartTime": 22375.0,
"EndTime": 22375.0,
"X": 240.4715,
"Y": 62.65587
}, {
"StartTime": 22875.0,
"EndTime": 22875.0,
"X": 398.054047,
"Y": 39.064167
}, {
"StartTime": 23375.0,
"EndTime": 23375.0,
"X": 439.749878,
"Y": 183.668091
}, {
"StartTime": 23839.0,
"EndTime": 23839.0,
"X": 408.720825,
"Y": 339.810455
}]
}]
} }

View File

@ -1,13 +1,16 @@
{ {
"Mappings": [{ "Mappings": [{
"StartTime": 118858, "StartTime": 118858.0,
"Objects": [{ "Objects": [{
"StartTime": 118858, "StartTime": 118858.0,
"EndTime": 119088, "EndTime": 118858.0,
"StartX": 219, "X": 219.0,
"StartY": 215, "Y": 215.0
"EndX": 239.6507, }, {
"EndY": 29.1437378 "StartTime": 119052.0,
"EndTime": 119052.0,
"X": 239.6507,
"Y": 29.1437378
}]
}] }]
}]
} }

View File

@ -0,0 +1,331 @@
{
"Mappings": [{
"StartTime": 500.0,
"Objects": [{
"StartTime": 500.0,
"EndTime": 500.0,
"X": 96.0,
"Y": 192.0
}, {
"StartTime": 624.0,
"EndTime": 624.0,
"X": 105.921242,
"Y": 192.0
}, {
"StartTime": 749.0,
"EndTime": 749.0,
"X": 115.922493,
"Y": 192.0
}, {
"StartTime": 874.0,
"EndTime": 874.0,
"X": 125.923737,
"Y": 192.0
}, {
"StartTime": 999.0,
"EndTime": 999.0,
"X": 135.924988,
"Y": 192.0
}, {
"StartTime": 1124.0,
"EndTime": 1124.0,
"X": 145.926239,
"Y": 192.0
}, {
"StartTime": 1249.0,
"EndTime": 1249.0,
"X": 155.92749,
"Y": 192.0
}, {
"StartTime": 1374.0,
"EndTime": 1374.0,
"X": 165.928741,
"Y": 192.0
}, {
"StartTime": 1499.0,
"EndTime": 1499.0,
"X": 175.93,
"Y": 192.0
}, {
"StartTime": 1624.0,
"EndTime": 1624.0,
"X": 185.931244,
"Y": 192.0
}, {
"StartTime": 1749.0,
"EndTime": 1749.0,
"X": 195.9325,
"Y": 192.0
}, {
"StartTime": 1874.0,
"EndTime": 1874.0,
"X": 205.933746,
"Y": 192.0
}, {
"StartTime": 1999.0,
"EndTime": 1999.0,
"X": 215.935,
"Y": 192.0
}, {
"StartTime": 2124.0,
"EndTime": 2124.0,
"X": 225.936234,
"Y": 192.0
}, {
"StartTime": 2249.0,
"EndTime": 2249.0,
"X": 235.9375,
"Y": 192.0
}, {
"StartTime": 2374.0,
"EndTime": 2374.0,
"X": 245.938751,
"Y": 192.0
}, {
"StartTime": 2499.0,
"EndTime": 2499.0,
"X": 255.94,
"Y": 192.0
}, {
"StartTime": 2624.0,
"EndTime": 2624.0,
"X": 265.941223,
"Y": 192.0
}, {
"StartTime": 2749.0,
"EndTime": 2749.0,
"X": 275.9425,
"Y": 192.0
}, {
"StartTime": 2874.0,
"EndTime": 2874.0,
"X": 285.943756,
"Y": 192.0
}, {
"StartTime": 2999.0,
"EndTime": 2999.0,
"X": 295.945,
"Y": 192.0
}, {
"StartTime": 3124.0,
"EndTime": 3124.0,
"X": 305.946259,
"Y": 192.0
}, {
"StartTime": 3249.0,
"EndTime": 3249.0,
"X": 315.9475,
"Y": 192.0
}, {
"StartTime": 3374.0,
"EndTime": 3374.0,
"X": 325.94873,
"Y": 192.0
}, {
"StartTime": 3499.0,
"EndTime": 3499.0,
"X": 335.949982,
"Y": 192.0
}, {
"StartTime": 3624.0,
"EndTime": 3624.0,
"X": 345.951233,
"Y": 192.0
}, {
"StartTime": 3749.0,
"EndTime": 3749.0,
"X": 355.952484,
"Y": 192.0
}, {
"StartTime": 3874.0,
"EndTime": 3874.0,
"X": 365.953766,
"Y": 192.0
}, {
"StartTime": 3999.0,
"EndTime": 3999.0,
"X": 375.955,
"Y": 192.0
}, {
"StartTime": 4124.0,
"EndTime": 4124.0,
"X": 385.956238,
"Y": 192.0
}, {
"StartTime": 4249.0,
"EndTime": 4249.0,
"X": 395.9575,
"Y": 192.0
}, {
"StartTime": 4374.0,
"EndTime": 4374.0,
"X": 405.95874,
"Y": 192.0
}, {
"StartTime": 4499.0,
"EndTime": 4499.0,
"X": 415.960022,
"Y": 192.0
}, {
"StartTime": 4624.0,
"EndTime": 4624.0,
"X": 406.038757,
"Y": 192.0
}, {
"StartTime": 4749.0,
"EndTime": 4749.0,
"X": 396.0375,
"Y": 192.0
}, {
"StartTime": 4874.0,
"EndTime": 4874.0,
"X": 386.036255,
"Y": 192.0
}, {
"StartTime": 4999.0,
"EndTime": 4999.0,
"X": 376.035034,
"Y": 192.0
}, {
"StartTime": 5124.0,
"EndTime": 5124.0,
"X": 366.033752,
"Y": 192.0
}, {
"StartTime": 5249.0,
"EndTime": 5249.0,
"X": 356.0325,
"Y": 192.0
}, {
"StartTime": 5374.0,
"EndTime": 5374.0,
"X": 346.03125,
"Y": 192.0
}, {
"StartTime": 5499.0,
"EndTime": 5499.0,
"X": 336.030029,
"Y": 192.0
}, {
"StartTime": 5624.0,
"EndTime": 5624.0,
"X": 326.028748,
"Y": 192.0
}, {
"StartTime": 5749.0,
"EndTime": 5749.0,
"X": 316.0275,
"Y": 192.0
}, {
"StartTime": 5874.0,
"EndTime": 5874.0,
"X": 306.026245,
"Y": 192.0
}, {
"StartTime": 5999.0,
"EndTime": 5999.0,
"X": 296.025,
"Y": 192.0
}, {
"StartTime": 6124.0,
"EndTime": 6124.0,
"X": 286.023773,
"Y": 192.0
}, {
"StartTime": 6249.0,
"EndTime": 6249.0,
"X": 276.022522,
"Y": 192.0
}, {
"StartTime": 6374.0,
"EndTime": 6374.0,
"X": 266.02124,
"Y": 192.0
}, {
"StartTime": 6499.0,
"EndTime": 6499.0,
"X": 256.02,
"Y": 192.0
}, {
"StartTime": 6624.0,
"EndTime": 6624.0,
"X": 246.018768,
"Y": 192.0
}, {
"StartTime": 6749.0,
"EndTime": 6749.0,
"X": 236.017517,
"Y": 192.0
}, {
"StartTime": 6874.0,
"EndTime": 6874.0,
"X": 226.016251,
"Y": 192.0
}, {
"StartTime": 6999.0,
"EndTime": 6999.0,
"X": 216.014984,
"Y": 192.0
}, {
"StartTime": 7124.0,
"EndTime": 7124.0,
"X": 206.013733,
"Y": 192.0
}, {
"StartTime": 7249.0,
"EndTime": 7249.0,
"X": 196.012512,
"Y": 192.0
}, {
"StartTime": 7374.0,
"EndTime": 7374.0,
"X": 186.011261,
"Y": 192.0
}, {
"StartTime": 7499.0,
"EndTime": 7499.0,
"X": 176.01,
"Y": 192.0
}, {
"StartTime": 7624.0,
"EndTime": 7624.0,
"X": 166.008728,
"Y": 192.0
}, {
"StartTime": 7749.0,
"EndTime": 7749.0,
"X": 156.0075,
"Y": 192.0
}, {
"StartTime": 7874.0,
"EndTime": 7874.0,
"X": 146.006256,
"Y": 192.0
}, {
"StartTime": 7999.0,
"EndTime": 7999.0,
"X": 136.005,
"Y": 192.0
}, {
"StartTime": 8124.0,
"EndTime": 8124.0,
"X": 126.003738,
"Y": 192.0
}, {
"StartTime": 8249.0,
"EndTime": 8249.0,
"X": 116.002518,
"Y": 192.0
}, {
"StartTime": 8374.0,
"EndTime": 8374.0,
"X": 106.001259,
"Y": 192.0
}, {
"StartTime": 8463.0,
"EndTime": 8463.0,
"X": 96.0,
"Y": 192.0
}]
}]
}

View File

@ -0,0 +1,20 @@
osu file format v14
[General]
StackLeniency: 0.7
[Difficulty]
HPDrainRate:6
CircleSize:4
OverallDifficulty:7
ApproachRate:8.3
SliderMultiplier:0.400000005960464
SliderTickRate:4
[TimingPoints]
500,500,4,2,1,50,1,0
13426,-100,4,3,1,45,0,0
14884,-100,4,2,1,50,0,0
[HitObjects]
96,192,500,6,0,L|416:192,2,320.000004768372

View File

@ -11,7 +11,6 @@ using osu.Game.Rulesets.Osu.Objects.Drawables.Connections;
using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI;
using System.Linq; using System.Linq;
using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Osu.Judgements;
namespace osu.Game.Rulesets.Osu.UI namespace osu.Game.Rulesets.Osu.UI
{ {
@ -75,7 +74,7 @@ namespace osu.Game.Rulesets.Osu.UI
DrawableOsuJudgement explosion = new DrawableOsuJudgement(judgement, judgedObject) DrawableOsuJudgement explosion = new DrawableOsuJudgement(judgement, judgedObject)
{ {
Origin = Anchor.Centre, Origin = Anchor.Centre,
Position = ((OsuHitObject)judgedObject.HitObject).StackedEndPosition + ((OsuJudgement)judgement).PositionOffset Position = ((OsuHitObject)judgedObject.HitObject).StackedEndPosition
}; };
judgementLayer.Add(explosion); judgementLayer.Add(explosion);

View File

@ -12,7 +12,8 @@ using osu.Game.Tests.Beatmaps;
namespace osu.Game.Rulesets.Taiko.Tests namespace osu.Game.Rulesets.Taiko.Tests
{ {
internal class TaikoBeatmapConversionTest : BeatmapConversionTest<ConvertValue> [TestFixture]
public class TaikoBeatmapConversionTest : BeatmapConversionTest<ConvertValue>
{ {
protected override string ResourceAssembly => "osu.Game.Rulesets.Taiko"; protected override string ResourceAssembly => "osu.Game.Rulesets.Taiko";
@ -41,7 +42,7 @@ namespace osu.Game.Rulesets.Taiko.Tests
protected override Ruleset CreateRuleset() => new TaikoRuleset(); protected override Ruleset CreateRuleset() => new TaikoRuleset();
} }
internal struct ConvertValue : IEquatable<ConvertValue> public struct ConvertValue : IEquatable<ConvertValue>
{ {
/// <summary> /// <summary>
/// A sane value to account for osu!stable using ints everwhere. /// A sane value to account for osu!stable using ints everwhere.

View File

@ -0,0 +1,19 @@
// 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.Difficulty;
using osu.Game.Rulesets.Mods;
namespace osu.Game.Rulesets.Taiko.Difficulty
{
public class TaikoDifficultyAttributes : DifficultyAttributes
{
public double GreatHitWindow;
public int MaxCombo;
public TaikoDifficultyAttributes(Mod[] mods, double starRating)
: base(mods, starRating)
{
}
}
}

View File

@ -3,6 +3,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
@ -34,6 +35,9 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
protected override DifficultyAttributes Calculate(IBeatmap beatmap, Mod[] mods, double timeRate) protected override DifficultyAttributes Calculate(IBeatmap beatmap, Mod[] mods, double timeRate)
{ {
if (!beatmap.HitObjects.Any())
return new TaikoDifficultyAttributes(mods, 0);
var difficultyHitObjects = new List<TaikoHitObjectDifficulty>(); var difficultyHitObjects = new List<TaikoHitObjectDifficulty>();
foreach (var hitObject in beatmap.HitObjects) foreach (var hitObject in beatmap.HitObjects)
@ -47,7 +51,12 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
double starRating = calculateDifficulty(difficultyHitObjects, timeRate) * star_scaling_factor; double starRating = calculateDifficulty(difficultyHitObjects, timeRate) * star_scaling_factor;
return new DifficultyAttributes(mods, starRating); return new TaikoDifficultyAttributes(mods, starRating)
{
// Todo: This int cast is temporary to achieve 1:1 results with osu!stable, and should be remoevd in the future
GreatHitWindow = (int)(beatmap.HitObjects.First().HitWindows.Great / 2) / timeRate,
MaxCombo = beatmap.HitObjects.Count(h => h is Hit)
};
} }
private bool calculateStrainValues(List<TaikoHitObjectDifficulty> objects, double timeRate) private bool calculateStrainValues(List<TaikoHitObjectDifficulty> objects, double timeRate)

View File

@ -8,13 +8,12 @@ using osu.Game.Beatmaps;
using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.Taiko.Objects;
namespace osu.Game.Rulesets.Taiko.Difficulty namespace osu.Game.Rulesets.Taiko.Difficulty
{ {
public class TaikoPerformanceCalculator : PerformanceCalculator public class TaikoPerformanceCalculator : PerformanceCalculator
{ {
private readonly int beatmapMaxCombo; protected new TaikoDifficultyAttributes Attributes => (TaikoDifficultyAttributes)base.Attributes;
private Mod[] mods; private Mod[] mods;
private int countGreat; private int countGreat;
@ -25,7 +24,6 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
public TaikoPerformanceCalculator(Ruleset ruleset, WorkingBeatmap beatmap, Score score) public TaikoPerformanceCalculator(Ruleset ruleset, WorkingBeatmap beatmap, Score score)
: base(ruleset, beatmap, score) : base(ruleset, beatmap, score)
{ {
beatmapMaxCombo = Beatmap.HitObjects.Count(h => h is Hit);
} }
public override double Calculate(Dictionary<string, double> categoryDifficulty = null) public override double Calculate(Dictionary<string, double> categoryDifficulty = null)
@ -78,8 +76,8 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
strainValue *= Math.Pow(0.985, countMiss); strainValue *= Math.Pow(0.985, countMiss);
// Combo scaling // Combo scaling
if (beatmapMaxCombo > 0) if (Attributes.MaxCombo > 0)
strainValue *= Math.Min(Math.Pow(Score.MaxCombo, 0.5) / Math.Pow(beatmapMaxCombo, 0.5), 1.0); strainValue *= Math.Min(Math.Pow(Score.MaxCombo, 0.5) / Math.Pow(Attributes.MaxCombo, 0.5), 1.0);
if (mods.Any(m => m is ModHidden)) if (mods.Any(m => m is ModHidden))
strainValue *= 1.025; strainValue *= 1.025;
@ -94,14 +92,12 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
private double computeAccuracyValue() private double computeAccuracyValue()
{ {
// Todo: This int cast is temporary to achieve 1:1 results with osu!stable, and should be remoevd in the future if (Attributes.GreatHitWindow <= 0)
double hitWindowGreat = (int)(Beatmap.HitObjects.First().HitWindows.Great / 2) / TimeRate;
if (hitWindowGreat <= 0)
return 0; return 0;
// Lots of arbitrary values from testing. // Lots of arbitrary values from testing.
// Considering to use derivation from perfect accuracy in a probabilistic manner - assume normal distribution // Considering to use derivation from perfect accuracy in a probabilistic manner - assume normal distribution
double accValue = Math.Pow(150.0 / hitWindowGreat, 1.1) * Math.Pow(Score.Accuracy, 15) * 22.0; double accValue = Math.Pow(150.0 / Attributes.GreatHitWindow, 1.1) * Math.Pow(Score.Accuracy, 15) * 22.0;
// Bonus for many hitcircles - it's harder to keep good accuracy up for longer // Bonus for many hitcircles - it's harder to keep good accuracy up for longer
return accValue * Math.Min(1.15, Math.Pow(totalHits / 1500.0, 0.3)); return accValue * Math.Min(1.15, Math.Pow(totalHits / 1500.0, 0.3));

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.Scoring;
namespace osu.Game.Rulesets.Taiko.Judgements
{
public class TaikoIntermediateSwellJudgement : TaikoJudgement
{
public override HitResult MaxResult => HitResult.Perfect;
public override bool AffectsCombo => false;
public TaikoIntermediateSwellJudgement()
{
Final = false;
}
/// <summary>
/// Computes the numeric result value for the combo portion of the score.
/// </summary>
/// <param name="result">The result to compute the value for.</param>
/// <returns>The numeric result value.</returns>
protected override int NumericResultFor(HitResult result) => 0;
}
}

View File

@ -86,6 +86,9 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
switch (State.Value) switch (State.Value)
{ {
case ArmedState.Idle: case ArmedState.Idle:
SecondHitAllowed = false;
validKeyPressed = false;
UnproxyContent(); UnproxyContent();
this.Delay(HitObject.HitWindows.HalfWindowFor(HitResult.Miss)).Expire(); this.Delay(HitObject.HitWindows.HalfWindowFor(HitResult.Miss)).Expire();
break; break;
@ -95,8 +98,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
break; break;
case ArmedState.Hit: case ArmedState.Hit:
// If we're far enough away from the left stage, we should bring outselves in front of it // If we're far enough away from the left stage, we should bring outselves in front of it
if (X >= -0.05f) ProxyContent();
ProxyContent();
var flash = circlePiece?.FlashBox; var flash = circlePiece?.FlashBox;
if (flash != null) if (flash != null)

View File

@ -3,6 +3,7 @@
using System; using System;
using System.Linq; using System.Linq;
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.Judgements;
@ -46,6 +47,19 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
AddJudgement(new TaikoStrongHitJudgement { Result = HitResult.Great }); 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) public override bool OnReleased(TaikoAction action)
{ {
if (action == firstHitAction) if (action == firstHitAction)

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.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
@ -20,6 +19,11 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
{ {
public class DrawableSwell : DrawableTaikoHitObject<Swell> public class DrawableSwell : DrawableTaikoHitObject<Swell>
{ {
/// <summary>
/// A judgement is only displayed when the user has complete the swell (either a hit or miss).
/// </summary>
public override bool DisplayJudgement => AllJudged;
private const float target_ring_thick_border = 1.4f; private const float target_ring_thick_border = 1.4f;
private const float target_ring_thin_border = 1f; private const float target_ring_thin_border = 1f;
private const float target_ring_scale = 5f; private const float target_ring_scale = 5f;
@ -29,11 +33,6 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
private readonly CircularContainer targetRing; private readonly CircularContainer targetRing;
private readonly CircularContainer expandingRing; private readonly CircularContainer expandingRing;
/// <summary>
/// The amount of times the user has hit this swell.
/// </summary>
private int userHits;
private readonly SwellSymbolPiece symbol; private readonly SwellSymbolPiece symbol;
public DrawableSwell(Swell swell) public DrawableSwell(Swell swell)
@ -129,9 +128,9 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
{ {
if (userTriggered) if (userTriggered)
{ {
userHits++; AddJudgement(new TaikoIntermediateSwellJudgement());
var completion = (float)userHits / HitObject.RequiredHits; var completion = (float)Judgements.Count / HitObject.RequiredHits;
expandingRing expandingRing
.FadeTo(expandingRing.Alpha + MathHelper.Clamp(completion / 16, 0.1f, 0.6f), 50) .FadeTo(expandingRing.Alpha + MathHelper.Clamp(completion / 16, 0.1f, 0.6f), 50)
@ -142,7 +141,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
expandingRing.ScaleTo(1f + Math.Min(target_ring_scale - 1f, (target_ring_scale - 1f) * completion * 1.3f), 260, Easing.OutQuint); expandingRing.ScaleTo(1f + Math.Min(target_ring_scale - 1f, (target_ring_scale - 1f) * completion * 1.3f), 260, Easing.OutQuint);
if (userHits == HitObject.RequiredHits) if (Judgements.Count == HitObject.RequiredHits)
AddJudgement(new TaikoJudgement { Result = HitResult.Great }); AddJudgement(new TaikoJudgement { Result = HitResult.Great });
} }
else else
@ -151,7 +150,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
return; return;
//TODO: THIS IS SHIT AND CAN'T EXIST POST-TAIKO WORLD CUP //TODO: THIS IS SHIT AND CAN'T EXIST POST-TAIKO WORLD CUP
AddJudgement(userHits > HitObject.RequiredHits / 2 AddJudgement(Judgements.Count > HitObject.RequiredHits / 2
? new TaikoJudgement { Result = HitResult.Good } ? new TaikoJudgement { Result = HitResult.Good }
: new TaikoJudgement { Result = HitResult.Miss }); : new TaikoJudgement { Result = HitResult.Miss });
} }
@ -162,23 +161,22 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
const float preempt = 100; const float preempt = 100;
const float out_transition_time = 300; const float out_transition_time = 300;
double untilStartTime = HitObject.StartTime - Time.Current;
double untilJudgement = untilStartTime + (Judgements.FirstOrDefault()?.TimeOffset ?? 0) + HitObject.Duration;
targetRing.Delay(untilStartTime - preempt).ScaleTo(target_ring_scale, preempt * 4, Easing.OutQuint);
this.Delay(untilJudgement).FadeOut(out_transition_time, Easing.Out);
switch (state) switch (state)
{ {
case ArmedState.Idle: case ArmedState.Idle:
UnproxyContent(); UnproxyContent();
expandingRing.FadeTo(0);
using (BeginAbsoluteSequence(HitObject.StartTime - preempt, true))
targetRing.ScaleTo(target_ring_scale, preempt * 4, Easing.OutQuint);
break; break;
case ArmedState.Miss:
case ArmedState.Hit: case ArmedState.Hit:
bodyContainer.Delay(untilJudgement).ScaleTo(1.4f, out_transition_time); this.FadeOut(out_transition_time, Easing.Out);
bodyContainer.ScaleTo(1.4f, out_transition_time);
Expire();
break; break;
} }
Expire();
} }
protected override void Update() protected override void Update()

View File

@ -17,6 +17,6 @@ namespace osu.Game.Rulesets.Taiko.Replays
protected override bool IsImportant(TaikoReplayFrame frame) => frame.Actions.Any(); protected override bool IsImportant(TaikoReplayFrame frame) => frame.Actions.Any();
public override List<InputState> GetPendingStates() => new List<InputState> { new ReplayState<TaikoAction> { PressedActions = CurrentFrame.Actions } }; public override List<IInput> GetPendingInputs() => new List<IInput> { new ReplayState<TaikoAction> { PressedActions = CurrentFrame.Actions } };
} }
} }

View File

@ -27,14 +27,12 @@ namespace osu.Game.Rulesets.Taiko
public override IEnumerable<KeyBinding> GetDefaultKeyBindings(int variant = 0) => new[] public override IEnumerable<KeyBinding> GetDefaultKeyBindings(int variant = 0) => new[]
{ {
new KeyBinding(InputKey.MouseLeft, TaikoAction.LeftCentre),
new KeyBinding(InputKey.MouseRight, TaikoAction.LeftRim),
new KeyBinding(InputKey.D, TaikoAction.LeftRim), new KeyBinding(InputKey.D, TaikoAction.LeftRim),
new KeyBinding(InputKey.F, TaikoAction.LeftCentre), new KeyBinding(InputKey.F, TaikoAction.LeftCentre),
new KeyBinding(InputKey.J, TaikoAction.RightCentre), new KeyBinding(InputKey.J, TaikoAction.RightCentre),
new KeyBinding(InputKey.K, TaikoAction.RightRim), new KeyBinding(InputKey.K, TaikoAction.RightRim),
new KeyBinding(InputKey.MouseLeft, TaikoAction.LeftCentre),
new KeyBinding(InputKey.MouseLeft, TaikoAction.RightCentre),
new KeyBinding(InputKey.MouseRight, TaikoAction.LeftRim),
new KeyBinding(InputKey.MouseRight, TaikoAction.RightRim),
}; };
public override IEnumerable<Mod> ConvertLegacyMods(LegacyMods mods) public override IEnumerable<Mod> ConvertLegacyMods(LegacyMods mods)

View File

@ -37,7 +37,7 @@ namespace osu.Game.Rulesets.Taiko.UI
private void loadBarLines() private void loadBarLines()
{ {
TaikoHitObject lastObject = Beatmap.HitObjects[Beatmap.HitObjects.Count - 1]; TaikoHitObject lastObject = Beatmap.HitObjects[Beatmap.HitObjects.Count - 1];
double lastHitTime = 1 + (lastObject as IHasEndTime)?.EndTime ?? lastObject.StartTime; double lastHitTime = 1 + ((lastObject as IHasEndTime)?.EndTime ?? lastObject.StartTime);
var timingPoints = Beatmap.ControlPointInfo.TimingPoints.ToList(); var timingPoints = Beatmap.ControlPointInfo.TimingPoints.ToList();
@ -69,11 +69,7 @@ namespace osu.Game.Rulesets.Taiko.UI
bool isMajor = currentBeat % (int)currentPoint.TimeSignature == 0; bool isMajor = currentBeat % (int)currentPoint.TimeSignature == 0;
Playfield.Add(isMajor ? new DrawableBarLineMajor(barLine) : new DrawableBarLine(barLine)); Playfield.Add(isMajor ? new DrawableBarLineMajor(barLine) : new DrawableBarLine(barLine));
double bl = currentPoint.BeatLength; time += currentPoint.BeatLength * (int)currentPoint.TimeSignature;
if (bl < 800)
bl *= (int)currentPoint.TimeSignature;
time += bl;
currentBeat++; currentBeat++;
} }
} }

View File

@ -65,17 +65,19 @@ namespace osu.Game.Tests.Visual
foreach (var rulesetInfo in rulesets.AvailableRulesets) foreach (var rulesetInfo in rulesets.AvailableRulesets)
{ {
var ruleset = rulesetInfo.CreateInstance(); var instance = rulesetInfo.CreateInstance();
var testBeatmap = createTestBeatmap(rulesetInfo); var testBeatmap = createTestBeatmap(rulesetInfo);
beatmaps.Add(testBeatmap); beatmaps.Add(testBeatmap);
AddStep("set ruleset", () => Ruleset.Value = rulesetInfo);
selectBeatmap(testBeatmap); selectBeatmap(testBeatmap);
testBeatmapLabels(ruleset); testBeatmapLabels(instance);
// TODO: adjust cases once more info is shown for other gamemodes // TODO: adjust cases once more info is shown for other gamemodes
switch (ruleset) switch (instance)
{ {
case OsuRuleset _: case OsuRuleset _:
testInfoLabels(5); testInfoLabels(5);

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