1
0
mirror of https://github.com/ppy/osu.git synced 2025-01-14 04:02:59 +08:00

Merge remote-tracking branch 'origin/master' into timingchange-improvements

This commit is contained in:
smoogipooo 2017-08-07 17:35:53 +09:00
commit 62ee613da3
43 changed files with 413 additions and 472 deletions

@ -1 +1 @@
Subproject commit c687b8b102dac1f8b7ffad7efc12939484e256c0
Subproject commit 107c5517670ca88dbe8c83a97e37e99ac5742ee6

View File

@ -1,118 +0,0 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Linq;
using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces;
using OpenTK;
using OpenTK.Graphics;
namespace osu.Desktop.Tests.Visual
{
internal class TestCaseTaikoHitObjects : OsuTestCase
{
public override string Description => "Taiko hit objects";
private bool kiai;
public TestCaseTaikoHitObjects()
{
AddToggleStep("Kiai", b =>
{
kiai = !kiai;
updateKiaiState();
});
Add(new CirclePiece
{
Position = new Vector2(100, 100),
AccentColour = Color4.DarkRed,
KiaiMode = kiai,
Children = new[]
{
new CentreHitSymbolPiece()
}
});
Add(new CirclePiece(true)
{
Position = new Vector2(350, 100),
AccentColour = Color4.DarkRed,
KiaiMode = kiai,
Children = new[]
{
new CentreHitSymbolPiece()
}
});
Add(new CirclePiece
{
Position = new Vector2(100, 300),
AccentColour = Color4.DarkBlue,
KiaiMode = kiai,
Children = new[]
{
new RimHitSymbolPiece()
}
});
Add(new CirclePiece(true)
{
Position = new Vector2(350, 300),
AccentColour = Color4.DarkBlue,
KiaiMode = kiai,
Children = new[]
{
new RimHitSymbolPiece()
}
});
Add(new CirclePiece
{
Position = new Vector2(100, 500),
AccentColour = Color4.Orange,
KiaiMode = kiai,
Children = new[]
{
new SwellSymbolPiece()
}
});
Add(new ElongatedCirclePiece
{
Position = new Vector2(575, 100),
AccentColour = Color4.Orange,
KiaiMode = kiai,
Length = 0.10f,
PlayfieldLengthReference = () => DrawSize.X
});
Add(new ElongatedCirclePiece(true)
{
Position = new Vector2(575, 300),
AccentColour = Color4.Orange,
KiaiMode = kiai,
Length = 0.10f,
PlayfieldLengthReference = () => DrawSize.X
});
}
private void updateKiaiState()
{
foreach (var c in Children.OfType<CirclePiece>())
c.KiaiMode = kiai;
}
private abstract class BaseCircle : Container
{
protected readonly CirclePiece Piece;
protected BaseCircle(CirclePiece piece)
{
Piece = piece;
Add(Piece);
}
}
}
}

View File

@ -12,12 +12,14 @@ using osu.Game.Rulesets.Taiko.Objects;
using osu.Game.Rulesets.Taiko.Objects.Drawables;
using osu.Game.Rulesets.Taiko.UI;
using OpenTK;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Beatmaps;
namespace osu.Desktop.Tests.Visual
{
internal class TestCaseTaikoPlayfield : OsuTestCase
{
private const double default_duration = 300;
private const double default_duration = 1000;
private const float scroll_time = 1000;
public override string Description => "Taiko playfield";
@ -30,7 +32,8 @@ namespace osu.Desktop.Tests.Visual
public TestCaseTaikoPlayfield()
{
AddStep("Hit!", addHitJudgement);
AddStep("Hit!", () => addHitJudgement(false));
AddStep("Kiai hit", () => addHitJudgement(true));
AddStep("Miss :(", addMissJudgement);
AddStep("DrumRoll", () => addDrumRoll(false));
AddStep("Strong DrumRoll", () => addDrumRoll(true));
@ -55,7 +58,7 @@ namespace osu.Desktop.Tests.Visual
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.X,
Height = TaikoPlayfield.DEFAULT_PLAYFIELD_HEIGHT,
Height = TaikoPlayfield.DEFAULT_HEIGHT,
Clock = new FramedClock(rateAdjustClock),
Children = new[]
{
@ -84,7 +87,7 @@ namespace osu.Desktop.Tests.Visual
addDrumRoll(true);
break;
case 5:
addSwell(1000);
addSwell();
delay = scroll_time - 100;
break;
}
@ -96,16 +99,25 @@ namespace osu.Desktop.Tests.Visual
playfieldContainer.Delay(delay).ResizeTo(new Vector2(1, rng.Next(25, 400)), 500);
break;
case 6:
playfieldContainer.Delay(delay).ResizeTo(new Vector2(1, TaikoPlayfield.DEFAULT_PLAYFIELD_HEIGHT), 500);
playfieldContainer.Delay(delay).ResizeTo(new Vector2(1, TaikoPlayfield.DEFAULT_HEIGHT), 500);
break;
}
}
private void addHitJudgement()
private void addHitJudgement(bool kiai)
{
TaikoHitResult hitResult = RNG.Next(2) == 0 ? TaikoHitResult.Good : TaikoHitResult.Great;
var h = new DrawableTestHit(new Hit())
var cpi = new ControlPointInfo();
cpi.EffectPoints.Add(new EffectControlPoint
{
KiaiMode = kiai
});
Hit hit = new Hit();
hit.ApplyDefaults(cpi, new BeatmapDifficulty());
var h = new DrawableTestHit(hit)
{
X = RNG.NextSingle(hitResult == TaikoHitResult.Good ? -0.1f : -0.05f, hitResult == TaikoHitResult.Good ? 0.1f : 0.05f),
Judgement = new TaikoJudgement
@ -179,7 +191,8 @@ namespace osu.Desktop.Tests.Visual
Hit h = new Hit
{
StartTime = playfield.Time.Current + scroll_time,
ScrollTime = scroll_time
ScrollTime = scroll_time,
IsStrong = strong
};
if (strong)
@ -193,7 +206,8 @@ namespace osu.Desktop.Tests.Visual
Hit h = new Hit
{
StartTime = playfield.Time.Current + scroll_time,
ScrollTime = scroll_time
ScrollTime = scroll_time,
IsStrong = strong
};
if (strong)

View File

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
@ -104,7 +104,6 @@
<Compile Include="Visual\TestCaseSocial.cs" />
<Compile Include="Visual\TestCaseSongProgress.cs" />
<Compile Include="Visual\TestCaseTabControl.cs" />
<Compile Include="Visual\TestCaseTaikoHitObjects.cs" />
<Compile Include="Visual\TestCaseTaikoPlayfield.cs" />
<Compile Include="Visual\TestCaseTextAwesome.cs" />
<Compile Include="Visual\TestCaseTwoLayerButton.cs" />
@ -170,4 +169,4 @@
<Target Name="AfterBuild">
</Target>
-->
</Project>
</Project>

View File

@ -155,10 +155,6 @@
<Project>{007b2356-ab6f-4bd9-96d5-116fc2dce69a}</Project>
<Name>osu.Framework.Testing</Name>
</ProjectReference>
<ProjectReference Include="..\osu-framework\osu.Framework.VisualTestBrowser\osu.Framework.VisualTestBrowser.csproj">
<Project>{1f02f11c-2c66-4d25-bbb5-5c752aad3e62}</Project>
<Name>osu.Framework.VisualTestBrowser</Name>
</ProjectReference>
<ProjectReference Include="..\osu-framework\osu.Framework\osu.Framework.csproj">
<Project>{c76bf5b3-985e-4d39-95fe-97c9c879b83a}</Project>
<Name>osu.Framework</Name>

View File

@ -10,6 +10,7 @@ using osu.Game.Rulesets.Taiko.Judgements;
using OpenTK;
using OpenTK.Graphics;
using osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces;
using osu.Framework.Graphics;
namespace osu.Game.Rulesets.Taiko.Objects.Drawables
{
@ -30,6 +31,9 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
public DrawableDrumRoll(DrumRoll drumRoll)
: base(drumRoll)
{
RelativeSizeAxes = Axes.Y;
AutoSizeAxes = Axes.X;
foreach (var tick in drumRoll.Ticks)
{
var newTick = new DrawableDrumRollTick(tick)
@ -46,7 +50,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
protected override TaikoJudgement CreateJudgement() => new TaikoJudgement { SecondHit = HitObject.IsStrong };
protected override TaikoPiece CreateMainPiece() => new ElongatedCirclePiece(HitObject.IsStrong)
protected override TaikoPiece CreateMainPiece() => new ElongatedCirclePiece
{
Length = (float)(HitObject.Duration / HitObject.ScrollTime),
PlayfieldLengthReference = () => Parent.DrawSize.X

View File

@ -15,6 +15,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
public DrawableDrumRollTick(DrumRollTick tick)
: base(tick)
{
FillMode = FillMode.Fit;
}
protected override TaikoPiece CreateMainPiece() => new TickPiece

View File

@ -26,6 +26,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
protected DrawableHit(Hit hit)
: base(hit)
{
FillMode = FillMode.Fit;
}
protected override void CheckJudgement(bool userTriggered)
@ -92,12 +93,13 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
Content.ScaleTo(0.8f, gravity_time * 2, Easing.OutQuad);
this.FadeOut(800)
.MoveToY(-gravity_travel_height, gravity_time, Easing.Out)
this.MoveToY(-gravity_travel_height, gravity_time, Easing.Out)
.Then()
.MoveToY(gravity_travel_height * 2, gravity_time * 2, Easing.In);
Expire();
this.FadeOut(800)
.Expire();
break;
}
}

View File

@ -7,7 +7,6 @@ using osu.Framework.Input;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Taiko.Judgements;
using OpenTK.Input;
using osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces;
namespace osu.Game.Rulesets.Taiko.Objects.Drawables
{
@ -28,8 +27,6 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
{
}
protected override TaikoPiece CreateMainPiece() => new CirclePiece(true);
protected override TaikoJudgement CreateJudgement() => new TaikoStrongHitJudgement();
protected override void CheckJudgement(bool userTriggered)

View File

@ -35,8 +35,6 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
private readonly CircularContainer targetRing;
private readonly CircularContainer expandingRing;
private readonly CirclePiece circlePiece;
private readonly Key[] rimKeys = { Key.D, Key.K };
private readonly Key[] centreKeys = { Key.F, Key.J };
private Key[] lastKeySet;
@ -52,89 +50,81 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
public DrawableSwell(Swell swell)
: base(swell)
{
Children = new Drawable[]
FillMode = FillMode.Fit;
Add(bodyContainer = new Container
{
bodyContainer = new Container
RelativeSizeAxes = Axes.Both,
Depth = 1,
Children = new Drawable[]
{
AutoSizeAxes = Axes.Both,
Children = new Drawable[]
expandingRing = new CircularContainer
{
expandingRing = new CircularContainer
Name = "Expanding ring",
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Alpha = 0,
RelativeSizeAxes = Axes.Both,
BlendingMode = BlendingMode.Additive,
Masking = true,
Children = new[]
{
Name = "Expanding ring",
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Alpha = 0,
Size = new Vector2(TaikoHitObject.DEFAULT_CIRCLE_DIAMETER),
BlendingMode = BlendingMode.Additive,
Masking = true,
Children = new[]
new Box
{
new Box
{
RelativeSizeAxes = Axes.Both,
Alpha = inner_ring_alpha,
}
RelativeSizeAxes = Axes.Both,
Alpha = inner_ring_alpha,
}
},
targetRing = new CircularContainer
}
},
targetRing = new CircularContainer
{
Name = "Target ring (thick border)",
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both,
Masking = true,
BorderThickness = target_ring_thick_border,
BlendingMode = BlendingMode.Additive,
Children = new Drawable[]
{
Name = "Target ring (thick border)",
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = new Vector2(TaikoHitObject.DEFAULT_CIRCLE_DIAMETER),
Masking = true,
BorderThickness = target_ring_thick_border,
BlendingMode = BlendingMode.Additive,
Children = new Drawable[]
new Box
{
new Box
RelativeSizeAxes = Axes.Both,
Alpha = 0,
AlwaysPresent = true
},
new CircularContainer
{
Name = "Target ring (thin border)",
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both,
Masking = true,
BorderThickness = target_ring_thin_border,
BorderColour = Color4.White,
Children = new[]
{
RelativeSizeAxes = Axes.Both,
Alpha = 0,
AlwaysPresent = true
},
new CircularContainer
{
Name = "Target ring (thin border)",
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both,
Masking = true,
BorderThickness = target_ring_thin_border,
BorderColour = Color4.White,
Children = new[]
new Box
{
new Box
{
RelativeSizeAxes = Axes.Both,
Alpha = 0,
AlwaysPresent = true
}
RelativeSizeAxes = Axes.Both,
Alpha = 0,
AlwaysPresent = true
}
}
}
},
circlePiece = new CirclePiece
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Children = new[]
{
symbol = new SwellSymbolPiece()
}
}
}
}
};
});
MainPiece.Add(symbol = new SwellSymbolPiece());
circlePiece.KiaiMode = HitObject.Kiai;
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
circlePiece.AccentColour = colours.YellowDark;
MainPiece.AccentColour = colours.YellowDark;
expandingRing.Colour = colours.YellowLight;
targetRing.BorderColour = colours.YellowDark.Opacity(0.25f);
}

View File

@ -3,7 +3,6 @@
using System.Collections.Generic;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Input;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Taiko.Judgements;
@ -24,12 +23,8 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
public override Vector2 OriginPosition => new Vector2(DrawHeight / 2);
protected override Container<Drawable> Content => bodyContainer;
protected readonly TaikoPiece MainPiece;
private readonly Container bodyContainer;
public new TaikoHitType HitObject;
protected DrawableTaikoHitObject(TaikoHitType hitObject)
@ -40,19 +35,12 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
Anchor = Anchor.CentreLeft;
Origin = Anchor.Custom;
AutoSizeAxes = Axes.Both;
RelativeSizeAxes = Axes.Both;
Size = new Vector2(HitObject.IsStrong ? TaikoHitObject.DEFAULT_STRONG_SIZE : TaikoHitObject.DEFAULT_SIZE);
RelativePositionAxes = Axes.X;
AddInternal(bodyContainer = new Container
{
AutoSizeAxes = Axes.Both,
Children = new[]
{
MainPiece = CreateMainPiece()
}
});
Add(MainPiece = CreateMainPiece());
MainPiece.KiaiMode = HitObject.Kiai;
LifetimeStart = HitObject.StartTime - HitObject.ScrollTime * 2;
@ -60,7 +48,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
protected override TaikoJudgement CreateJudgement() => new TaikoJudgement();
protected virtual TaikoPiece CreateMainPiece() => new CirclePiece(HitObject.IsStrong);
protected virtual TaikoPiece CreateMainPiece() => new CirclePiece();
/// <summary>
/// Sets the scroll position of the DrawableHitObject relative to the offset between

View File

@ -11,19 +11,24 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces
/// <summary>
/// The symbol used for centre hit pieces.
/// </summary>
public class CentreHitSymbolPiece : CircularContainer
public class CentreHitSymbolPiece : Container
{
public CentreHitSymbolPiece()
{
Anchor = Anchor.Centre;
Origin = Anchor.Centre;
Size = new Vector2(CirclePiece.SYMBOL_INNER_SIZE);
Masking = true;
RelativeSizeAxes = Axes.Both;
Size = new Vector2(CirclePiece.SYMBOL_SIZE);
Padding = new MarginPadding(CirclePiece.SYMBOL_BORDER);
Children = new[]
{
new Box
new CircularContainer
{
RelativeSizeAxes = Axes.Both
RelativeSizeAxes = Axes.Both,
Masking = true,
Children = new[] { new Box { RelativeSizeAxes = Axes.Both } }
}
};
}

View File

@ -21,9 +21,8 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces
/// </summary>
public class CirclePiece : TaikoPiece
{
public const float SYMBOL_SIZE = TaikoHitObject.DEFAULT_CIRCLE_DIAMETER * 0.45f;
public const float SYMBOL_SIZE = 0.45f;
public const float SYMBOL_BORDER = 8;
public const float SYMBOL_INNER_SIZE = SYMBOL_SIZE - 2 * SYMBOL_BORDER;
private const double pre_beat_transition_time = 80;
/// <summary>
@ -64,7 +63,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces
public Box FlashBox;
public CirclePiece(bool isStrong = false)
public CirclePiece()
{
EarlyActivationMilliseconds = pre_beat_transition_time;
@ -120,28 +119,12 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces
},
content = new Container
{
RelativeSizeAxes = Axes.Both,
Name = "Content",
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both,
}
});
if (isStrong)
{
Size *= TaikoHitObject.STRONG_CIRCLE_DIAMETER_SCALE;
//default for symbols etc.
Content.Scale *= TaikoHitObject.STRONG_CIRCLE_DIAMETER_SCALE;
}
}
protected override void Update()
{
base.Update();
//we want to allow for width of content to remain mapped to the area inside us, regardless of the scale applied above.
Content.Width = 1 / Content.Scale.X;
}
private const float edge_alpha_kiai = 0.5f;

View File

@ -18,8 +18,9 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces
/// </summary>
public float Length;
public ElongatedCirclePiece(bool isStrong = false) : base(isStrong)
public ElongatedCirclePiece()
{
RelativeSizeAxes = Axes.Y;
}
protected override void Update()

View File

@ -18,7 +18,10 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces
{
Anchor = Anchor.Centre;
Origin = Anchor.Centre;
RelativeSizeAxes = Axes.Both;
Size = new Vector2(CirclePiece.SYMBOL_SIZE);
BorderThickness = CirclePiece.SYMBOL_BORDER;
BorderColour = Color4.White;
Masking = true;

View File

@ -1,24 +1,36 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.Graphics;
using osu.Game.Graphics;
using OpenTK;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Graphics;
namespace osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces
{
/// <summary>
/// The symbol used for swell pieces.
/// </summary>
public class SwellSymbolPiece : SpriteIcon
public class SwellSymbolPiece : Container
{
public SwellSymbolPiece()
{
Anchor = Anchor.Centre;
Origin = Anchor.Centre;
Size = new Vector2(CirclePiece.SYMBOL_INNER_SIZE);
Icon = FontAwesome.fa_asterisk;
Shadow = false;
RelativeSizeAxes = Axes.Both;
Size = new Vector2(CirclePiece.SYMBOL_SIZE);
Padding = new MarginPadding(CirclePiece.SYMBOL_BORDER);
Children = new[]
{
new SpriteIcon
{
RelativeSizeAxes = Axes.Both,
Icon = FontAwesome.fa_asterisk,
Shadow = false
}
};
}
}
}

View File

@ -2,9 +2,9 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Graphics;
using OpenTK;
using OpenTK.Graphics;
using osu.Game.Graphics.Containers;
using osu.Framework.Graphics;
namespace osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces
{
@ -35,8 +35,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces
public TaikoPiece()
{
//just a default
Size = new Vector2(TaikoHitObject.DEFAULT_CIRCLE_DIAMETER);
RelativeSizeAxes = Axes.Both;
}
}
}

View File

@ -15,12 +15,12 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces
/// Any tick that is not the first for a drumroll is not filled, but is instead displayed
/// as a hollow circle. This is what controls the border width of that circle.
/// </summary>
private const float tick_border_width = TaikoHitObject.DEFAULT_CIRCLE_DIAMETER / 16;
private const float tick_border_width = 5;
/// <summary>
/// The size of a tick.
/// </summary>
private const float tick_size = TaikoHitObject.DEFAULT_CIRCLE_DIAMETER / 6;
private const float tick_size = 0.35f;
private bool filled;
public bool Filled
@ -37,6 +37,11 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces
public TickPiece()
{
Anchor = Anchor.Centre;
Origin = Anchor.Centre;
RelativeSizeAxes = Axes.Both;
FillMode = FillMode.Fit;
Size = new Vector2(tick_size);
Add(new CircularContainer

View File

@ -4,31 +4,25 @@
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Taiko.UI;
namespace osu.Game.Rulesets.Taiko.Objects
{
public abstract class TaikoHitObject : HitObject
{
/// <summary>
/// Diameter of a circle relative to the size of the <see cref="TaikoPlayfield"/>.
/// Default size of a drawable taiko hit object.
/// </summary>
public const float PLAYFIELD_RELATIVE_DIAMETER = 0.45f;
public const float DEFAULT_SIZE = 0.45f;
/// <summary>
/// Scale multiplier for a strong circle.
/// Scale multiplier for a strong drawable taiko hit object.
/// </summary>
public const float STRONG_CIRCLE_DIAMETER_SCALE = 1.4f;
public const float STRONG_SCALE = 1.4f;
/// <summary>
/// Default circle diameter.
/// Default size of a strong drawable taiko hit object.
/// </summary>
public const float DEFAULT_CIRCLE_DIAMETER = TaikoPlayfield.DEFAULT_PLAYFIELD_HEIGHT * PLAYFIELD_RELATIVE_DIAMETER;
/// <summary>
/// Default strong circle diameter.
/// </summary>
public const float DEFAULT_STRONG_CIRCLE_DIAMETER = DEFAULT_CIRCLE_DIAMETER * STRONG_CIRCLE_DIAMETER_SCALE;
public const float DEFAULT_STRONG_SIZE = DEFAULT_SIZE * STRONG_SCALE;
/// <summary>
/// The time taken from the initial (off-screen) spawn position to the centre of the hit target for a <see cref="TimingControlPoint.BeatLength"/> of 1000ms.

View File

@ -30,10 +30,11 @@ namespace osu.Game.Rulesets.Taiko.UI
Judgement = judgement;
Anchor = Anchor.Centre;
Anchor = Anchor.CentreLeft;
Origin = Anchor.Centre;
Size = new Vector2(TaikoPlayfield.HIT_TARGET_OFFSET + TaikoHitObject.DEFAULT_CIRCLE_DIAMETER);
RelativeSizeAxes = Axes.Both;
Size = new Vector2(TaikoHitObject.DEFAULT_SIZE);
RelativePositionAxes = Axes.Both;
@ -73,7 +74,7 @@ namespace osu.Game.Rulesets.Taiko.UI
/// </summary>
public void VisualiseSecondHit()
{
this.ResizeTo(new Vector2(TaikoPlayfield.HIT_TARGET_OFFSET + TaikoHitObject.DEFAULT_STRONG_CIRCLE_DIAMETER), 50);
this.ResizeTo(new Vector2(TaikoHitObject.DEFAULT_STRONG_SIZE), 50);
}
}
}

View File

@ -15,11 +15,6 @@ namespace osu.Game.Rulesets.Taiko.UI
/// </summary>
internal class HitTarget : Container
{
/// <summary>
/// The 1px inner border of the taiko playfield.
/// </summary>
private const float border_offset = 1;
/// <summary>
/// Thickness of all drawn line pieces.
/// </summary>
@ -27,8 +22,6 @@ namespace osu.Game.Rulesets.Taiko.UI
public HitTarget()
{
Size = new Vector2(TaikoPlayfield.DEFAULT_PLAYFIELD_HEIGHT);
Children = new Drawable[]
{
new Box
@ -36,8 +29,8 @@ namespace osu.Game.Rulesets.Taiko.UI
Name = "Bar Upper",
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
Y = border_offset,
Size = new Vector2(border_thickness, (TaikoPlayfield.DEFAULT_PLAYFIELD_HEIGHT - TaikoHitObject.DEFAULT_STRONG_CIRCLE_DIAMETER) / 2f - border_offset),
RelativeSizeAxes = Axes.Y,
Size = new Vector2(border_thickness, (1 - TaikoHitObject.DEFAULT_STRONG_SIZE) / 2f),
Alpha = 0.1f
},
new CircularContainer
@ -45,7 +38,9 @@ namespace osu.Game.Rulesets.Taiko.UI
Name = "Strong Hit Ring",
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = new Vector2(TaikoHitObject.DEFAULT_STRONG_CIRCLE_DIAMETER),
RelativeSizeAxes = Axes.Both,
FillMode = FillMode.Fit,
Scale = new Vector2(TaikoHitObject.DEFAULT_STRONG_SIZE),
Masking = true,
BorderColour = Color4.White,
BorderThickness = border_thickness,
@ -65,7 +60,9 @@ namespace osu.Game.Rulesets.Taiko.UI
Name = "Normal Hit Ring",
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = new Vector2(TaikoHitObject.DEFAULT_CIRCLE_DIAMETER),
RelativeSizeAxes = Axes.Both,
FillMode = FillMode.Fit,
Scale = new Vector2(TaikoHitObject.DEFAULT_SIZE),
Masking = true,
BorderColour = Color4.White,
BorderThickness = border_thickness,
@ -85,8 +82,8 @@ namespace osu.Game.Rulesets.Taiko.UI
Name = "Bar Lower",
Anchor = Anchor.BottomCentre,
Origin = Anchor.BottomCentre,
Y = -border_offset,
Size = new Vector2(border_thickness, (TaikoPlayfield.DEFAULT_PLAYFIELD_HEIGHT - TaikoHitObject.DEFAULT_STRONG_CIRCLE_DIAMETER) / 2f - border_offset),
RelativeSizeAxes = Axes.Y,
Size = new Vector2(border_thickness, (1 - TaikoHitObject.DEFAULT_STRONG_SIZE) / 2f),
Alpha = 0.1f
},
};

View File

@ -21,9 +21,10 @@ namespace osu.Game.Rulesets.Taiko.UI
{
public InputDrum()
{
Size = new Vector2(TaikoPlayfield.DEFAULT_PLAYFIELD_HEIGHT);
RelativeSizeAxes = Axes.Both;
FillMode = FillMode.Fit;
const float middle_split = 10;
const float middle_split = 0.025f;
Children = new Drawable[]
{
@ -33,6 +34,7 @@ namespace osu.Game.Rulesets.Taiko.UI
Anchor = Anchor.Centre,
Origin = Anchor.CentreRight,
RelativeSizeAxes = Axes.Both,
RelativePositionAxes = Axes.X,
X = -middle_split / 2,
RimKey = Key.D,
CentreKey = Key.F
@ -43,8 +45,8 @@ namespace osu.Game.Rulesets.Taiko.UI
Anchor = Anchor.Centre,
Origin = Anchor.CentreLeft,
RelativeSizeAxes = Axes.Both,
RelativePositionAxes = Axes.X,
X = middle_split / 2,
Position = new Vector2(-1f, 0),
RimKey = Key.K,
CentreKey = Key.J
}

View File

@ -24,11 +24,11 @@ namespace osu.Game.Rulesets.Taiko.UI
Judgement = judgement;
Anchor = Anchor.Centre;
Anchor = Anchor.CentreLeft;
Origin = Anchor.Centre;
RelativeSizeAxes = Axes.Y;
Size = new Vector2(TaikoHitObject.DEFAULT_CIRCLE_DIAMETER, 1);
RelativeSizeAxes = Axes.Both;
Size = new Vector2(TaikoHitObject.DEFAULT_SIZE, 1);
Masking = true;
Alpha = 0.25f;

View File

@ -85,7 +85,7 @@ namespace osu.Game.Rulesets.Taiko.UI
protected override Vector2 GetPlayfieldAspectAdjust()
{
const float default_relative_height = TaikoPlayfield.DEFAULT_PLAYFIELD_HEIGHT / 768;
const float default_relative_height = TaikoPlayfield.DEFAULT_HEIGHT / 768;
const float default_aspect = 16f / 9f;
float aspectAdjust = MathHelper.Clamp(DrawWidth / DrawHeight, 0.4f, 4) / default_aspect;

View File

@ -15,21 +15,20 @@ using osu.Framework.Graphics.Containers;
using osu.Framework.Extensions.Color4Extensions;
using System.Linq;
using osu.Game.Rulesets.Taiko.Objects.Drawables;
using System;
namespace osu.Game.Rulesets.Taiko.UI
{
public class TaikoPlayfield : Playfield<TaikoHitObject, TaikoJudgement>
{
/// <summary>
/// The default play field height.
/// Default height of a <see cref="TaikoPlayfield"/> when inside a <see cref="TaikoHitRenderer"/>.
/// </summary>
public const float DEFAULT_PLAYFIELD_HEIGHT = 178f;
public const float DEFAULT_HEIGHT = 178;
/// <summary>
/// The offset from <see cref="left_area_size"/> which the center of the hit target lies at.
/// </summary>
public const float HIT_TARGET_OFFSET = TaikoHitObject.DEFAULT_STRONG_CIRCLE_DIAMETER / 2f + 40;
public const float HIT_TARGET_OFFSET = 100;
/// <summary>
/// The size of the left area of the playfield. This area contains the input drum.
@ -56,112 +55,124 @@ namespace osu.Game.Rulesets.Taiko.UI
{
AddRangeInternal(new Drawable[]
{
new ScaleFixContainer
backgroundContainer = new Container
{
RelativeSizeAxes = Axes.X,
Height = DEFAULT_PLAYFIELD_HEIGHT,
Children = new[]
Name = "Transparent playfield background",
RelativeSizeAxes = Axes.Both,
Masking = true,
EdgeEffect = new EdgeEffectParameters
{
backgroundContainer = new Container
Type = EdgeEffectType.Shadow,
Colour = Color4.Black.Opacity(0.2f),
Radius = 5,
},
Children = new Drawable[]
{
background = new Box
{
Name = "Transparent playfield background",
RelativeSizeAxes = Axes.Both,
BorderThickness = 2,
Masking = true,
EdgeEffect = new EdgeEffectParameters
{
Type = EdgeEffectType.Shadow,
Colour = Color4.Black.Opacity(0.2f),
Radius = 5,
},
Children = new Drawable[]
{
background = new Box
{
RelativeSizeAxes = Axes.Both,
Alpha = 0.6f
},
}
Alpha = 0.6f
},
}
},
new Container
{
Name = "Right area",
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding { Left = left_area_size },
Children = new Drawable[]
{
new Container
{
Name = "Right area",
Name = "Masked elements",
RelativeSizeAxes = Axes.Both,
Margin = new MarginPadding { Left = left_area_size },
Padding = new MarginPadding { Left = HIT_TARGET_OFFSET },
Masking = true,
Children = new Drawable[]
{
new Container
hitExplosionContainer = new Container<HitExplosion>
{
Name = "Masked elements",
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding { Left = HIT_TARGET_OFFSET },
Masking = true,
Children = new Drawable[]
{
hitExplosionContainer = new Container<HitExplosion>
{
RelativeSizeAxes = Axes.Y,
BlendingMode = BlendingMode.Additive,
},
barLineContainer = new Container<DrawableBarLine>
{
RelativeSizeAxes = Axes.Both,
},
new HitTarget
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.Centre,
},
hitObjectContainer = new Container
{
RelativeSizeAxes = Axes.Both,
},
}
FillMode = FillMode.Fit,
BlendingMode = BlendingMode.Additive,
},
kiaiExplosionContainer = new Container<KiaiHitExplosion>
{
Name = "Kiai hit explosions",
RelativeSizeAxes = Axes.Y,
Margin = new MarginPadding { Left = HIT_TARGET_OFFSET },
BlendingMode = BlendingMode.Additive
},
judgementContainer = new Container<DrawableTaikoJudgement>
{
Name = "Judgements",
RelativeSizeAxes = Axes.Y,
Margin = new MarginPadding { Left = HIT_TARGET_OFFSET },
BlendingMode = BlendingMode.Additive
},
}
},
overlayBackgroundContainer = new Container
{
Name = "Left overlay",
Size = new Vector2(left_area_size, DEFAULT_PLAYFIELD_HEIGHT),
BorderThickness = 1,
Children = new Drawable[]
{
overlayBackground = new Box
barLineContainer = new Container<DrawableBarLine>
{
RelativeSizeAxes = Axes.Both,
},
new InputDrum
new HitTarget
{
Anchor = Anchor.Centre,
Anchor = Anchor.CentreLeft,
Origin = Anchor.Centre,
RelativePositionAxes = Axes.X,
Position = new Vector2(0.10f, 0),
Scale = new Vector2(0.9f)
RelativeSizeAxes = Axes.Both,
FillMode = FillMode.Fit
},
new Box
hitObjectContainer = new Container
{
Anchor = Anchor.TopRight,
RelativeSizeAxes = Axes.Y,
Width = 10,
Colour = Framework.Graphics.Colour.ColourInfo.GradientHorizontal(Color4.Black.Opacity(0.6f), Color4.Black.Opacity(0)),
RelativeSizeAxes = Axes.Both,
},
}
},
kiaiExplosionContainer = new Container<KiaiHitExplosion>
{
Name = "Kiai hit explosions",
RelativeSizeAxes = Axes.Both,
FillMode = FillMode.Fit,
Margin = new MarginPadding { Left = HIT_TARGET_OFFSET },
BlendingMode = BlendingMode.Additive
},
judgementContainer = new Container<DrawableTaikoJudgement>
{
Name = "Judgements",
RelativeSizeAxes = Axes.Y,
Margin = new MarginPadding { Left = HIT_TARGET_OFFSET },
BlendingMode = BlendingMode.Additive
},
}
},
overlayBackgroundContainer = new Container
{
Name = "Left overlay",
RelativeSizeAxes = Axes.Y,
Size = new Vector2(left_area_size, 1),
Children = new Drawable[]
{
overlayBackground = new Box
{
RelativeSizeAxes = Axes.Both,
},
new InputDrum
{
Anchor = Anchor.CentreRight,
Origin = Anchor.CentreRight,
Scale = new Vector2(0.9f),
Margin = new MarginPadding { Right = 20 }
},
new Box
{
Anchor = Anchor.TopRight,
RelativeSizeAxes = Axes.Y,
Width = 10,
Colour = Framework.Graphics.Colour.ColourInfo.GradientHorizontal(Color4.Black.Opacity(0.6f), Color4.Black.Opacity(0)),
},
}
},
new Container
{
Name = "Border",
RelativeSizeAxes = Axes.Both,
Masking = true,
MaskingSmoothness = 0,
BorderThickness = 2,
AlwaysPresent = true,
Children = new[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Alpha = 0,
AlwaysPresent = true
}
}
},
topLevelHitContainer = new Container
@ -233,56 +244,5 @@ namespace osu.Game.Rulesets.Taiko.UI
else
hitExplosionContainer.Children.FirstOrDefault(e => e.Judgement == judgedObject.Judgement)?.VisualiseSecondHit();
}
/// <summary>
/// This is a very special type of container. It serves a similar purpose to <see cref="FillMode.Fit"/>, however unlike <see cref="FillMode.Fit"/>,
/// this will only adjust the scale relative to the height of its parent and will maintain the original width relative to its parent.
///
/// <para>
/// By adjusting the scale relative to the height of its parent, the aspect ratio of this container's children is maintained, however this is undesirable
/// in the case where the hit object container should not have its width adjusted by scale. To counteract this, another container is nested inside this
/// container which takes care of reversing the width adjustment while appearing transparent to the user.
/// </para>
/// </summary>
private class ScaleFixContainer : Container
{
protected override Container<Drawable> Content => widthAdjustmentContainer;
private readonly WidthAdjustmentContainer widthAdjustmentContainer;
/// <summary>
/// We only want to apply DrawScale in the Y-axis to preserve aspect ratio and <see cref="TaikoPlayfield"/> doesn't care about having its width adjusted.
/// </summary>
protected override Vector2 DrawScale => Scale * RelativeToAbsoluteFactor.Y / DrawHeight;
public ScaleFixContainer()
{
AddInternal(widthAdjustmentContainer = new WidthAdjustmentContainer { ParentDrawScaleReference = () => DrawScale.X });
}
/// <summary>
/// The container type that reverses the <see cref="Drawable.DrawScale"/> width adjustment.
/// </summary>
private class WidthAdjustmentContainer : Container
{
/// <summary>
/// This container needs to know its parent's <see cref="Drawable.DrawScale"/> so it can reverse the width adjustment caused by <see cref="Drawable.DrawScale"/>.
/// </summary>
public Func<float> ParentDrawScaleReference;
public WidthAdjustmentContainer()
{
// This container doesn't care about height, it should always fill its parent
RelativeSizeAxes = Axes.Y;
}
protected override void Update()
{
base.Update();
// Reverse the DrawScale adjustment
Width = Parent.DrawSize.X / ParentDrawScaleReference();
}
}
}
}
}

View File

@ -7,6 +7,7 @@ using osu.Game.Rulesets.Objects;
using System.Collections.Generic;
using System.Linq;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.IO.Serialization;
namespace osu.Game.Beatmaps
{
@ -45,7 +46,7 @@ namespace osu.Game.Beatmaps
/// <param name="original">The original beatmap to use the parameters of.</param>
public Beatmap(Beatmap original = null)
{
BeatmapInfo = original?.BeatmapInfo ?? BeatmapInfo;
BeatmapInfo = original?.BeatmapInfo.DeepClone() ?? BeatmapInfo;
ControlPointInfo = original?.ControlPointInfo ?? ControlPointInfo;
Breaks = original?.Breaks ?? Breaks;
ComboColors = original?.ComboColors ?? ComboColors;

View File

@ -69,7 +69,7 @@ namespace osu.Game.Beatmaps
// Editor
// This bookmarks stuff is necessary because DB doesn't know how to store int[]
public string StoredBookmarks { get; internal set; }
public string StoredBookmarks { get; set; }
[Ignore]
[JsonIgnore]

View File

@ -52,7 +52,14 @@ namespace osu.Game.Beatmaps
{
lock (beatmapLock)
{
return beatmap ?? (beatmap = GetBeatmap());
if (beatmap != null) return beatmap;
beatmap = GetBeatmap();
// use the database-backed info.
beatmap.BeatmapInfo = BeatmapInfo;
return beatmap;
}
}
}

View File

@ -128,13 +128,11 @@ namespace osu.Game.Graphics.Containers
RelativeSizeAxes = Axes.Both,
Masking = true,
ScrollbarVisible = false,
Children = new Drawable[] { scrollContentContainer = CreateScrollContentContainer() },
Depth = float.MaxValue
Children = new Drawable[] { scrollContentContainer = CreateScrollContentContainer() }
});
AddInternal(headerBackgroundContainer = new Container
{
RelativeSizeAxes = Axes.X,
Depth = float.MaxValue / 2
RelativeSizeAxes = Axes.X
});
originalSectionsMargin = scrollContentContainer.Margin;
}

View File

@ -20,14 +20,19 @@ namespace osu.Game.Graphics
public static class AccentedColourExtensions
{
/// <summary>
/// Tweens the accent colour of a drawable to another colour.
/// Smoothly adjusts <see cref="IHasAccentColour.AccentColour"/> over time.
/// </summary>
/// <param name="accentedDrawable">The drawable to apply the accent colour to.</param>
/// <param name="newColour">The new accent colour.</param>
/// <param name="duration">The tween duration.</param>
/// <param name="easing">The tween easing.</param>
/// <returns>A <see cref="TransformSequence{T}"/> to which further transforms can be added.</returns>
public static TransformSequence<T> FadeAccent<T>(this T accentedDrawable, Color4 newColour, double duration = 0, Easing easing = Easing.None)
where T : IHasAccentColour
=> accentedDrawable.TransformTo(nameof(accentedDrawable.AccentColour), newColour, duration, easing);
/// <summary>
/// Smoothly adjusts <see cref="IHasAccentColour.AccentColour"/> over time.
/// </summary>
/// <returns>A <see cref="TransformSequence{T}"/> to which further transforms can be added.</returns>
public static TransformSequence<T> FadeAccent<T>(this TransformSequence<T> t, Color4 newColour, double duration = 0, Easing easing = Easing.None)
where T : Drawable, IHasAccentColour
=> t.Append(o => o.FadeAccent(newColour, duration, easing));
}
}

View File

@ -1,6 +1,7 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using osu.Framework.Allocation;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
@ -8,6 +9,7 @@ using osu.Framework.Graphics;
using osu.Framework.IO.Stores;
using OpenTK;
using OpenTK.Graphics;
using osu.Framework.Caching;
namespace osu.Game.Graphics
{
@ -16,19 +18,28 @@ namespace osu.Game.Graphics
private readonly Sprite spriteShadow;
private readonly Sprite spriteMain;
private Cached layout = new Cached();
private readonly Container shadowVisibility;
public SpriteIcon()
{
InternalChildren = new[]
InternalChildren = new Drawable[]
{
spriteShadow = new Sprite
shadowVisibility = new Container
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both,
FillMode = FillMode.Fit,
Position = new Vector2(0, 0.06f),
Colour = new Color4(0f, 0f, 0f, 0.2f),
Alpha = 0
Child = spriteShadow = new Sprite
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both,
FillMode = FillMode.Fit,
Y = 2,
Colour = new Color4(0f, 0f, 0f, 0.2f),
},
Alpha = 0,
},
spriteMain = new Sprite
{
@ -60,10 +71,34 @@ namespace osu.Game.Graphics
Size = new Vector2(texture?.DisplayWidth ?? 0, texture?.DisplayHeight ?? 0);
}
public override bool Invalidate(Invalidation invalidation = Invalidation.All, Drawable source = null, bool shallPropagate = true)
{
if ((invalidation & Invalidation.Colour) > 0 && Shadow)
layout.Invalidate();
return base.Invalidate(invalidation, source, shallPropagate);
}
protected override void Update()
{
if (!layout.IsValid)
{
//adjust shadow alpha based on highest component intensity to avoid muddy display of darker text.
//squared result for quadratic fall-off seems to give the best result.
var avgColour = (Color4)DrawInfo.Colour.AverageColour;
spriteShadow.Alpha = (float)Math.Pow(Math.Max(Math.Max(avgColour.R, avgColour.G), avgColour.B), 2);
layout.Validate();
}
}
public bool Shadow
{
get { return spriteShadow.IsPresent; }
set { spriteShadow.Alpha = value ? 1 : 0; }
set
{
shadowVisibility.Alpha = value ? 1 : 0;
}
}
private FontAwesome icon;

View File

@ -13,7 +13,7 @@ namespace osu.Game.IO.Serialization
{
public static string Serialize(this IJsonSerializable obj)
{
return JsonConvert.SerializeObject(obj);
return JsonConvert.SerializeObject(obj, new JsonSerializerSettings { ReferenceLoopHandling = ReferenceLoopHandling.Ignore });
}
public static T Deserialize<T>(this string objString)

View File

@ -169,13 +169,11 @@ namespace osu.Game.Overlays
{
header.User = user;
for (int i = 0; i < user.ProfileOrder.Length; i++)
foreach (string id in user.ProfileOrder)
{
string id = user.ProfileOrder[i];
var sec = sections.FirstOrDefault(s => s.Identifier == id);
if (sec != null)
{
sec.Depth = -i;
sectionsContainer.Add(sec);
tabs.AddItem(sec);
}

View File

@ -0,0 +1,15 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Beatmaps;
namespace osu.Game.Rulesets.Mods
{
/// <summary>
/// An interface for mods that make general adjustments to difficulty.
/// </summary>
public interface IApplicableToDifficulty
{
void ApplyToDifficulty(BeatmapDifficulty difficulty);
}
}

View File

@ -45,5 +45,10 @@ namespace osu.Game.Rulesets.Mods
/// The mods this mod cannot be enabled with.
/// </summary>
public virtual Type[] IncompatibleMods => new Type[] { };
/// <summary>
/// Whether we should allow failing at the current point in time.
/// </summary>
public virtual bool AllowFail => true;
}
}

View File

@ -2,11 +2,12 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using osu.Game.Beatmaps;
using osu.Game.Graphics;
namespace osu.Game.Rulesets.Mods
{
public abstract class ModEasy : Mod
public abstract class ModEasy : Mod, IApplicableToDifficulty
{
public override string Name => "Easy";
public override FontAwesome Icon => FontAwesome.fa_osu_mod_easy;
@ -15,5 +16,14 @@ namespace osu.Game.Rulesets.Mods
public override double ScoreMultiplier => 0.5;
public override bool Ranked => true;
public override Type[] IncompatibleMods => new[] { typeof(ModHardRock) };
public void ApplyToDifficulty(BeatmapDifficulty difficulty)
{
const float ratio = 0.5f;
difficulty.CircleSize *= ratio;
difficulty.ApproachRate *= ratio;
difficulty.DrainRate *= ratio;
difficulty.OverallDifficulty *= ratio;
}
}
}

View File

@ -2,16 +2,26 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using osu.Game.Beatmaps;
using osu.Game.Graphics;
namespace osu.Game.Rulesets.Mods
{
public abstract class ModHardRock : Mod
public abstract class ModHardRock : Mod, IApplicableToDifficulty
{
public override string Name => "Hard Rock";
public override FontAwesome Icon => FontAwesome.fa_osu_mod_hardrock;
public override ModType Type => ModType.DifficultyIncrease;
public override string Description => "Everything just got a bit harder...";
public override Type[] IncompatibleMods => new[] { typeof(ModEasy) };
public void ApplyToDifficulty(BeatmapDifficulty difficulty)
{
const float ratio = 1.4f;
difficulty.CircleSize *= 1.3f; // CS uses a custom 1.3 ratio.
difficulty.ApproachRate *= ratio;
difficulty.DrainRate *= ratio;
difficulty.OverallDifficulty *= ratio;
}
}
}
}

View File

@ -15,5 +15,10 @@ namespace osu.Game.Rulesets.Mods
public override double ScoreMultiplier => 0.5;
public override bool Ranked => true;
public override Type[] IncompatibleMods => new[] { typeof(ModRelax), typeof(ModSuddenDeath), typeof(ModAutoplay) };
/// <summary>
/// We never fail, 'yo.
/// </summary>
public override bool AllowFail => false;
}
}

View File

@ -16,8 +16,9 @@ namespace osu.Game.Rulesets.Scoring
{
/// <summary>
/// Invoked when the ScoreProcessor is in a failed state.
/// Return true if the fail was permitted.
/// </summary>
public event Action Failed;
public event Func<bool> Failed;
/// <summary>
/// Invoked when a new judgement has occurred. This occurs after the judgement has been processed by the <see cref="ScoreProcessor"/>.
@ -106,8 +107,8 @@ namespace osu.Game.Rulesets.Scoring
if (alreadyFailed || !HasFailed)
return;
alreadyFailed = true;
Failed?.Invoke();
if (Failed?.Invoke() != false)
alreadyFailed = true;
}
/// <summary>

View File

@ -153,6 +153,10 @@ namespace osu.Game.Rulesets.UI
// Convert the beatmap
Beatmap = converter.Convert(beatmap.Beatmap, isForCurrentRuleset);
// Apply difficulty adjustments from mods before using Difficulty.
foreach (var mod in Mods.OfType<IApplicableToDifficulty>())
mod.ApplyToDifficulty(Beatmap.BeatmapInfo.Difficulty);
// Apply defaults
foreach (var h in Beatmap.HitObjects)
h.ApplyDefaults(Beatmap.ControlPointInfo, Beatmap.BeatmapInfo.Difficulty);
@ -163,7 +167,7 @@ namespace osu.Game.Rulesets.UI
ApplyBeatmap();
// Add mods, should always be the last thing applied to give full control to mods
applyMods(beatmap.Mods.Value);
applyMods(Mods);
}
/// <summary>

View File

@ -45,10 +45,11 @@ namespace osu.Game.Rulesets.UI
},
modIcon = new SpriteIcon
{
Origin = Anchor.Centre,
Anchor = Anchor.Centre,
Origin = Anchor.TopCentre,
Anchor = Anchor.TopCentre,
Colour = OsuColour.Gray(84),
Size = new Vector2(icon_size - 35),
Y = 25,
Icon = mod.Icon
},
};

View File

@ -22,6 +22,7 @@ using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Scoring;
using osu.Game.Screens.Ranking;
using osu.Framework.Audio.Sample;
using osu.Game.Beatmaps;
namespace osu.Game.Screens.Play
{
@ -77,23 +78,28 @@ namespace osu.Game.Screens.Play
Ruleset rulesetInstance;
WorkingBeatmap working = Beatmap.Value;
Beatmap beatmap;
try
{
if (Beatmap.Value.Beatmap == null)
beatmap = working.Beatmap;
if (beatmap == null)
throw new InvalidOperationException("Beatmap was not loaded");
ruleset = osu?.Ruleset.Value ?? Beatmap.Value.BeatmapInfo.Ruleset;
ruleset = osu?.Ruleset.Value ?? beatmap.BeatmapInfo.Ruleset;
rulesetInstance = ruleset.CreateInstance();
try
{
HitRenderer = rulesetInstance.CreateHitRendererWith(Beatmap, ruleset.ID == Beatmap.Value.BeatmapInfo.Ruleset.ID);
HitRenderer = rulesetInstance.CreateHitRendererWith(working, ruleset.ID == beatmap.BeatmapInfo.Ruleset.ID);
}
catch (BeatmapInvalidForRulesetException)
{
// we may fail to create a HitRenderer if the beatmap cannot be loaded with the user's preferred ruleset
// let's try again forcing the beatmap's ruleset.
ruleset = Beatmap.Value.BeatmapInfo.Ruleset;
ruleset = beatmap.BeatmapInfo.Ruleset;
rulesetInstance = ruleset.CreateInstance();
HitRenderer = rulesetInstance.CreateHitRendererWith(Beatmap, true);
}
@ -110,11 +116,11 @@ namespace osu.Game.Screens.Play
return;
}
adjustableSourceClock = (IAdjustableClock)Beatmap.Value.Track ?? new StopwatchClock();
adjustableSourceClock = (IAdjustableClock)working.Track ?? new StopwatchClock();
decoupledClock = new DecoupleableInterpolatingFramedClock { IsCoupled = false };
var firstObjectTime = HitRenderer.Objects.First().StartTime;
decoupledClock.Seek(Math.Min(0, firstObjectTime - Math.Max(Beatmap.Value.Beatmap.ControlPointInfo.TimingPointAt(firstObjectTime).BeatLength * 4, Beatmap.Value.BeatmapInfo.AudioLeadIn)));
decoupledClock.Seek(Math.Min(0, firstObjectTime - Math.Max(beatmap.ControlPointInfo.TimingPointAt(firstObjectTime).BeatLength * 4, beatmap.BeatmapInfo.AudioLeadIn)));
decoupledClock.ProcessFrame();
offsetClock = new FramedOffsetClock(decoupledClock);
@ -127,7 +133,7 @@ namespace osu.Game.Screens.Play
{
adjustableSourceClock.Reset();
foreach (var mod in Beatmap.Value.Mods.Value.OfType<IApplicableToClock>())
foreach (var mod in working.Mods.Value.OfType<IApplicableToClock>())
mod.ApplyToClock(adjustableSourceClock);
decoupledClock.ChangeSource(adjustableSourceClock);
@ -195,7 +201,7 @@ namespace osu.Game.Screens.Play
hudOverlay.Progress.AllowSeeking = HitRenderer.HasReplayLoaded;
hudOverlay.Progress.OnSeek = pos => decoupledClock.Seek(pos);
hudOverlay.ModDisplay.Current.BindTo(Beatmap.Value.Mods);
hudOverlay.ModDisplay.Current.BindTo(working.Mods);
//bind HitRenderer to ScoreProcessor and ourselves (for a pass situation)
HitRenderer.OnAllJudged += onCompletion;
@ -238,13 +244,17 @@ namespace osu.Game.Screens.Play
}
}
private void onFail()
private bool onFail()
{
if (Beatmap.Value.Mods.Value.Any(m => !m.AllowFail))
return false;
decoupledClock.Stop();
HasFailed = true;
failOverlay.Retries = RestartCount;
failOverlay.Show();
return true;
}
protected override void OnEntering(Screen last)

View File

@ -117,6 +117,7 @@
<Compile Include="Overlays\Profile\Sections\RanksSection.cs" />
<Compile Include="Overlays\Profile\Sections\RecentSection.cs" />
<Compile Include="Graphics\Containers\ConstrainedIconContainer.cs" />
<Compile Include="Rulesets\Mods\IApplicableToDifficulty.cs" />
<Compile Include="Users\UserCoverBackground.cs" />
<Compile Include="Overlays\UserProfileOverlay.cs" />
<Compile Include="Overlays\Profile\ProfileHeader.cs" />