1
0
mirror of https://github.com/ppy/osu.git synced 2024-11-11 11:37:28 +08:00

Merge pull request #591 from peppy/taiko-wtf

taiko improvements
This commit is contained in:
Dan Balasescu 2017-04-06 13:23:25 +09:00 committed by GitHub
commit 0acf5b45b1
36 changed files with 468 additions and 426 deletions

@ -1 +1 @@
Subproject commit cd715ac535ace224188ac56f88a08c4c2908f51b
Subproject commit a7c99e06ff4c3f56fad24bec170eb93f42b1e149

View File

@ -29,7 +29,6 @@ namespace osu.Desktop.VisualTests.Tests
Add(new CirclePiece
{
Position = new Vector2(100, 100),
Width = 0,
AccentColour = Color4.DarkRed,
KiaiMode = kiai,
Children = new[]
@ -38,10 +37,9 @@ namespace osu.Desktop.VisualTests.Tests
}
});
Add(new StrongCirclePiece
Add(new CirclePiece(true)
{
Position = new Vector2(350, 100),
Width = 0,
AccentColour = Color4.DarkRed,
KiaiMode = kiai,
Children = new[]
@ -53,7 +51,6 @@ namespace osu.Desktop.VisualTests.Tests
Add(new CirclePiece
{
Position = new Vector2(100, 300),
Width = 0,
AccentColour = Color4.DarkBlue,
KiaiMode = kiai,
Children = new[]
@ -62,10 +59,9 @@ namespace osu.Desktop.VisualTests.Tests
}
});
Add(new StrongCirclePiece
Add(new CirclePiece(true)
{
Position = new Vector2(350, 300),
Width = 0,
AccentColour = Color4.DarkBlue,
KiaiMode = kiai,
Children = new[]
@ -77,7 +73,6 @@ namespace osu.Desktop.VisualTests.Tests
Add(new CirclePiece
{
Position = new Vector2(100, 500),
Width = 0,
AccentColour = Color4.Orange,
KiaiMode = kiai,
Children = new[]
@ -86,20 +81,22 @@ namespace osu.Desktop.VisualTests.Tests
}
});
Add(new CirclePiece
Add(new ElongatedCirclePiece
{
Position = new Vector2(575, 100),
Width = 0.25f,
AccentColour = Color4.Orange,
KiaiMode = kiai,
Length = 0.10f,
PlayfieldLengthReference = () => DrawSize.X
});
Add(new StrongCirclePiece
Add(new ElongatedCirclePiece(true)
{
Position = new Vector2(575, 300),
Width = 0.25f,
AccentColour = Color4.Orange,
KiaiMode = kiai
KiaiMode = kiai,
Length = 0.10f,
PlayfieldLengthReference = () => DrawSize.X
});
}

View File

@ -5,6 +5,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.MathUtils;
using osu.Framework.Testing;
using osu.Framework.Timing;
using osu.Game.Modes.Objects.Drawables;
using osu.Game.Modes.Taiko.Judgements;
using osu.Game.Modes.Taiko.Objects;
@ -19,7 +20,11 @@ namespace osu.Desktop.VisualTests.Tests
private TaikoPlayfield playfield;
protected override double TimePerAction => 500;
protected override double TimePerAction => default_duration * 2;
private const double default_duration = 300;
private const float scroll_time = 1000;
public override void Reset()
{
@ -29,7 +34,7 @@ namespace osu.Desktop.VisualTests.Tests
AddStep("Miss :(", addMissJudgement);
AddStep("DrumRoll", () => addDrumRoll(false));
AddStep("Strong DrumRoll", () => addDrumRoll(true));
AddStep("Swell", addSwell);
AddStep("Swell", () => addSwell());
AddStep("Centre", () => addCentreHit(false));
AddStep("Strong Centre", () => addCentreHit(true));
AddStep("Rim", () => addRimHit(false));
@ -37,8 +42,12 @@ namespace osu.Desktop.VisualTests.Tests
AddStep("Add bar line", () => addBarLine(false));
AddStep("Add major bar line", () => addBarLine(true));
var rateAdjustClock = new StopwatchClock(true) { Rate = 1 };
Add(new Container
{
Clock = new FramedClock(rateAdjustClock),
RelativeSizeAxes = Axes.X,
Y = 200,
Children = new[]
@ -77,49 +86,53 @@ namespace osu.Desktop.VisualTests.Tests
});
}
private void addBarLine(bool major)
private void addBarLine(bool major, double delay = scroll_time)
{
BarLine bl = new BarLine
{
StartTime = Time.Current + 1000,
PreEmpt = 1000
StartTime = playfield.Time.Current + delay,
ScrollTime = scroll_time
};
playfield.AddBarLine(major ? new DrawableMajorBarLine(bl) : new DrawableBarLine(bl));
playfield.AddBarLine(major ? new DrawableBarLineMajor(bl) : new DrawableBarLine(bl));
}
private void addSwell()
private void addSwell(double duration = default_duration)
{
playfield.Add(new DrawableSwell(new Swell
{
StartTime = Time.Current + 1000,
EndTime = Time.Current + 1000,
PreEmpt = 1000
StartTime = playfield.Time.Current + scroll_time,
Duration = duration,
ScrollTime = scroll_time
}));
}
private void addDrumRoll(bool strong)
private void addDrumRoll(bool strong, double duration = default_duration)
{
addBarLine(true);
addBarLine(true, scroll_time + duration);
var d = new DrumRoll
{
StartTime = Time.Current + 1000,
Distance = 1000,
PreEmpt = 1000,
StartTime = playfield.Time.Current + scroll_time,
IsStrong = strong,
Duration = duration,
ScrollTime = scroll_time,
};
playfield.Add(strong ? new DrawableStrongDrumRoll(d) : new DrawableDrumRoll(d));
playfield.Add(new DrawableDrumRoll(d));
}
private void addCentreHit(bool strong)
{
Hit h = new Hit
{
StartTime = Time.Current + 1000,
PreEmpt = 1000
StartTime = playfield.Time.Current + scroll_time,
ScrollTime = scroll_time
};
if (strong)
playfield.Add(new DrawableStrongCentreHit(h));
playfield.Add(new DrawableCentreHitStrong(h));
else
playfield.Add(new DrawableCentreHit(h));
}
@ -128,12 +141,12 @@ namespace osu.Desktop.VisualTests.Tests
{
Hit h = new Hit
{
StartTime = Time.Current + 1000,
PreEmpt = 1000
StartTime = playfield.Time.Current + scroll_time,
ScrollTime = scroll_time
};
if (strong)
playfield.Add(new DrawableStrongRimHit(h));
playfield.Add(new DrawableRimHitStrong(h));
else
playfield.Add(new DrawableRimHit(h));
}

View File

@ -9,6 +9,8 @@ using osu.Game.Modes.Taiko.Objects;
using System;
using System.Collections.Generic;
using System.Linq;
using osu.Game.Beatmaps.Legacy;
using osu.Game.Beatmaps.Timing;
using osu.Game.Database;
namespace osu.Game.Modes.Taiko.Beatmaps
@ -41,6 +43,7 @@ namespace osu.Game.Modes.Taiko.Beatmaps
{
return new Beatmap<TaikoHitObject>(original)
{
TimingInfo = original is LegacyBeatmap ? new LegacyTimingInfo(original.TimingInfo) : original.TimingInfo,
HitObjects = original.HitObjects.SelectMany(h => convertHitObject(h, original)).ToList()
};
}
@ -100,7 +103,6 @@ namespace osu.Game.Modes.Taiko.Beatmaps
StartTime = j,
Sample = obj.Sample,
IsStrong = strong,
VelocityMultiplier = legacy_velocity_multiplier
};
}
}
@ -111,9 +113,8 @@ namespace osu.Game.Modes.Taiko.Beatmaps
StartTime = obj.StartTime,
Sample = obj.Sample,
IsStrong = strong,
Distance = distance,
Duration = taikoDuration,
TickRate = beatmap.BeatmapInfo.Difficulty.SliderTickRate == 3 ? 3 : 4,
VelocityMultiplier = legacy_velocity_multiplier
};
}
}
@ -126,9 +127,8 @@ namespace osu.Game.Modes.Taiko.Beatmaps
StartTime = obj.StartTime,
Sample = obj.Sample,
IsStrong = strong,
EndTime = endTimeData.EndTime,
Duration = endTimeData.Duration,
RequiredHits = (int)Math.Max(1, endTimeData.Duration / 1000 * hitMultiplier),
VelocityMultiplier = legacy_velocity_multiplier
};
}
else
@ -142,7 +142,6 @@ namespace osu.Game.Modes.Taiko.Beatmaps
StartTime = obj.StartTime,
Sample = obj.Sample,
IsStrong = strong,
VelocityMultiplier = legacy_velocity_multiplier
};
}
else
@ -152,10 +151,25 @@ namespace osu.Game.Modes.Taiko.Beatmaps
StartTime = obj.StartTime,
Sample = obj.Sample,
IsStrong = strong,
VelocityMultiplier = legacy_velocity_multiplier
};
}
}
}
private class LegacyTimingInfo : TimingInfo
{
public LegacyTimingInfo(TimingInfo original)
{
if (original is LegacyTimingInfo)
ControlPoints.AddRange(original.ControlPoints);
else
{
ControlPoints.AddRange(original.ControlPoints.Select(c => c.Clone()));
foreach (var c in ControlPoints)
c.SpeedMultiplier *= legacy_velocity_multiplier;
}
}
}
}
}

View File

@ -1,26 +1,9 @@
// 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.Timing;
using osu.Game.Database;
namespace osu.Game.Modes.Taiko.Objects
{
public class BarLine
public class BarLine : TaikoHitObject
{
/// <summary>
/// The start time of the control point this bar line represents.
/// </summary>
public double StartTime;
/// <summary>
/// The time to scroll in the bar line.
/// </summary>
public double PreEmpt;
public void ApplyDefaults(TimingInfo timing, BeatmapDifficulty difficulty)
{
PreEmpt = TaikoHitObject.BASE_SCROLL_TIME / difficulty.SliderMultiplier * timing.BeatLengthAt(StartTime) * timing.SpeedMultiplierAt(StartTime) / 1000;
}
}
}

View File

@ -56,20 +56,19 @@ namespace osu.Game.Modes.Taiko.Objects.Drawables
Alpha = 0.75f
}
};
LifetimeStart = BarLine.StartTime - BarLine.ScrollTime * 2;
LifetimeEnd = BarLine.StartTime + BarLine.ScrollTime;
}
protected override void LoadComplete()
{
base.LoadComplete();
LifetimeStart = BarLine.StartTime - BarLine.PreEmpt * 2;
LifetimeEnd = BarLine.StartTime + BarLine.PreEmpt;
Delay(BarLine.StartTime - Time.Current);
FadeOut(base_fadeout_time * BarLine.PreEmpt / 1000);
FadeOut(base_fadeout_time * BarLine.ScrollTime / 1000);
}
private void updateScrollPosition(double time) => MoveToX((float)((BarLine.StartTime - time) / BarLine.PreEmpt));
private void updateScrollPosition(double time) => MoveToX((float)((BarLine.StartTime - time) / BarLine.ScrollTime));
protected override void Update()
{

View File

@ -8,7 +8,7 @@ using OpenTK;
namespace osu.Game.Modes.Taiko.Objects.Drawables
{
public class DrawableMajorBarLine : DrawableBarLine
public class DrawableBarLineMajor : DrawableBarLine
{
/// <summary>
/// The vertical offset of the triangles from the line tracker.
@ -20,7 +20,7 @@ namespace osu.Game.Modes.Taiko.Objects.Drawables
/// </summary>
private const float triangle_size = 20f;
public DrawableMajorBarLine(BarLine barLine)
public DrawableBarLineMajor(BarLine barLine)
: base(barLine)
{
Add(new Container

View File

@ -15,17 +15,13 @@ namespace osu.Game.Modes.Taiko.Objects.Drawables
public DrawableCentreHit(Hit hit)
: base(hit)
{
MainPiece.Add(new CentreHitSymbolPiece());
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
Circle.AccentColour = colours.PinkDarker;
}
protected override CirclePiece CreateCirclePiece() => new CirclePiece
{
Children = new[] { new CentreHitSymbolPiece() }
};
MainPiece.AccentColour = colours.PinkDarker;
}
}
}

View File

@ -8,24 +8,20 @@ using OpenTK.Input;
namespace osu.Game.Modes.Taiko.Objects.Drawables
{
public class DrawableStrongCentreHit : DrawableStrongHit
public class DrawableCentreHitStrong : DrawableHitStrong
{
protected override Key[] HitKeys { get; } = { Key.F, Key.J };
public DrawableStrongCentreHit(Hit hit)
public DrawableCentreHitStrong(Hit hit)
: base(hit)
{
MainPiece.Add(new CentreHitSymbolPiece());
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
Circle.AccentColour = colours.PinkDarker;
}
protected override CirclePiece CreateCirclePiece() => new StrongCirclePiece
{
Children = new[] { new CentreHitSymbolPiece() }
};
MainPiece.AccentColour = colours.PinkDarker;
}
}
}

View File

@ -3,28 +3,23 @@
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.MathUtils;
using osu.Game.Graphics;
using osu.Game.Modes.Objects.Drawables;
using osu.Game.Modes.Taiko.Judgements;
using osu.Game.Modes.Taiko.Objects.Drawables.Pieces;
using OpenTK;
using OpenTK.Graphics;
using osu.Game.Modes.Taiko.Objects.Drawables.Pieces;
namespace osu.Game.Modes.Taiko.Objects.Drawables
{
public class DrawableDrumRoll : DrawableTaikoHitObject
public class DrawableDrumRoll : DrawableTaikoHitObject<DrumRoll>
{
/// <summary>
/// Number of rolling hits required to reach the dark/final accent colour.
/// </summary>
private const int rolling_hits_for_dark_accent = 5;
private readonly DrumRoll drumRoll;
private readonly CirclePiece circle;
private Color4 accentDarkColour;
/// <summary>
@ -35,32 +30,32 @@ namespace osu.Game.Modes.Taiko.Objects.Drawables
public DrawableDrumRoll(DrumRoll drumRoll)
: base(drumRoll)
{
this.drumRoll = drumRoll;
RelativeSizeAxes = Axes.X;
Width = (float)(drumRoll.Duration / drumRoll.PreEmpt);
Add(circle = CreateCirclePiece());
circle.KiaiMode = HitObject.Kiai;
foreach (var tick in drumRoll.Ticks)
{
var newTick = new DrawableDrumRollTick(tick)
{
X = (float)((tick.StartTime - HitObject.StartTime) / drumRoll.Duration)
X = (float)((tick.StartTime - HitObject.StartTime) / HitObject.Duration)
};
newTick.OnJudgement += onTickJudgement;
AddNested(newTick);
Add(newTick);
MainPiece.Add(newTick);
}
}
protected override TaikoJudgement CreateJudgement() => new TaikoJudgement { SecondHit = HitObject.IsStrong };
protected override TaikoPiece CreateMainPiece() => new ElongatedCirclePiece(HitObject.IsStrong)
{
Length = (float)(HitObject.Duration / HitObject.ScrollTime),
PlayfieldLengthReference = () => Parent.DrawSize.X
};
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
circle.AccentColour = AccentColour = colours.YellowDark;
MainPiece.AccentColour = AccentColour = colours.YellowDark;
accentDarkColour = colours.YellowDarker;
}
@ -72,7 +67,7 @@ namespace osu.Game.Modes.Taiko.Objects.Drawables
// is further than mid point of the play field, so the time taken to scroll in should always
// be greater than the time taken to scroll out to the left of the screen.
// Thus, using PreEmpt here is enough for the drum roll to completely scroll out.
LifetimeEnd = drumRoll.EndTime + drumRoll.PreEmpt;
LifetimeEnd = HitObject.EndTime + HitObject.ScrollTime;
}
private void onTickJudgement(DrawableHitObject<TaikoHitObject, TaikoJudgement> obj)
@ -85,7 +80,7 @@ namespace osu.Game.Modes.Taiko.Objects.Drawables
rollingHits = MathHelper.Clamp(rollingHits, 0, rolling_hits_for_dark_accent);
Color4 newAccent = Interpolation.ValueAt((float)rollingHits / rolling_hits_for_dark_accent, AccentColour, accentDarkColour, 0, 1);
circle.FadeAccent(newAccent, 100);
MainPiece.FadeAccent(newAccent, 100);
}
protected override void CheckJudgement(bool userTriggered)
@ -98,10 +93,10 @@ namespace osu.Game.Modes.Taiko.Objects.Drawables
int countHit = NestedHitObjects.Count(o => o.Judgement.Result == HitResult.Hit);
if (countHit > drumRoll.RequiredGoodHits)
if (countHit > HitObject.RequiredGoodHits)
{
Judgement.Result = HitResult.Hit;
Judgement.TaikoResult = countHit >= drumRoll.RequiredGreatHits ? TaikoHitResult.Great : TaikoHitResult.Good;
Judgement.TaikoResult = countHit >= HitObject.RequiredGreatHits ? TaikoHitResult.Great : TaikoHitResult.Good;
}
else
Judgement.Result = HitResult.Miss;
@ -110,7 +105,5 @@ namespace osu.Game.Modes.Taiko.Objects.Drawables
protected override void UpdateState(ArmedState state)
{
}
protected virtual CirclePiece CreateCirclePiece() => new CirclePiece();
}
}

View File

@ -3,79 +3,37 @@
using System;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Game.Modes.Objects.Drawables;
using osu.Game.Modes.Taiko.Judgements;
using OpenTK;
using OpenTK.Graphics;
using OpenTK.Input;
using osu.Game.Modes.Taiko.Objects.Drawables.Pieces;
namespace osu.Game.Modes.Taiko.Objects.Drawables
{
public class DrawableDrumRollTick : DrawableTaikoHitObject
public class DrawableDrumRollTick : DrawableTaikoHitObject<DrumRollTick>
{
/// <summary>
/// The size of a tick.
/// </summary>
private const float tick_size = TaikoHitObject.CIRCLE_RADIUS / 2;
/// <summary>
/// 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 = tick_size / 4;
private readonly DrumRollTick tick;
private readonly CircularContainer bodyContainer;
public DrawableDrumRollTick(DrumRollTick tick)
: base(tick)
{
this.tick = tick;
Anchor = Anchor.CentreLeft;
Origin = Anchor.Centre;
RelativePositionAxes = Axes.X;
Size = new Vector2(tick_size);
Children = new[]
{
bodyContainer = new CircularContainer
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both,
Masking = true,
BorderThickness = tick_border_width,
BorderColour = Color4.White,
Children = new[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Alpha = tick.FirstTick ? 1 : 0,
AlwaysPresent = true
}
}
}
protected override TaikoPiece CreateMainPiece() => new TickPiece
{
Filled = HitObject.FirstTick
};
}
protected override TaikoJudgement CreateJudgement() => new TaikoDrumRollTickJudgement { SecondHit = tick.IsStrong };
protected override TaikoJudgement CreateJudgement() => new TaikoDrumRollTickJudgement { SecondHit = HitObject.IsStrong };
protected override void CheckJudgement(bool userTriggered)
{
if (!userTriggered)
{
if (Judgement.TimeOffset > tick.HitWindow)
if (Judgement.TimeOffset > HitObject.HitWindow)
Judgement.Result = HitResult.Miss;
return;
}
if (Math.Abs(Judgement.TimeOffset) < tick.HitWindow)
if (Math.Abs(Judgement.TimeOffset) < HitObject.HitWindow)
{
Judgement.Result = HitResult.Hit;
Judgement.TaikoResult = TaikoHitResult.Great;
@ -87,7 +45,7 @@ namespace osu.Game.Modes.Taiko.Objects.Drawables
switch (state)
{
case ArmedState.Hit:
bodyContainer.ScaleTo(0, 100, EasingTypes.OutQuint);
Content.ScaleTo(0, 100, EasingTypes.OutQuint);
break;
}
}

View File

@ -4,29 +4,19 @@
using System;
using System.Linq;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Modes.Objects.Drawables;
using osu.Game.Modes.Taiko.Judgements;
using osu.Game.Modes.Taiko.Objects.Drawables.Pieces;
using OpenTK.Input;
namespace osu.Game.Modes.Taiko.Objects.Drawables
{
public abstract class DrawableHit : DrawableTaikoHitObject
public abstract class DrawableHit : DrawableTaikoHitObject<Hit>
{
/// <summary>
/// A list of keys which can result in hits for this HitObject.
/// </summary>
protected abstract Key[] HitKeys { get; }
protected override Container<Drawable> Content => bodyContainer;
protected readonly CirclePiece Circle;
private readonly Hit hit;
private readonly Container bodyContainer;
/// <summary>
/// Whether the last key pressed is a valid hit key.
/// </summary>
@ -35,41 +25,28 @@ namespace osu.Game.Modes.Taiko.Objects.Drawables
protected DrawableHit(Hit hit)
: base(hit)
{
this.hit = hit;
AddInternal(bodyContainer = new Container
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Children = new[]
{
Circle = CreateCirclePiece()
}
});
Circle.KiaiMode = HitObject.Kiai;
}
protected override void CheckJudgement(bool userTriggered)
{
if (!userTriggered)
{
if (Judgement.TimeOffset > hit.HitWindowGood)
if (Judgement.TimeOffset > HitObject.HitWindowGood)
Judgement.Result = HitResult.Miss;
return;
}
double hitOffset = Math.Abs(Judgement.TimeOffset);
if (hitOffset > hit.HitWindowMiss)
if (hitOffset > HitObject.HitWindowMiss)
return;
if (!validKeyPressed)
Judgement.Result = HitResult.Miss;
else if (hitOffset < hit.HitWindowGood)
else if (hitOffset < HitObject.HitWindowGood)
{
Judgement.Result = HitResult.Hit;
Judgement.TaikoResult = hitOffset < hit.HitWindowGreat ? TaikoHitResult.Great : TaikoHitResult.Good;
Judgement.TaikoResult = hitOffset < HitObject.HitWindowGreat ? TaikoHitResult.Great : TaikoHitResult.Good;
}
else
Judgement.Result = HitResult.Miss;
@ -92,24 +69,26 @@ namespace osu.Game.Modes.Taiko.Objects.Drawables
switch (State)
{
case ArmedState.Idle:
Delay(hit.HitWindowMiss);
Delay(HitObject.HitWindowMiss);
break;
case ArmedState.Miss:
FadeOut(100);
break;
case ArmedState.Hit:
bodyContainer.ScaleTo(0.8f, 400, EasingTypes.OutQuad);
bodyContainer.MoveToY(-200, 250, EasingTypes.Out);
bodyContainer.Delay(250);
bodyContainer.MoveToY(0, 500, EasingTypes.In);
FadeOut(600);
const float gravity_time = 300;
const float gravity_travel_height = 200;
Content.ScaleTo(0.8f, gravity_time * 2, EasingTypes.OutQuad);
MoveToY(-gravity_travel_height, gravity_time, EasingTypes.Out);
Delay(gravity_time, true);
MoveToY(gravity_travel_height * 2, gravity_time * 2, EasingTypes.In);
break;
}
Expire();
}
protected abstract CirclePiece CreateCirclePiece();
}
}

View File

@ -7,10 +7,11 @@ using osu.Framework.Input;
using osu.Game.Modes.Objects.Drawables;
using osu.Game.Modes.Taiko.Judgements;
using OpenTK.Input;
using osu.Game.Modes.Taiko.Objects.Drawables.Pieces;
namespace osu.Game.Modes.Taiko.Objects.Drawables
{
public abstract class DrawableStrongHit : DrawableHit
public abstract class DrawableHitStrong : DrawableHit
{
/// <summary>
/// The lenience for the second key press.
@ -22,11 +23,13 @@ namespace osu.Game.Modes.Taiko.Objects.Drawables
private bool firstKeyHeld;
private Key firstHitKey;
protected DrawableStrongHit(Hit hit)
protected DrawableHitStrong(Hit hit)
: base(hit)
{
}
protected override TaikoPiece CreateMainPiece() => new CirclePiece(true);
protected override TaikoJudgement CreateJudgement() => new TaikoStrongHitJudgement();
protected override void CheckJudgement(bool userTriggered)

View File

@ -15,17 +15,13 @@ namespace osu.Game.Modes.Taiko.Objects.Drawables
public DrawableRimHit(Hit hit)
: base(hit)
{
MainPiece.Add(new RimHitSymbolPiece());
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
Circle.AccentColour = colours.BlueDarker;
}
protected override CirclePiece CreateCirclePiece() => new CirclePiece
{
Children = new[] { new RimHitSymbolPiece() }
};
MainPiece.AccentColour = colours.BlueDarker;
}
}
}

View File

@ -8,24 +8,20 @@ using OpenTK.Input;
namespace osu.Game.Modes.Taiko.Objects.Drawables
{
public class DrawableStrongRimHit : DrawableStrongHit
public class DrawableRimHitStrong : DrawableHitStrong
{
protected override Key[] HitKeys { get; } = { Key.D, Key.K };
public DrawableStrongRimHit(Hit hit)
public DrawableRimHitStrong(Hit hit)
: base(hit)
{
MainPiece.Add(new RimHitSymbolPiece());
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
Circle.AccentColour = colours.BlueDarker;
}
protected override CirclePiece CreateCirclePiece() => new StrongCirclePiece
{
Children = new[] { new RimHitSymbolPiece() }
};
MainPiece.AccentColour = colours.BlueDarker;
}
}
}

View File

@ -1,20 +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 osu.Game.Modes.Taiko.Judgements;
using osu.Game.Modes.Taiko.Objects.Drawables.Pieces;
namespace osu.Game.Modes.Taiko.Objects.Drawables
{
public class DrawableStrongDrumRoll : DrawableDrumRoll
{
public DrawableStrongDrumRoll(DrumRoll drumRoll)
: base(drumRoll)
{
}
protected override TaikoJudgement CreateJudgement() => new TaikoJudgement { SecondHit = true };
protected override CirclePiece CreateCirclePiece() => new StrongCirclePiece();
}
}

View File

@ -18,7 +18,7 @@ using OpenTK.Input;
namespace osu.Game.Modes.Taiko.Objects.Drawables
{
public class DrawableSwell : DrawableTaikoHitObject
public class DrawableSwell : DrawableTaikoHitObject<Swell>
{
/// <summary>
/// Invoked when the swell has reached the hit target, i.e. when CurrentTime >= StartTime.
@ -31,8 +31,6 @@ namespace osu.Game.Modes.Taiko.Objects.Drawables
private const float target_ring_scale = 5f;
private const float inner_ring_alpha = 0.65f;
private readonly Swell swell;
private readonly Container bodyContainer;
private readonly CircularContainer targetRing;
private readonly CircularContainer expandingRing;
@ -54,12 +52,11 @@ namespace osu.Game.Modes.Taiko.Objects.Drawables
public DrawableSwell(Swell swell)
: base(swell)
{
this.swell = swell;
Children = new Drawable[]
{
bodyContainer = new Container
{
AutoSizeAxes = Axes.Both,
Children = new Drawable[]
{
expandingRing = new CircularContainer
@ -120,6 +117,8 @@ namespace osu.Game.Modes.Taiko.Objects.Drawables
},
circlePiece = new CirclePiece
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Children = new []
{
symbol = new SwellSymbolPiece()
@ -146,18 +145,18 @@ namespace osu.Game.Modes.Taiko.Objects.Drawables
{
userHits++;
var completion = (float)userHits / swell.RequiredHits;
var completion = (float)userHits / HitObject.RequiredHits;
expandingRing.FadeTo(expandingRing.Alpha + MathHelper.Clamp(completion / 16, 0.1f, 0.6f), 50);
expandingRing.Delay(50);
expandingRing.FadeTo(completion / 8, 2000, EasingTypes.OutQuint);
expandingRing.DelayReset();
symbol.RotateTo((float)(completion * swell.Duration / 8), 4000, EasingTypes.OutQuint);
symbol.RotateTo((float)(completion * HitObject.Duration / 8), 4000, EasingTypes.OutQuint);
expandingRing.ScaleTo(1f + Math.Min(target_ring_scale - 1f, (target_ring_scale - 1f) * completion * 1.3f), 260, EasingTypes.OutQuint);
if (userHits == swell.RequiredHits)
if (userHits == HitObject.RequiredHits)
{
Judgement.Result = HitResult.Hit;
Judgement.TaikoResult = TaikoHitResult.Great;
@ -169,7 +168,7 @@ namespace osu.Game.Modes.Taiko.Objects.Drawables
return;
//TODO: THIS IS SHIT AND CAN'T EXIST POST-TAIKO WORLD CUP
if (userHits > swell.RequiredHits / 2)
if (userHits > HitObject.RequiredHits / 2)
{
Judgement.Result = HitResult.Hit;
Judgement.TaikoResult = TaikoHitResult.Good;
@ -189,7 +188,7 @@ namespace osu.Game.Modes.Taiko.Objects.Drawables
Delay(preempt, true);
Delay(Judgement.TimeOffset + swell.Duration, true);
Delay(Judgement.TimeOffset + HitObject.Duration, true);
const float out_transition_time = 300;

View File

@ -3,14 +3,18 @@
using System.Collections.Generic;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Input;
using osu.Game.Modes.Objects.Drawables;
using osu.Game.Modes.Taiko.Judgements;
using osu.Game.Modes.Taiko.Objects.Drawables.Pieces;
using OpenTK;
using OpenTK.Input;
namespace osu.Game.Modes.Taiko.Objects.Drawables
{
public abstract class DrawableTaikoHitObject : DrawableHitObject<TaikoHitObject, TaikoJudgement>
public abstract class DrawableTaikoHitObject<TaikoHitType> : DrawableHitObject<TaikoHitObject, TaikoJudgement>
where TaikoHitType : TaikoHitObject
{
/// <summary>
/// A list of keys which this hit object will accept. These are the standard Taiko keys for now.
@ -18,33 +22,52 @@ namespace osu.Game.Modes.Taiko.Objects.Drawables
/// </summary>
private readonly List<Key> validKeys = new List<Key>(new[] { Key.D, Key.F, Key.J, Key.K });
protected DrawableTaikoHitObject(TaikoHitObject hitObject)
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)
: base(hitObject)
{
HitObject = hitObject;
Anchor = Anchor.CentreLeft;
Origin = Anchor.CentreLeft;
Origin = Anchor.Custom;
AutoSizeAxes = Axes.Both;
RelativePositionAxes = Axes.X;
}
protected override void LoadComplete()
AddInternal(bodyContainer = new Container
{
LifetimeStart = HitObject.StartTime - HitObject.PreEmpt * 2;
AutoSizeAxes = Axes.Both,
Children = new[]
{
MainPiece = CreateMainPiece()
}
});
base.LoadComplete();
MainPiece.KiaiMode = HitObject.Kiai;
LifetimeStart = HitObject.StartTime - HitObject.ScrollTime * 2;
}
protected override TaikoJudgement CreateJudgement() => new TaikoJudgement();
protected virtual TaikoPiece CreateMainPiece() => new CirclePiece(HitObject.IsStrong);
/// <summary>
/// Sets the scroll position of the DrawableHitObject relative to the offset between
/// a time value and the HitObject's StartTime.
/// </summary>
/// <param name="time"></param>
protected virtual void UpdateScrollPosition(double time)
{
MoveToX((float)((HitObject.StartTime - time) / HitObject.PreEmpt));
}
protected virtual void UpdateScrollPosition(double time) => X = (float)((HitObject.StartTime - time) / HitObject.ScrollTime);
protected override void Update()
{

View File

@ -1,12 +1,10 @@
// 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.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Game.Graphics;
using osu.Game.Graphics.Backgrounds;
using OpenTK.Graphics;
@ -15,26 +13,30 @@ namespace osu.Game.Modes.Taiko.Objects.Drawables.Pieces
/// <summary>
/// A circle piece which is used uniformly through osu!taiko to visualise hitobjects.
/// <para>
/// The body of this piece will overshoot its parent by <see cref="CirclePiece.Height"/> to form
/// a rounded (_[-Width-]_) figure such that a regular "circle" is the result of a parent with Width = 0.
/// Note that this can actually be non-circle if the width is changed. See <see cref="ElongatedCirclePiece"/>
/// for a usage example.
/// </para>
/// </summary>
public class CirclePiece : Container, IHasAccentColour
public class CirclePiece : TaikoPiece
{
public const float SYMBOL_SIZE = TaikoHitObject.CIRCLE_RADIUS * 2f * 0.45f;
public const float SYMBOL_BORDER = 8;
public const float SYMBOL_INNER_SIZE = SYMBOL_SIZE - 2 * SYMBOL_BORDER;
private Color4 accentColour;
/// <summary>
/// The amount to scale up the base circle to show it as a "strong" piece.
/// </summary>
private const float strong_scale = 1.5f;
/// <summary>
/// The colour of the inner circle and outer glows.
/// </summary>
public Color4 AccentColour
public override Color4 AccentColour
{
get { return accentColour; }
get { return base.AccentColour; }
set
{
accentColour = value;
base.AccentColour = value;
background.Colour = AccentColour;
@ -42,46 +44,29 @@ namespace osu.Game.Modes.Taiko.Objects.Drawables.Pieces
}
}
private bool kiaiMode;
/// <summary>
/// Whether Kiai mode effects are enabled for this circle piece.
/// </summary>
public bool KiaiMode
public override bool KiaiMode
{
get { return kiaiMode; }
get { return base.KiaiMode; }
set
{
kiaiMode = value;
base.KiaiMode = value;
resetEdgeEffects();
}
}
public override Anchor Origin
{
get { return Anchor.CentreLeft; }
set { throw new InvalidOperationException($"{nameof(CirclePiece)} must always use CentreLeft origin."); }
}
protected override Container<Drawable> Content => content;
protected override Container<Drawable> Content => SymbolContainer;
protected readonly Container SymbolContainer;
private readonly Container content;
private readonly Container background;
private readonly Container innerLayer;
public CirclePiece()
public CirclePiece(bool isStrong = false)
{
RelativeSizeAxes = Axes.X;
Height = TaikoHitObject.CIRCLE_RADIUS * 2;
// The "inner layer" is the body of the CirclePiece that overshoots it by Height/2 px on both sides
AddInternal(innerLayer = new Container
{
Name = "Inner Layer",
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Y,
Children = new Drawable[]
AddInternal(new Drawable[]
{
background = new CircularContainer
{
@ -129,20 +114,30 @@ namespace osu.Game.Modes.Taiko.Objects.Drawables.Pieces
}
}
},
SymbolContainer = new Container
content = new Container
{
Name = "Symbol",
RelativeSizeAxes = Axes.Both,
Name = "Content",
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
}
}
});
if (isStrong)
{
Size *= strong_scale;
//default for symbols etc.
Content.Scale *= strong_scale;
}
}
protected override void Update()
{
// Add the overshoot to compensate for corner radius
innerLayer.Width = DrawWidth + DrawHeight;
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 void resetEdgeEffects()

View File

@ -0,0 +1,41 @@
// 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.Graphics.Primitives;
using osu.Game.Modes.Taiko.UI;
namespace osu.Game.Modes.Taiko.Objects.Drawables.Pieces
{
public class ElongatedCirclePiece : CirclePiece
{
/// <summary>
/// As we are being used to define the absolute size of hits, we need to be given a relative reference of our containing <see cref="TaikoPlayfield"/>.
/// </summary>
public Func<float> PlayfieldLengthReference;
/// <summary>
/// The length of this piece as a multiple of the value returned by <see cref="PlayfieldLengthReference"/>
/// </summary>
public float Length;
public ElongatedCirclePiece(bool isStrong = false) : base(isStrong)
{
}
protected override void Update()
{
base.Update();
var padding = Content.DrawHeight * Content.Width / 2;
Content.Padding = new MarginPadding
{
Left = padding,
Right = padding,
};
Width = (PlayfieldLengthReference?.Invoke() ?? 0) * Length + DrawHeight;
}
}
}

View File

@ -1,25 +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 OpenTK;
namespace osu.Game.Modes.Taiko.Objects.Drawables.Pieces
{
/// <summary>
/// A type of circle piece which is drawn at a higher scale to represent a "strong" piece.
/// </summary>
public class StrongCirclePiece : CirclePiece
{
/// <summary>
/// The amount to scale up the base circle to show it as a "strong" piece.
/// </summary>
private const float strong_scale = 1.5f;
public StrongCirclePiece()
{
SymbolContainer.Scale = new Vector2(strong_scale);
}
public override Vector2 Size => new Vector2(base.Size.X, base.Size.Y * strong_scale);
}
}

View File

@ -0,0 +1,45 @@
// 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.Containers;
using osu.Game.Graphics;
using OpenTK;
using OpenTK.Graphics;
namespace osu.Game.Modes.Taiko.Objects.Drawables.Pieces
{
public class TaikoPiece : Container, IHasAccentColour
{
private Color4 accentColour;
/// <summary>
/// The colour of the inner circle and outer glows.
/// </summary>
public virtual Color4 AccentColour
{
get { return accentColour; }
set
{
accentColour = value;
}
}
private bool kiaiMode;
/// <summary>
/// Whether Kiai mode effects are enabled for this circle piece.
/// </summary>
public virtual bool KiaiMode
{
get { return kiaiMode; }
set
{
kiaiMode = value;
}
}
public TaikoPiece()
{
//just a default
Size = new Vector2(TaikoHitObject.CIRCLE_RADIUS * 2);
}
}
}

View File

@ -0,0 +1,60 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using OpenTK;
using OpenTK.Graphics;
namespace osu.Game.Modes.Taiko.Objects.Drawables.Pieces
{
public class TickPiece : TaikoPiece
{
/// <summary>
/// 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.CIRCLE_RADIUS / 2 / 4;
/// <summary>
/// The size of a tick.
/// </summary>
private const float tick_size = TaikoHitObject.CIRCLE_RADIUS / 2;
private bool filled;
public bool Filled
{
get { return filled; }
set
{
filled = value;
fillBox.Alpha = filled ? 1 : 0;
}
}
private readonly Box fillBox;
public TickPiece()
{
Size = new Vector2(tick_size);
Add(new CircularContainer
{
RelativeSizeAxes = Axes.Both,
Masking = true,
BorderThickness = tick_border_width,
BorderColour = Color4.White,
Children = new[]
{
fillBox = new Box
{
RelativeSizeAxes = Axes.Both,
Alpha = 0,
AlwaysPresent = true
}
}
});
}
}
}

View File

@ -11,26 +11,16 @@ using osu.Game.Database;
namespace osu.Game.Modes.Taiko.Objects
{
public class DrumRoll : TaikoHitObject, IHasDistance
public class DrumRoll : TaikoHitObject, IHasEndTime
{
/// <summary>
/// Drum roll distance that results in a duration of 1 speed-adjusted beat length.
/// </summary>
private const float base_distance = 100;
public double EndTime => StartTime + Distance / Velocity;
public double EndTime => StartTime + Duration;
public double Duration => EndTime - StartTime;
/// <summary>
/// Raw length of the drum roll in positional length units.
/// </summary>
public double Distance { get; set; }
/// <summary>
/// Velocity of the drum roll in positional length units per millisecond.
/// </summary>
public double Velocity { get; protected set; } = 5;
public double Duration { get; set; }
/// <summary>
/// Numer of ticks per beat length.
@ -69,9 +59,6 @@ namespace osu.Game.Modes.Taiko.Objects
{
base.ApplyDefaults(timing, difficulty);
double speedAdjutedBeatLength = timing.SpeedMultiplierAt(StartTime) * timing.BeatLengthAt(StartTime);
Velocity = base_distance * difficulty.SliderMultiplier / speedAdjutedBeatLength * VelocityMultiplier;
tickSpacing = timing.BeatLengthAt(StartTime) / TickRate;
RequiredGoodHits = TotalTicks * Math.Min(0.15, 0.05 + 0.10 / 6 * difficulty.OverallDifficulty);
@ -86,12 +73,12 @@ namespace osu.Game.Modes.Taiko.Objects
return ret;
bool first = true;
for (double t = StartTime; t < EndTime + (int)tickSpacing; t += tickSpacing)
for (double t = StartTime; t < EndTime + tickSpacing / 2; t += tickSpacing)
{
ret.Add(new DrumRollTick
{
FirstTick = first,
PreEmpt = PreEmpt,
ScrollTime = ScrollTime,
TickSpacing = tickSpacing,
StartTime = t,
IsStrong = IsStrong,

View File

@ -7,9 +7,9 @@ namespace osu.Game.Modes.Taiko.Objects
{
public class Swell : TaikoHitObject, IHasEndTime
{
public double EndTime { get; set; }
public double EndTime => StartTime + Duration;
public double Duration => EndTime - StartTime;
public double Duration { get; set; }
/// <summary>
/// The number of hits required to complete the swell successfully.

View File

@ -15,19 +15,14 @@ namespace osu.Game.Modes.Taiko.Objects
public const float CIRCLE_RADIUS = 42f;
/// <summary>
/// Time (in milliseconds) to scroll in the hit object with a speed-adjusted beat length of 1 second.
/// The time taken from the initial (off-screen) spawn position to the centre of the hit target for a <see cref="ControlPoint.BeatLength"/> of 1000ms.
/// </summary>
public const double BASE_SCROLL_TIME = 6000;
private const double scroll_time = 6000;
/// <summary>
/// The velocity multiplier applied to this hit object.
/// Our adjusted <see cref="scroll_time"/> taking into consideration local <see cref="ControlPoint.BeatLength"/> and other speed multipliers.
/// </summary>
public float VelocityMultiplier = 1;
/// <summary>
/// The time to scroll in the HitObject.
/// </summary>
public double PreEmpt;
public double ScrollTime;
/// <summary>
/// Whether this HitObject is a "strong" type.
@ -44,7 +39,7 @@ namespace osu.Game.Modes.Taiko.Objects
{
base.ApplyDefaults(timing, difficulty);
PreEmpt = BASE_SCROLL_TIME / difficulty.SliderMultiplier * timing.BeatLengthAt(StartTime) * timing.SpeedMultiplierAt(StartTime) / VelocityMultiplier / 1000;
ScrollTime = scroll_time * (timing.BeatLengthAt(StartTime) / 1000) / (difficulty.SliderMultiplier * timing.SpeedMultiplierAt(StartTime));
ControlPoint overridePoint;
Kiai = timing.TimingPointAt(StartTime, out overridePoint).KiaiMode;

View File

@ -128,17 +128,36 @@ namespace osu.Game.Modes.Taiko.UI
return false;
Drawable target = null;
Drawable back = null;
if (args.Key == CentreKey)
{
target = centreHit;
back = centre;
}
else if (args.Key == RimKey)
{
target = rimHit;
back = rim;
}
if (target != null)
{
target.FadeTo(Math.Min(target.Alpha + 0.4f, 1), 40, EasingTypes.OutQuint);
target.Delay(40);
target.FadeOut(1000, EasingTypes.OutQuint);
const float scale_amount = 0.05f;
const float alpha_amount = 0.5f;
const float down_time = 40;
const float up_time = 1000;
back.ScaleTo(target.Scale.X - scale_amount, down_time, EasingTypes.OutQuint);
back.Delay(down_time);
back.ScaleTo(1, up_time, EasingTypes.OutQuint);
target.ScaleTo(target.Scale.X - scale_amount, down_time, EasingTypes.OutQuint);
target.FadeTo(Math.Min(target.Alpha + alpha_amount, 1), down_time, EasingTypes.OutQuint);
target.Delay(down_time);
target.ScaleTo(1, up_time, EasingTypes.OutQuint);
target.FadeOut(up_time, EasingTypes.OutQuint);
}
return false;

View File

@ -79,7 +79,7 @@ namespace osu.Game.Modes.Taiko.UI
barLine.ApplyDefaults(Beatmap.TimingInfo, Beatmap.BeatmapInfo.Difficulty);
taikoPlayfield.AddBarLine(isMajor ? new DrawableMajorBarLine(barLine) : new DrawableBarLine(barLine));
taikoPlayfield.AddBarLine(isMajor ? new DrawableBarLineMajor(barLine) : new DrawableBarLine(barLine));
currentBeat++;
}
@ -118,7 +118,7 @@ namespace osu.Game.Modes.Taiko.UI
if (centreHit != null)
{
if (h.IsStrong)
return new DrawableStrongCentreHit(centreHit);
return new DrawableCentreHitStrong(centreHit);
return new DrawableCentreHit(centreHit);
}
@ -126,15 +126,13 @@ namespace osu.Game.Modes.Taiko.UI
if (rimHit != null)
{
if (h.IsStrong)
return new DrawableStrongRimHit(rimHit);
return new DrawableRimHitStrong(rimHit);
return new DrawableRimHit(rimHit);
}
var drumRoll = h as DrumRoll;
if (drumRoll != null)
{
if (h.IsStrong)
return new DrawableStrongDrumRoll(drumRoll);
return new DrawableDrumRoll(drumRoll);
}

View File

@ -85,7 +85,7 @@ namespace osu.Game.Modes.Taiko.UI
{
new Container
{
Padding = new MarginPadding { Left = hit_target_offset },
X = hit_target_offset,
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
{

View File

@ -55,24 +55,25 @@
<Compile Include="Judgements\TaikoHitResult.cs" />
<Compile Include="Objects\BarLine.cs" />
<Compile Include="Objects\Drawables\DrawableBarLine.cs" />
<Compile Include="Objects\Drawables\DrawableMajorBarLine.cs" />
<Compile Include="Objects\Drawables\DrawableBarLineMajor.cs" />
<Compile Include="Objects\CentreHit.cs" />
<Compile Include="Objects\Drawables\DrawableRimHit.cs" />
<Compile Include="Objects\Drawables\DrawableStrongRimHit.cs" />
<Compile Include="Objects\Drawables\DrawableRimHitStrong.cs" />
<Compile Include="Objects\Drawables\DrawableCentreHit.cs" />
<Compile Include="Objects\Drawables\DrawableHit.cs" />
<Compile Include="Objects\Drawables\DrawableStrongDrumRoll.cs" />
<Compile Include="Objects\Drawables\DrawableStrongCentreHit.cs" />
<Compile Include="Objects\Drawables\DrawableStrongHit.cs" />
<Compile Include="Objects\Drawables\DrawableCentreHitStrong.cs" />
<Compile Include="Objects\Drawables\DrawableHitStrong.cs" />
<Compile Include="Objects\Drawables\Pieces\ElongatedCirclePiece.cs" />
<Compile Include="Objects\Drawables\Pieces\CentreHitSymbolPiece.cs" />
<Compile Include="Objects\Drawables\DrawableDrumRoll.cs" />
<Compile Include="Objects\Drawables\DrawableDrumRollTick.cs" />
<Compile Include="Objects\Drawables\DrawableSwell.cs" />
<Compile Include="Objects\Drawables\DrawableTaikoHitObject.cs" />
<Compile Include="Objects\Drawables\Pieces\RimHitSymbolPiece.cs" />
<Compile Include="Objects\Drawables\Pieces\StrongCirclePiece.cs" />
<Compile Include="Objects\Drawables\Pieces\CirclePiece.cs" />
<Compile Include="Objects\Drawables\Pieces\SwellSymbolPiece.cs" />
<Compile Include="Objects\Drawables\Pieces\TaikoPiece.cs" />
<Compile Include="Objects\Drawables\Pieces\TickPiece.cs" />
<Compile Include="Objects\DrumRoll.cs" />
<Compile Include="Objects\DrumRollTick.cs" />
<Compile Include="Objects\Hit.cs" />

View File

@ -215,7 +215,7 @@ namespace osu.Game.Beatmaps.Formats
{
Time = double.Parse(split[0].Trim(), NumberFormatInfo.InvariantInfo),
BeatLength = beatLength > 0 ? beatLength : 0,
VelocityAdjustment = beatLength < 0 ? -beatLength / 100.0 : 1,
SpeedMultiplier = beatLength < 0 ? -beatLength / 100.0 : 1,
TimingChange = split.Length <= 6 || split[6][0] == '1',
KiaiMode = (effectFlags & 1) > 0,
OmitFirstBarLine = (effectFlags & 8) > 0,

View File

@ -5,18 +5,14 @@ namespace osu.Game.Beatmaps.Timing
{
public class ControlPoint
{
public static ControlPoint Default = new ControlPoint
{
BeatLength = 500,
TimingChange = true,
};
public TimeSignatures TimeSignature;
public double Time;
public double BeatLength;
public double VelocityAdjustment;
public bool TimingChange;
public double BeatLength = 500;
public double SpeedMultiplier = 1;
public bool TimingChange = true;
public bool KiaiMode;
public bool OmitFirstBarLine;
public ControlPoint Clone() => (ControlPoint)MemberwiseClone();
}
}

View File

@ -10,8 +10,8 @@ namespace osu.Game.Beatmaps.Timing
{
public readonly List<ControlPoint> ControlPoints = new List<ControlPoint>();
public double BPMMaximum => 60000 / (ControlPoints?.Where(c => c.BeatLength != 0).OrderBy(c => c.BeatLength).FirstOrDefault() ?? ControlPoint.Default).BeatLength;
public double BPMMinimum => 60000 / (ControlPoints?.Where(c => c.BeatLength != 0).OrderByDescending(c => c.BeatLength).FirstOrDefault() ?? ControlPoint.Default).BeatLength;
public double BPMMaximum => 60000 / (ControlPoints?.Where(c => c.BeatLength != 0).OrderBy(c => c.BeatLength).FirstOrDefault() ?? new ControlPoint()).BeatLength;
public double BPMMinimum => 60000 / (ControlPoints?.Where(c => c.BeatLength != 0).OrderByDescending(c => c.BeatLength).FirstOrDefault() ?? new ControlPoint()).BeatLength;
public double BPMMode => BPMAt(ControlPoints.Where(c => c.BeatLength != 0).GroupBy(c => c.BeatLength).OrderByDescending(grp => grp.Count()).First().First().Time);
public double BPMAt(double time)
@ -29,7 +29,7 @@ namespace osu.Game.Beatmaps.Timing
ControlPoint overridePoint;
ControlPoint timingPoint = TimingPointAt(time, out overridePoint);
return overridePoint?.VelocityAdjustment ?? timingPoint?.VelocityAdjustment ?? 1;
return overridePoint?.SpeedMultiplier ?? timingPoint?.SpeedMultiplier ?? 1;
}
/// <summary>
@ -74,7 +74,7 @@ namespace osu.Game.Beatmaps.Timing
else break;
}
return timingPoint ?? ControlPoint.Default;
return timingPoint ?? new ControlPoint();
}
}
}

View File

@ -9,12 +9,12 @@ namespace osu.Game.Database
{
[PrimaryKey, AutoIncrement]
public int ID { get; set; }
public float DrainRate { get; set; }
public float CircleSize { get; set; }
public float OverallDifficulty { get; set; }
public float ApproachRate { get; set; }
public float SliderMultiplier { get; set; }
public float SliderTickRate { get; set; }
public float DrainRate { get; set; } = 5;
public float CircleSize { get; set; } = 5;
public float OverallDifficulty { get; set; } = 5;
public float ApproachRate { get; set; } = 5;
public float SliderMultiplier { get; set; } = 1;
public float SliderTickRate { get; set; } = 1;
/// <summary>
/// Maps a difficulty value [0, 10] to a two-piece linear range of values.

View File

@ -15,6 +15,7 @@ namespace osu.Game.Database
[PrimaryKey, AutoIncrement]
public int ID { get; set; }
//TODO: should be in database
public int BeatmapVersion;
public int? OnlineBeatmapID { get; set; }

View File

@ -56,13 +56,17 @@ namespace osu.Game.Modes.Objects.Drawables
Sample?.Play();
}
protected override void LoadComplete()
[BackgroundDependencyLoader]
private void load()
{
base.LoadComplete();
//we may be setting a custom judgement in test cases or what not.
if (Judgement == null)
Judgement = CreateJudgement();
}
protected override void LoadComplete()
{
base.LoadComplete();
//force application of the state that was set before we loaded.
UpdateState(State);