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