diff --git a/osu.Game.Rulesets.Mania/Judgements/HoldNoteTailJudgement.cs b/osu.Game.Rulesets.Mania/Judgements/HoldNoteTailJudgement.cs
index df2f7e9e63..d5cf57a5da 100644
--- a/osu.Game.Rulesets.Mania/Judgements/HoldNoteTailJudgement.cs
+++ b/osu.Game.Rulesets.Mania/Judgements/HoldNoteTailJudgement.cs
@@ -9,5 +9,29 @@ namespace osu.Game.Rulesets.Mania.Judgements
/// Whether the hold note has been released too early and shouldn't give full score for the release.
///
public bool HasBroken;
+
+ public override int NumericResultForScore(ManiaHitResult result)
+ {
+ switch (result)
+ {
+ default:
+ return base.NumericResultForScore(result);
+ case ManiaHitResult.Great:
+ case ManiaHitResult.Perfect:
+ return base.NumericResultForScore(HasBroken ? ManiaHitResult.Good : result);
+ }
+ }
+
+ public override int NumericResultForAccuracy(ManiaHitResult result)
+ {
+ switch (result)
+ {
+ default:
+ return base.NumericResultForAccuracy(result);
+ case ManiaHitResult.Great:
+ case ManiaHitResult.Perfect:
+ return base.NumericResultForAccuracy(HasBroken ? ManiaHitResult.Good : result);
+ }
+ }
}
}
\ No newline at end of file
diff --git a/osu.Game.Rulesets.Mania/Judgements/HoldNoteTickJudgement.cs b/osu.Game.Rulesets.Mania/Judgements/HoldNoteTickJudgement.cs
index bead455c13..852f97b3f2 100644
--- a/osu.Game.Rulesets.Mania/Judgements/HoldNoteTickJudgement.cs
+++ b/osu.Game.Rulesets.Mania/Judgements/HoldNoteTickJudgement.cs
@@ -5,5 +5,9 @@ namespace osu.Game.Rulesets.Mania.Judgements
{
public class HoldNoteTickJudgement : ManiaJudgement
{
+ public override bool AffectsCombo => false;
+
+ public override int NumericResultForScore(ManiaHitResult result) => 20;
+ public override int NumericResultForAccuracy(ManiaHitResult result) => 0; // Don't count ticks into accuracy
}
}
\ No newline at end of file
diff --git a/osu.Game.Rulesets.Mania/Judgements/ManiaJudgement.cs b/osu.Game.Rulesets.Mania/Judgements/ManiaJudgement.cs
index 6e69da3da7..33083ca0f5 100644
--- a/osu.Game.Rulesets.Mania/Judgements/ManiaJudgement.cs
+++ b/osu.Game.Rulesets.Mania/Judgements/ManiaJudgement.cs
@@ -2,11 +2,37 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Rulesets.Judgements;
+using osu.Game.Rulesets.Objects.Drawables;
namespace osu.Game.Rulesets.Mania.Judgements
{
public class ManiaJudgement : Judgement
{
+ ///
+ /// The maximum possible hit result.
+ ///
+ public const ManiaHitResult MAX_HIT_RESULT = ManiaHitResult.Perfect;
+
+ ///
+ /// The result value for the combo portion of the score.
+ ///
+ public int ResultValueForScore => Result == HitResult.Miss ? 0 : NumericResultForScore(ManiaResult);
+
+ ///
+ /// The result value for the accuracy portion of the score.
+ ///
+ public int ResultValueForAccuracy => Result == HitResult.Miss ? 0 : NumericResultForAccuracy(ManiaResult);
+
+ ///
+ /// The maximum result value for the combo portion of the score.
+ ///
+ public int MaxResultValueForScore => NumericResultForScore(MAX_HIT_RESULT);
+
+ ///
+ /// The maximum result value for the accuracy portion of the score.
+ ///
+ public int MaxResultValueForAccuracy => NumericResultForAccuracy(MAX_HIT_RESULT);
+
public override string ResultString => string.Empty;
public override string MaxResultString => string.Empty;
@@ -15,5 +41,42 @@ namespace osu.Game.Rulesets.Mania.Judgements
/// The hit result.
///
public ManiaHitResult ManiaResult;
+
+ public virtual int NumericResultForScore(ManiaHitResult result)
+ {
+ switch (result)
+ {
+ default:
+ return 0;
+ case ManiaHitResult.Bad:
+ return 50;
+ case ManiaHitResult.Ok:
+ return 100;
+ case ManiaHitResult.Good:
+ return 200;
+ case ManiaHitResult.Great:
+ case ManiaHitResult.Perfect:
+ return 300;
+ }
+ }
+
+ public virtual int NumericResultForAccuracy(ManiaHitResult result)
+ {
+ switch (result)
+ {
+ default:
+ return 0;
+ case ManiaHitResult.Bad:
+ return 50;
+ case ManiaHitResult.Ok:
+ return 100;
+ case ManiaHitResult.Good:
+ return 200;
+ case ManiaHitResult.Great:
+ return 300;
+ case ManiaHitResult.Perfect:
+ return 305;
+ }
+ }
}
}
diff --git a/osu.Game.Rulesets.Mania/Objects/BarLine.cs b/osu.Game.Rulesets.Mania/Objects/BarLine.cs
new file mode 100644
index 0000000000..76a3d3920d
--- /dev/null
+++ b/osu.Game.Rulesets.Mania/Objects/BarLine.cs
@@ -0,0 +1,21 @@
+// Copyright (c) 2007-2017 ppy Pty Ltd .
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using osu.Game.Beatmaps.ControlPoints;
+
+namespace osu.Game.Rulesets.Mania.Objects
+{
+ public class BarLine : ManiaHitObject
+ {
+ ///
+ /// The control point which this bar line is part of.
+ ///
+ public TimingControlPoint ControlPoint;
+
+ ///
+ /// The index of the beat which this bar line represents within the control point.
+ /// This is a "major" bar line if % == 0.
+ ///
+ public int BeatIndex;
+ }
+}
\ No newline at end of file
diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableBarLine.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableBarLine.cs
new file mode 100644
index 0000000000..0b4d8b2d4e
--- /dev/null
+++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableBarLine.cs
@@ -0,0 +1,74 @@
+// Copyright (c) 2007-2017 ppy Pty Ltd .
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using OpenTK;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Sprites;
+using osu.Game.Rulesets.Objects.Drawables;
+
+namespace osu.Game.Rulesets.Mania.Objects.Drawables
+{
+ ///
+ /// Visualises a . Although this derives DrawableManiaHitObject,
+ /// this does not handle input/sound like a normal hit object.
+ ///
+ public class DrawableBarLine : DrawableManiaHitObject
+ {
+ ///
+ /// Height of major bar line triangles.
+ ///
+ private const float triangle_height = 12;
+
+ ///
+ /// Offset of the major bar line triangles from the sides of the bar line.
+ ///
+ private const float triangle_offset = 9;
+
+ public DrawableBarLine(BarLine barLine)
+ : base(barLine)
+ {
+ RelativeSizeAxes = Axes.X;
+ Height = 1;
+
+ Add(new Box
+ {
+ Name = "Bar line",
+ Anchor = Anchor.BottomCentre,
+ Origin = Anchor.BottomCentre,
+ RelativeSizeAxes = Axes.Both,
+ });
+
+ bool isMajor = barLine.BeatIndex % (int)barLine.ControlPoint.TimeSignature == 0;
+
+ if (isMajor)
+ {
+ Add(new EquilateralTriangle
+ {
+ Name = "Left triangle",
+ Anchor = Anchor.BottomLeft,
+ Origin = Anchor.TopCentre,
+ Size = new Vector2(triangle_height),
+ X = -triangle_offset,
+ Rotation = 90
+ });
+
+ Add(new EquilateralTriangle
+ {
+ Name = "Right triangle",
+ Anchor = Anchor.BottomRight,
+ Origin = Anchor.TopCentre,
+ Size = new Vector2(triangle_height),
+ X = triangle_offset,
+ Rotation = -90
+ });
+ }
+
+ if (!isMajor && barLine.BeatIndex % 2 == 1)
+ Alpha = 0.2f;
+ }
+
+ protected override void UpdateState(ArmedState state)
+ {
+ }
+ }
+}
\ No newline at end of file
diff --git a/osu.Game.Rulesets.Mania/Objects/HoldNote.cs b/osu.Game.Rulesets.Mania/Objects/HoldNote.cs
index 8cd757734c..fa32d46a88 100644
--- a/osu.Game.Rulesets.Mania/Objects/HoldNote.cs
+++ b/osu.Game.Rulesets.Mania/Objects/HoldNote.cs
@@ -1,4 +1,4 @@
-// Copyright (c) 2007-2017 ppy Pty Ltd .
+// Copyright (c) 2007-2017 ppy Pty Ltd .
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Collections.Generic;
diff --git a/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs b/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs
index 7a9572a0c7..798d4b8c5b 100644
--- a/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs
+++ b/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs
@@ -1,8 +1,13 @@
// Copyright (c) 2007-2017 ppy Pty Ltd .
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+using System;
+using System.Linq;
+using osu.Game.Beatmaps;
+using osu.Game.Database;
using osu.Game.Rulesets.Mania.Judgements;
using osu.Game.Rulesets.Mania.Objects;
+using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.UI;
@@ -10,6 +15,143 @@ namespace osu.Game.Rulesets.Mania.Scoring
{
internal class ManiaScoreProcessor : ScoreProcessor
{
+ ///
+ /// The maximum score achievable.
+ /// Does _not_ include bonus score - for bonus score see .
+ ///
+ private const int max_score = 1000000;
+
+ ///
+ /// The amount of the score attributed to combo.
+ ///
+ private const double combo_portion_max = max_score * 0.2;
+
+ ///
+ /// The amount of the score attributed to accuracy.
+ ///
+ private const double accuracy_portion_max = max_score * 0.8;
+
+ ///
+ /// The factor used to determine relevance of combos.
+ ///
+ private const double combo_base = 4;
+
+ ///
+ /// The combo value at which hit objects result in the max score possible.
+ ///
+ private const int combo_relevance_cap = 400;
+
+ ///
+ /// The hit HP multiplier at OD = 0.
+ ///
+ private const double hp_multiplier_min = 0.75;
+
+ ///
+ /// The hit HP multiplier at OD = 0.
+ ///
+ private const double hp_multiplier_mid = 0.85;
+
+ ///
+ /// The hit HP multiplier at OD = 0.
+ ///
+ private const double hp_multiplier_max = 1;
+
+ ///
+ /// The default BAD hit HP increase.
+ ///
+ private const double hp_increase_bad = 0.005;
+
+ ///
+ /// The default OK hit HP increase.
+ ///
+ private const double hp_increase_ok = 0.010;
+
+ ///
+ /// The default GOOD hit HP increase.
+ ///
+ private const double hp_increase_good = 0.035;
+
+ ///
+ /// The default tick hit HP increase.
+ ///
+ private const double hp_increase_tick = 0.040;
+
+ ///
+ /// The default GREAT hit HP increase.
+ ///
+ private const double hp_increase_great = 0.055;
+
+ ///
+ /// The default PERFECT hit HP increase.
+ ///
+ private const double hp_increase_perfect = 0.065;
+
+ ///
+ /// The MISS HP multiplier at OD = 0.
+ ///
+ private const double hp_multiplier_miss_min = 0.5;
+
+ ///
+ /// The MISS HP multiplier at OD = 5.
+ ///
+ private const double hp_multiplier_miss_mid = 0.75;
+
+ ///
+ /// The MISS HP multiplier at OD = 10.
+ ///
+ private const double hp_multiplier_miss_max = 1;
+
+ ///
+ /// The default MISS HP increase.
+ ///
+ private const double hp_increase_miss = -0.125;
+
+ ///
+ /// The MISS HP multiplier. This is multiplied to the miss hp increase.
+ ///
+ private double hpMissMultiplier = 1;
+
+ ///
+ /// The HIT HP multiplier. This is multiplied to hit hp increases.
+ ///
+ private double hpMultiplier = 1;
+
+ ///
+ /// The cumulative combo portion of the score.
+ ///
+ private double comboScore => combo_portion_max * comboPortion / maxComboPortion;
+
+ ///
+ /// The cumulative accuracy portion of the score.
+ ///
+ private double accuracyScore => accuracy_portion_max * Math.Pow(Accuracy, 4) * totalHits / maxTotalHits;
+
+ ///
+ /// The cumulative bonus score.
+ /// This is added on top of , thus the total score can exceed .
+ ///
+ private double bonusScore;
+
+ ///
+ /// The achieved by a perfect playthrough.
+ ///
+ private double maxComboPortion;
+
+ ///
+ /// The portion of the score dedicated to combo.
+ ///
+ private double comboPortion;
+
+ ///
+ /// The achieved by a perfect playthrough.
+ ///
+ private int maxTotalHits;
+
+ ///
+ /// The total hits.
+ ///
+ private int totalHits;
+
public ManiaScoreProcessor()
{
}
@@ -19,8 +161,124 @@ namespace osu.Game.Rulesets.Mania.Scoring
{
}
+ protected override void ComputeTargets(Beatmap beatmap)
+ {
+ BeatmapDifficulty difficulty = beatmap.BeatmapInfo.Difficulty;
+ hpMultiplier = BeatmapDifficulty.DifficultyRange(difficulty.DrainRate, hp_multiplier_min, hp_multiplier_mid, hp_multiplier_max);
+ hpMissMultiplier = BeatmapDifficulty.DifficultyRange(difficulty.DrainRate, hp_multiplier_miss_min, hp_multiplier_miss_mid, hp_multiplier_miss_max);
+
+ while (true)
+ {
+ foreach (var obj in beatmap.HitObjects)
+ {
+ var holdNote = obj as HoldNote;
+
+ if (obj is Note)
+ {
+ AddJudgement(new ManiaJudgement
+ {
+ Result = HitResult.Hit,
+ ManiaResult = ManiaHitResult.Perfect
+ });
+ }
+ else if (holdNote != null)
+ {
+ // Head
+ AddJudgement(new ManiaJudgement
+ {
+ Result = HitResult.Hit,
+ ManiaResult = ManiaJudgement.MAX_HIT_RESULT
+ });
+
+ // Ticks
+ int tickCount = holdNote.Ticks.Count();
+ for (int i = 0; i < tickCount; i++)
+ {
+ AddJudgement(new HoldNoteTickJudgement
+ {
+ Result = HitResult.Hit,
+ ManiaResult = ManiaJudgement.MAX_HIT_RESULT,
+ });
+ }
+
+ AddJudgement(new HoldNoteTailJudgement
+ {
+ Result = HitResult.Hit,
+ ManiaResult = ManiaJudgement.MAX_HIT_RESULT
+ });
+ }
+ }
+
+ if (!HasFailed)
+ break;
+
+ hpMultiplier *= 1.01;
+ hpMissMultiplier *= 0.98;
+
+ Reset();
+ }
+
+ maxTotalHits = totalHits;
+ maxComboPortion = comboPortion;
+ }
+
protected override void OnNewJudgement(ManiaJudgement judgement)
{
+ bool isTick = judgement is HoldNoteTickJudgement;
+
+ if (!isTick)
+ totalHits++;
+
+ switch (judgement.Result)
+ {
+ case HitResult.Miss:
+ Health.Value += hpMissMultiplier * hp_increase_miss;
+ break;
+ case HitResult.Hit:
+ if (isTick)
+ {
+ Health.Value += hpMultiplier * hp_increase_tick;
+ bonusScore += judgement.ResultValueForScore;
+ }
+ else
+ {
+ switch (judgement.ManiaResult)
+ {
+ case ManiaHitResult.Bad:
+ Health.Value += hpMultiplier * hp_increase_bad;
+ break;
+ case ManiaHitResult.Ok:
+ Health.Value += hpMultiplier * hp_increase_ok;
+ break;
+ case ManiaHitResult.Good:
+ Health.Value += hpMultiplier * hp_increase_good;
+ break;
+ case ManiaHitResult.Great:
+ Health.Value += hpMultiplier * hp_increase_great;
+ break;
+ case ManiaHitResult.Perfect:
+ Health.Value += hpMultiplier * hp_increase_perfect;
+ break;
+ }
+
+ // A factor that is applied to make higher combos more relevant
+ double comboRelevance = Math.Min(Math.Max(0.5, Math.Log(Combo.Value, combo_base)), Math.Log(combo_relevance_cap, combo_base));
+ comboPortion += judgement.ResultValueForScore * comboRelevance;
+ }
+ break;
+ }
+
+ int scoreForAccuracy = 0;
+ int maxScoreForAccuracy = 0;
+
+ foreach (var j in Judgements)
+ {
+ scoreForAccuracy += j.ResultValueForAccuracy;
+ maxScoreForAccuracy += j.MaxResultValueForAccuracy;
+ }
+
+ Accuracy.Value = (double)scoreForAccuracy / maxScoreForAccuracy;
+ TotalScore.Value = comboScore + accuracyScore + bonusScore;
}
protected override void Reset()
@@ -28,6 +286,10 @@ namespace osu.Game.Rulesets.Mania.Scoring
base.Reset();
Health.Value = 1;
+
+ bonusScore = 0;
+ comboPortion = 0;
+ totalHits = 0;
}
}
}
diff --git a/osu.Game.Rulesets.Mania/UI/ManiaHitRenderer.cs b/osu.Game.Rulesets.Mania/UI/ManiaHitRenderer.cs
index 95b7979e43..57477147d5 100644
--- a/osu.Game.Rulesets.Mania/UI/ManiaHitRenderer.cs
+++ b/osu.Game.Rulesets.Mania/UI/ManiaHitRenderer.cs
@@ -6,9 +6,11 @@ using System.Collections.Generic;
using System.Linq;
using OpenTK;
using OpenTK.Input;
+using osu.Framework.Allocation;
using osu.Framework.Configuration;
using osu.Framework.Graphics;
using osu.Framework.Lists;
+using osu.Framework.MathUtils;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Beatmaps;
@@ -85,6 +87,34 @@ namespace osu.Game.Rulesets.Mania.UI
};
}
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ var maniaPlayfield = (ManiaPlayfield)Playfield;
+
+ double lastObjectTime = (Objects.LastOrDefault() as IHasEndTime)?.EndTime ?? Objects.LastOrDefault()?.StartTime ?? double.MaxValue;
+
+ SortedList timingPoints = Beatmap.ControlPointInfo.TimingPoints;
+ for (int i = 0; i < timingPoints.Count; i++)
+ {
+ TimingControlPoint point = timingPoints[i];
+
+ // Stop on the beat before the next timing point, or if there is no next timing point stop slightly past the last object
+ double endTime = i < timingPoints.Count - 1 ? timingPoints[i + 1].Time - point.BeatLength : lastObjectTime + point.BeatLength * (int)point.TimeSignature;
+
+ int index = 0;
+ for (double t = timingPoints[i].Time; Precision.DefinitelyBigger(endTime, t); t += point.BeatLength, index++)
+ {
+ maniaPlayfield.Add(new DrawableBarLine(new BarLine
+ {
+ StartTime = t,
+ ControlPoint = point,
+ BeatIndex = index
+ }));
+ }
+ }
+ }
+
public override ScoreProcessor CreateScoreProcessor() => new ManiaScoreProcessor(this);
protected override BeatmapConverter CreateBeatmapConverter() => new ManiaBeatmapConverter();
diff --git a/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs b/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs
index ff763f87c4..2e6b63579e 100644
--- a/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs
+++ b/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs
@@ -21,6 +21,7 @@ using osu.Game.Rulesets.Mania.Timing;
using osu.Framework.Input;
using osu.Framework.Graphics.Transforms;
using osu.Framework.MathUtils;
+using osu.Game.Rulesets.Mania.Objects.Drawables;
namespace osu.Game.Rulesets.Mania.UI
{
@@ -57,7 +58,7 @@ namespace osu.Game.Rulesets.Mania.UI
private readonly FlowContainer columns;
public IEnumerable Columns => columns.Children;
- private readonly ControlPointContainer barlineContainer;
+ private readonly ControlPointContainer barLineContainer;
private List normalColumnColours = new List();
private Color4 specialColumnColour;
@@ -77,35 +78,51 @@ namespace osu.Game.Rulesets.Mania.UI
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
- RelativeSizeAxes = Axes.Y,
- AutoSizeAxes = Axes.X,
+ RelativeSizeAxes = Axes.Both,
Masking = true,
Children = new Drawable[]
{
- new Box
+ new Container
{
- RelativeSizeAxes = Axes.Both,
- Colour = Color4.Black
- },
- columns = new FillFlowContainer
- {
- Name = "Columns",
+ Name = "Masked elements",
+ Anchor = Anchor.TopCentre,
+ Origin = Anchor.TopCentre,
RelativeSizeAxes = Axes.Y,
AutoSizeAxes = Axes.X,
- Direction = FillDirection.Horizontal,
- Padding = new MarginPadding { Left = 1, Right = 1 },
- Spacing = new Vector2(1, 0)
+ Masking = true,
+ Children = new Drawable[]
+ {
+ new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ Colour = Color4.Black
+ },
+ columns = new FillFlowContainer
+ {
+ Name = "Columns",
+ RelativeSizeAxes = Axes.Y,
+ AutoSizeAxes = Axes.X,
+ Direction = FillDirection.Horizontal,
+ Padding = new MarginPadding { Left = 1, Right = 1 },
+ Spacing = new Vector2(1, 0)
+ }
+ }
},
new Container
{
+ Anchor = Anchor.TopCentre,
+ Origin = Anchor.TopCentre,
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding { Top = HIT_TARGET_POSITION },
Children = new[]
{
- barlineContainer = new ControlPointContainer(timingChanges)
+ barLineContainer = new ControlPointContainer(timingChanges)
{
Name = "Bar lines",
- RelativeSizeAxes = Axes.Both,
+ Anchor = Anchor.TopCentre,
+ Origin = Anchor.TopCentre,
+ RelativeSizeAxes = Axes.Y
+ // Width is set in the Update method
}
}
}
@@ -190,6 +207,7 @@ namespace osu.Game.Rulesets.Mania.UI
}
public override void Add(DrawableHitObject h) => Columns.ElementAt(h.HitObject.Column).Add(h);
+ public void Add(DrawableBarLine barline) => barLineContainer.Add(barline);
protected override bool OnKeyDown(InputState state, KeyDownEventArgs args)
{
@@ -224,7 +242,7 @@ namespace osu.Game.Rulesets.Mania.UI
timeSpan = MathHelper.Clamp(timeSpan, time_span_min, time_span_max);
- barlineContainer.TimeSpan = value;
+ barLineContainer.TimeSpan = value;
Columns.ForEach(c => c.ControlPointContainer.TimeSpan = value);
}
}
@@ -234,6 +252,13 @@ namespace osu.Game.Rulesets.Mania.UI
TransformTo(() => TimeSpan, newTimeSpan, duration, easing, new TransformTimeSpan());
}
+ protected override void Update()
+ {
+ // Due to masking differences, it is not possible to get the width of the columns container automatically
+ // While masking on effectively only the Y-axis, so we need to set the width of the bar line container manually
+ barLineContainer.Width = columns.Width;
+ }
+
private class TransformTimeSpan : Transform
{
public override double CurrentValue
diff --git a/osu.Game.Rulesets.Mania/osu.Game.Rulesets.Mania.csproj b/osu.Game.Rulesets.Mania/osu.Game.Rulesets.Mania.csproj
index 7a8ec25fe4..3d5614bd90 100644
--- a/osu.Game.Rulesets.Mania/osu.Game.Rulesets.Mania.csproj
+++ b/osu.Game.Rulesets.Mania/osu.Game.Rulesets.Mania.csproj
@@ -62,6 +62,7 @@
+
@@ -70,6 +71,7 @@
+
diff --git a/osu.Game/Graphics/Backgrounds/Triangles.cs b/osu.Game/Graphics/Backgrounds/Triangles.cs
index 9a19819af8..7a2345a80c 100644
--- a/osu.Game/Graphics/Backgrounds/Triangles.cs
+++ b/osu.Game/Graphics/Backgrounds/Triangles.cs
@@ -23,6 +23,13 @@ namespace osu.Game.Graphics.Backgrounds
public class Triangles : Drawable
{
private const float triangle_size = 100;
+ private const float base_velocity = 50;
+
+ ///
+ /// How many screen-space pixels are smoothed over.
+ /// Same behavior as Sprite's EdgeSmoothness.
+ ///
+ private const float edge_smoothness = 1;
public override bool HandleInput => false;
@@ -103,31 +110,34 @@ namespace osu.Game.Graphics.Backgrounds
Invalidate(Invalidation.DrawNode, shallPropagate: false);
+ if (CreateNewTriangles)
+ addTriangles(false);
+
+ float adjustedAlpha = HideAlphaDiscrepancies ?
+ // Cubically scale alpha to make it drop off more sharply.
+ (float)Math.Pow(DrawInfo.Colour.AverageColour.Linear.A, 3) :
+ 1;
+
+ float elapsedSeconds = (float)Time.Elapsed / 1000;
+ // Since position is relative, the velocity needs to scale inversely with DrawHeight.
+ // Since we will later multiply by the scale of individual triangles we normalize by
+ // dividing by triangleScale.
+ float movedDistance = -elapsedSeconds * Velocity * base_velocity / (DrawHeight * triangleScale);
+
for (int i = 0; i < parts.Count; i++)
{
TriangleParticle newParticle = parts[i];
- float adjustedAlpha = HideAlphaDiscrepancies ?
- // Cubically scale alpha to make it drop off more sharply.
- (float)Math.Pow(DrawInfo.Colour.AverageColour.Linear.A, 3) :
- 1;
-
-
- newParticle.Position += new Vector2(0, -(parts[i].Scale * (50 / DrawHeight)) / triangleScale * Velocity) * ((float)Time.Elapsed / 950);
+ // Scale moved distance by the size of the triangle. Smaller triangles should move more slowly.
+ newParticle.Position.Y += parts[i].Scale * movedDistance;
newParticle.Colour.A = adjustedAlpha;
parts[i] = newParticle;
- if (!CreateNewTriangles)
- continue;
-
float bottomPos = parts[i].Position.Y + triangle_size * parts[i].Scale * 0.866f / DrawHeight;
-
if (bottomPos < 0)
parts.RemoveAt(i);
}
-
- addTriangles(false);
}
private void addTriangles(bool randomY)
@@ -211,20 +221,28 @@ namespace osu.Game.Graphics.Backgrounds
Shader.Bind();
Texture.TextureGL.Bind();
+ Vector2 localInflationAmount = edge_smoothness * DrawInfo.MatrixInverse.ExtractScale().Xy;
+
foreach (TriangleParticle particle in Parts)
{
- var offset = new Vector2(particle.Scale * 0.5f, particle.Scale * 0.866f);
+ var offset = triangle_size * new Vector2(particle.Scale * 0.5f, particle.Scale * 0.866f);
+ var size = new Vector2(2 * offset.X, offset.Y);
var triangle = new Triangle(
particle.Position * Size * DrawInfo.Matrix,
- (particle.Position * Size + offset * triangle_size) * DrawInfo.Matrix,
- (particle.Position * Size + new Vector2(-offset.X, offset.Y) * triangle_size) * DrawInfo.Matrix
+ (particle.Position * Size + offset) * DrawInfo.Matrix,
+ (particle.Position * Size + new Vector2(-offset.X, offset.Y)) * DrawInfo.Matrix
);
ColourInfo colourInfo = DrawInfo.Colour;
colourInfo.ApplyChild(particle.Colour);
- Texture.DrawTriangle(triangle, colourInfo, null, Shared.VertexBatch.Add);
+ Texture.DrawTriangle(
+ triangle,
+ colourInfo,
+ null,
+ Shared.VertexBatch.Add,
+ Vector2.Divide(localInflationAmount, size));
}
Shader.Unbind();
diff --git a/osu.Game/Overlays/Chat/ChatTabControl.cs b/osu.Game/Overlays/Chat/ChatTabControl.cs
index 57447f1913..a281cff7db 100644
--- a/osu.Game/Overlays/Chat/ChatTabControl.cs
+++ b/osu.Game/Overlays/Chat/ChatTabControl.cs
@@ -14,6 +14,7 @@ using osu.Game.Graphics.UserInterface;
using osu.Game.Online.Chat;
using OpenTK;
using OpenTK.Graphics;
+using osu.Framework.Configuration;
namespace osu.Game.Overlays.Chat
{
@@ -23,6 +24,8 @@ namespace osu.Game.Overlays.Chat
private const float shear_width = 10;
+ public readonly Bindable ChannelSelectorActive = new Bindable();
+
public ChatTabControl()
{
TabContainer.Margin = new MarginPadding { Left = 50 };
@@ -37,6 +40,8 @@ namespace osu.Game.Overlays.Chat
TextSize = 20,
Padding = new MarginPadding(10),
});
+
+ AddTabItem(new ChannelTabItem.ChannelSelectorTabItem(new Channel { Name = "+" }, ChannelSelectorActive));
}
private class ChannelTabItem : TabItem
@@ -49,6 +54,7 @@ namespace osu.Game.Overlays.Chat
private readonly SpriteText textBold;
private readonly Box box;
private readonly Box highlightBox;
+ private readonly TextAwesome icon;
public override bool Active
{
@@ -114,6 +120,11 @@ namespace osu.Game.Overlays.Chat
backgroundHover = colours.Gray7;
highlightBox.Colour = colours.Yellow;
+ }
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
updateState();
}
@@ -159,7 +170,7 @@ namespace osu.Game.Overlays.Chat
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
{
- new TextAwesome
+ icon = new TextAwesome
{
Icon = FontAwesome.fa_hashtag,
Anchor = Anchor.CentreLeft,
@@ -191,6 +202,40 @@ namespace osu.Game.Overlays.Chat
}
};
}
+
+ public class ChannelSelectorTabItem : ChannelTabItem
+ {
+ public override bool Active
+ {
+ get { return base.Active; }
+ set
+ {
+ activeBindable.Value = value;
+ base.Active = value;
+ }
+ }
+
+ private readonly Bindable activeBindable;
+
+ public ChannelSelectorTabItem(Channel value, Bindable active) : base(value)
+ {
+ activeBindable = active;
+ Depth = float.MaxValue;
+ Width = 45;
+
+ icon.Alpha = 0;
+
+ text.TextSize = 45;
+ textBold.TextSize = 45;
+ }
+
+ [BackgroundDependencyLoader]
+ private new void load(OsuColour colour)
+ {
+ backgroundInactive = colour.Gray2;
+ backgroundActive = colour.Gray3;
+ }
+ }
}
}
}
diff --git a/osu.Game/Overlays/ChatOverlay.cs b/osu.Game/Overlays/ChatOverlay.cs
index dccbc18f30..5b2c01151c 100644
--- a/osu.Game/Overlays/ChatOverlay.cs
+++ b/osu.Game/Overlays/ChatOverlay.cs
@@ -260,6 +260,8 @@ namespace osu.Game.Overlays
{
if (currentChannel == value) return;
+ if (channelTabs.ChannelSelectorActive) return;
+
if (currentChannel != null)
currentChannelContainer.Clear(false);