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

Merge remote-tracking branch 'upstream/master' into fix-mod-implementation-check

This commit is contained in:
Dean Herbert 2018-01-12 17:49:31 +09:00
commit 354d781002
66 changed files with 1308 additions and 1338 deletions

View File

@ -10,6 +10,17 @@ using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Catch.Objects.Drawable
{
public abstract class PalpableCatchHitObject<TObject> : DrawableCatchHitObject<TObject>
where TObject : CatchHitObject
{
protected PalpableCatchHitObject(TObject hitObject)
: base(hitObject)
{
Scale = new Vector2(HitObject.Scale);
}
}
public abstract class DrawableCatchHitObject<TObject> : DrawableCatchHitObject
where TObject : CatchHitObject
{
@ -19,19 +30,17 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
: base(hitObject)
{
HitObject = hitObject;
Scale = new Vector2(HitObject.Scale);
Anchor = Anchor.BottomLeft;
}
}
public abstract class DrawableCatchHitObject : DrawableScrollingHitObject<CatchHitObject>
public abstract class DrawableCatchHitObject : DrawableHitObject<CatchHitObject>
{
protected DrawableCatchHitObject(CatchHitObject hitObject)
: base(hitObject)
{
RelativePositionAxes = Axes.Both;
RelativePositionAxes = Axes.X;
X = hitObject.X;
Y = (float)HitObject.StartTime;
}
public Func<CatchHitObject, bool> CheckPosition;

View File

@ -8,7 +8,7 @@ using OpenTK;
namespace osu.Game.Rulesets.Catch.Objects.Drawable
{
public class DrawableDroplet : DrawableCatchHitObject<Droplet>
public class DrawableDroplet : PalpableCatchHitObject<Droplet>
{
public DrawableDroplet(Droplet h)
: base(h)

View File

@ -14,7 +14,7 @@ using OpenTK.Graphics;
namespace osu.Game.Rulesets.Catch.Objects.Drawable
{
public class DrawableFruit : DrawableCatchHitObject<Fruit>
public class DrawableFruit : PalpableCatchHitObject<Fruit>
{
private Circle border;

View File

@ -16,15 +16,10 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
public DrawableJuiceStream(JuiceStream s) : base(s)
{
RelativeSizeAxes = Axes.Both;
Height = (float)HitObject.Duration;
Origin = Anchor.BottomLeft;
X = 0;
Child = dropletContainer = new Container
{
RelativeSizeAxes = Axes.Both,
RelativeChildOffset = new Vector2(0, (float)HitObject.StartTime),
RelativeChildSize = new Vector2(1, (float)HitObject.Duration)
};
Child = dropletContainer = new Container { RelativeSizeAxes = Axes.Both, };
foreach (CatchHitObject tick in s.NestedHitObjects.OfType<CatchHitObject>())
{
@ -45,7 +40,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
}
}
protected override void AddNested(DrawableHitObject<CatchHitObject> h)
protected override void AddNested(DrawableHitObject h)
{
((DrawableCatchHitObject)h).CheckPosition = o => CheckPosition?.Invoke(o) ?? false;
dropletContainer.Add(h);

View File

@ -2,13 +2,13 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.Graphics;
using osu.Game.Rulesets.UI;
using osu.Framework.Graphics.Containers;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Catch.Objects.Drawable;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.UI.Scrolling;
namespace osu.Game.Rulesets.Catch.UI
{
@ -22,12 +22,10 @@ namespace osu.Game.Rulesets.Catch.UI
private readonly CatcherArea catcherArea;
public CatchPlayfield(BeatmapDifficulty difficulty)
: base(Axes.Y, BASE_WIDTH)
: base(ScrollingDirection.Down, BASE_WIDTH)
{
Container explodingFruitContainer;
Reversed.Value = true;
Anchor = Anchor.TopCentre;
Origin = Anchor.TopCentre;

View File

@ -10,6 +10,7 @@ using osu.Game.Rulesets.Catch.Scoring;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.UI;
using osu.Game.Rulesets.UI.Scrolling;
namespace osu.Game.Rulesets.Catch.UI
{

View File

@ -15,6 +15,7 @@ using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Catch.Objects.Drawable;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.UI;
using OpenTK;
using OpenTK.Graphics;
@ -48,7 +49,7 @@ namespace osu.Game.Rulesets.Catch.UI
var screenSpacePosition = fruit.ScreenSpaceDrawQuad.Centre;
// todo: make this less ugly, somehow.
(fruit.Parent as Container<DrawableHitObject>)?.Remove(fruit);
(fruit.Parent as HitObjectContainer)?.Remove(fruit);
(fruit.Parent as Container)?.Remove(fruit);
fruit.RelativePositionAxes = Axes.None;

View File

@ -95,7 +95,6 @@ namespace osu.Game.Rulesets.Mania
new ModCinema(),
},
},
new ManiaModGravity()
};
default:

View File

@ -1,23 +0,0 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Collections.Generic;
using osu.Game.Rulesets.Mania.UI;
using osu.Game.Rulesets.Timing;
namespace osu.Game.Rulesets.Mania.Mods
{
/// <summary>
/// A type of mod which generates speed adjustments that scroll the hit objects and bar lines.
/// </summary>
internal interface IGenerateSpeedAdjustments
{
/// <summary>
/// Applies this mod to a hit renderer.
/// </summary>
/// <param name="rulesetContainer">The hit renderer to apply to.</param>
/// <param name="hitObjectTimingChanges">The per-column list of speed adjustments for hit objects.</param>
/// <param name="barlineTimingChanges">The list of speed adjustments for bar lines.</param>
void ApplyToRulesetContainer(ManiaRulesetContainer rulesetContainer, ref List<SpeedAdjustmentContainer>[] hitObjectTimingChanges, ref List<SpeedAdjustmentContainer> barlineTimingChanges);
}
}

View File

@ -1,48 +0,0 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Collections.Generic;
using System.Linq;
using osu.Game.Graphics;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.Timing;
using osu.Game.Rulesets.Mania.UI;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Timing;
namespace osu.Game.Rulesets.Mania.Mods
{
public class ManiaModGravity : Mod, IGenerateSpeedAdjustments
{
public override string Name => "Gravity";
public override string ShortenedName => "GR";
public override double ScoreMultiplier => 0;
public override FontAwesome Icon => FontAwesome.fa_sort_desc;
public void ApplyToRulesetContainer(ManiaRulesetContainer rulesetContainer, ref List<SpeedAdjustmentContainer>[] hitObjectTimingChanges,
ref List<SpeedAdjustmentContainer> barlineTimingChanges)
{
// We have to generate one speed adjustment per hit object for gravity
foreach (var obj in rulesetContainer.Objects.OfType<ManiaHitObject>())
{
var controlPoint = rulesetContainer.CreateControlPointAt(obj.StartTime);
// Beat length has too large of an effect for gravity, so we'll force it to a constant value for now
controlPoint.TimingPoint.BeatLength = 1000;
hitObjectTimingChanges[obj.Column].Add(new ManiaSpeedAdjustmentContainer(controlPoint, ScrollingAlgorithm.Gravity));
}
// Like with hit objects, we need to generate one speed adjustment per bar line
foreach (var barLine in rulesetContainer.BarLines)
{
var controlPoint = rulesetContainer.CreateControlPointAt(barLine.HitObject.StartTime);
// Beat length has too large of an effect for gravity, so we'll force it to a constant value for now
controlPoint.TimingPoint.BeatLength = 1000;
barlineTimingChanges.Add(new ManiaSpeedAdjustmentContainer(controlPoint, ScrollingAlgorithm.Gravity));
}
}
}
}

View File

@ -7,7 +7,6 @@ using osu.Game.Rulesets.Objects.Drawables;
using osu.Framework.Graphics;
using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces;
using OpenTK.Graphics;
using OpenTK;
using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Mania.Judgements;
using osu.Framework.Extensions.IEnumerableExtensions;
@ -42,8 +41,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
public DrawableHoldNote(HoldNote hitObject, ManiaAction action)
: base(hitObject, action)
{
RelativeSizeAxes = Axes.Both;
Height = (float)HitObject.Duration;
RelativeSizeAxes = Axes.X;
AddRange(new Drawable[]
{
@ -60,12 +58,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
Origin = Anchor.TopCentre,
RelativeSizeAxes = Axes.X,
},
tickContainer = new Container<DrawableHoldNoteTick>
{
RelativeSizeAxes = Axes.Both,
RelativeChildOffset = new Vector2(0, (float)HitObject.StartTime),
RelativeChildSize = new Vector2(1, (float)HitObject.Duration)
},
tickContainer = new Container<DrawableHoldNoteTick> { RelativeSizeAxes = Axes.Both },
head = new DrawableHeadNote(this, action)
{
Anchor = Anchor.TopCentre,
@ -73,7 +66,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
},
tail = new DrawableTailNote(this, action)
{
Anchor = Anchor.BottomCentre,
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre
}
});
@ -175,13 +168,6 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
{
this.holdNote = holdNote;
RelativePositionAxes = Axes.None;
Y = 0;
// Life time managed by the parent DrawableHoldNote
LifetimeStart = double.MinValue;
LifetimeEnd = double.MaxValue;
GlowPiece.Alpha = 0;
}
@ -200,6 +186,11 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
return true;
}
protected override void UpdateState(ArmedState state)
{
// The holdnote keeps scrolling through for now, so having the head disappear looks weird
}
}
/// <summary>
@ -214,13 +205,6 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
{
this.holdNote = holdNote;
RelativePositionAxes = Axes.None;
Y = 0;
// Life time managed by the parent DrawableHoldNote
LifetimeStart = double.MinValue;
LifetimeEnd = double.MaxValue;
GlowPiece.Alpha = 0;
}
@ -252,6 +236,11 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
});
}
protected override void UpdateState(ArmedState state)
{
// The holdnote keeps scrolling through, so having the tail disappear looks weird
}
public override bool OnPressed(ManiaAction action) => false; // Tail doesn't handle key down
public override bool OnReleased(ManiaAction action)

View File

@ -32,15 +32,9 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
Anchor = Anchor.TopCentre;
Origin = Anchor.TopCentre;
Y = (float)HitObject.StartTime;
RelativeSizeAxes = Axes.X;
Size = new Vector2(1);
// Life time managed by the parent DrawableHoldNote
LifetimeStart = double.MinValue;
LifetimeEnd = double.MaxValue;
Children = new[]
{
glowContainer = new CircularContainer

View File

@ -7,7 +7,7 @@ using osu.Game.Rulesets.Objects.Drawables;
namespace osu.Game.Rulesets.Mania.Objects.Drawables
{
public abstract class DrawableManiaHitObject<TObject> : DrawableScrollingHitObject<ManiaHitObject>
public abstract class DrawableManiaHitObject<TObject> : DrawableHitObject<ManiaHitObject>
where TObject : ManiaHitObject
{
/// <summary>
@ -20,7 +20,9 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
protected DrawableManiaHitObject(TObject hitObject, ManiaAction? action = null)
: base(hitObject)
{
RelativePositionAxes = Axes.Y;
Anchor = Anchor.TopCentre;
Origin = Anchor.TopCentre;
HitObject = hitObject;
if (action != null)

View File

@ -78,6 +78,13 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
protected override void UpdateState(ArmedState state)
{
switch (state)
{
case ArmedState.Hit:
case ArmedState.Miss:
this.FadeOut(100).Expire();
break;
}
}
public virtual bool OnPressed(ManiaAction action)

View File

@ -5,16 +5,13 @@ using System;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics;
using osu.Framework.Timing;
using osu.Game.Rulesets.Mania.Judgements;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.Objects.Drawables;
using osu.Game.Rulesets.Mania.Timing;
using osu.Game.Rulesets.Mania.UI;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.Timing;
using osu.Game.Tests.Visual;
namespace osu.Game.Rulesets.Mania.Tests
@ -44,10 +41,10 @@ namespace osu.Game.Rulesets.Mania.Tests
AddStep("Right special style", () => createPlayfield(8, SpecialColumnPosition.Right));
AddStep("Reversed", () => createPlayfield(4, SpecialColumnPosition.Normal, true));
AddStep("Notes with input", () => createPlayfieldWithNotes(false));
AddStep("Notes with input (reversed)", () => createPlayfieldWithNotes(false, true));
AddStep("Notes with gravity", () => createPlayfieldWithNotes(true));
AddStep("Notes with gravity (reversed)", () => createPlayfieldWithNotes(true, true));
AddStep("Notes with input", () => createPlayfieldWithNotes());
AddStep("Notes with input (reversed)", () => createPlayfieldWithNotes(true));
AddStep("Notes with gravity", () => createPlayfieldWithNotes());
AddStep("Notes with gravity (reversed)", () => createPlayfieldWithNotes(true));
AddStep("Hit explosion", () =>
{
@ -70,11 +67,6 @@ namespace osu.Game.Rulesets.Mania.Tests
maniaRuleset = rulesets.GetRuleset(3);
}
private SpeedAdjustmentContainer createTimingChange(double time, bool gravity) => new ManiaSpeedAdjustmentContainer(new MultiplierControlPoint(time)
{
TimingPoint = { BeatLength = 1000 }
}, gravity ? ScrollingAlgorithm.Gravity : ScrollingAlgorithm.Basic);
private ManiaPlayfield createPlayfield(int cols, SpecialColumnPosition specialPos, bool inverted = false)
{
Clear();
@ -95,7 +87,7 @@ namespace osu.Game.Rulesets.Mania.Tests
return playfield;
}
private void createPlayfieldWithNotes(bool gravity, bool inverted = false)
private void createPlayfieldWithNotes(bool inverted = false)
{
Clear();
@ -114,23 +106,14 @@ namespace osu.Game.Rulesets.Mania.Tests
playfield.Inverted.Value = inverted;
if (!gravity)
playfield.Columns.ForEach(c => c.Add(createTimingChange(0, false)));
for (double t = start_time; t <= start_time + duration; t += 100)
{
if (gravity)
playfield.Columns.ElementAt(0).Add(createTimingChange(t, true));
playfield.Add(new DrawableNote(new Note
{
StartTime = t,
Column = 0
}, ManiaAction.Key1));
if (gravity)
playfield.Columns.ElementAt(3).Add(createTimingChange(t, true));
playfield.Add(new DrawableNote(new Note
{
StartTime = t,
@ -138,9 +121,6 @@ namespace osu.Game.Rulesets.Mania.Tests
}, ManiaAction.Key4));
}
if (gravity)
playfield.Columns.ElementAt(1).Add(createTimingChange(start_time, true));
playfield.Add(new DrawableHoldNote(new HoldNote
{
StartTime = start_time,
@ -148,9 +128,6 @@ namespace osu.Game.Rulesets.Mania.Tests
Column = 1
}, ManiaAction.Key2));
if (gravity)
playfield.Columns.ElementAt(2).Add(createTimingChange(start_time, true));
playfield.Add(new DrawableHoldNote(new HoldNote
{
StartTime = start_time,

View File

@ -1,60 +0,0 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Rulesets.Timing;
namespace osu.Game.Rulesets.Mania.Timing
{
/// <summary>
/// A <see cref="ScrollingContainer"/> that emulates a form of gravity where hit objects speed up over time.
/// </summary>
internal class GravityScrollingContainer : ScrollingContainer
{
private readonly MultiplierControlPoint controlPoint;
public GravityScrollingContainer(MultiplierControlPoint controlPoint)
{
this.controlPoint = controlPoint;
}
protected override void UpdateAfterChildren()
{
base.UpdateAfterChildren();
// The gravity-adjusted start position
float startPos = (float)computeGravityTime(controlPoint.StartTime);
// The gravity-adjusted end position
float endPos = (float)computeGravityTime(controlPoint.StartTime + RelativeChildSize.Y);
Y = startPos;
Height = endPos - startPos;
}
/// <summary>
/// Applies gravity to a time value based on the current time.
/// </summary>
/// <param name="time">The time value gravity should be applied to.</param>
/// <returns>The time after gravity is applied to <paramref name="time"/>.</returns>
private double computeGravityTime(double time)
{
double relativeTime = relativeTimeAt(time);
// The sign of the relative time, this is used to apply backwards acceleration leading into startTime
double sign = relativeTime < 0 ? -1 : 1;
return VisibleTimeRange - acceleration * relativeTime * relativeTime * sign;
}
/// <summary>
/// The acceleration due to "gravity" of the content of this container.
/// </summary>
private double acceleration => 1 / VisibleTimeRange;
/// <summary>
/// Computes the current time relative to <paramref name="time"/>, accounting for <see cref="ScrollingContainer.VisibleTimeRange"/>.
/// </summary>
/// <param name="time">The non-offset time.</param>
/// <returns>The current time relative to <paramref name="time"/> - <see cref="ScrollingContainer.VisibleTimeRange"/>. </returns>
private double relativeTimeAt(double time) => Time.Current - time + VisibleTimeRange;
}
}

View File

@ -1,29 +0,0 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Rulesets.Timing;
namespace osu.Game.Rulesets.Mania.Timing
{
public class ManiaSpeedAdjustmentContainer : SpeedAdjustmentContainer
{
private readonly ScrollingAlgorithm scrollingAlgorithm;
public ManiaSpeedAdjustmentContainer(MultiplierControlPoint timingSection, ScrollingAlgorithm scrollingAlgorithm)
: base(timingSection)
{
this.scrollingAlgorithm = scrollingAlgorithm;
}
protected override ScrollingContainer CreateScrollingContainer()
{
switch (scrollingAlgorithm)
{
default:
return base.CreateScrollingContainer();
case ScrollingAlgorithm.Gravity:
return new GravityScrollingContainer(ControlPoint);
}
}
}
}

View File

@ -1,17 +0,0 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
namespace osu.Game.Rulesets.Mania.Timing
{
public enum ScrollingAlgorithm
{
/// <summary>
/// Basic scrolling algorithm based on the timing section time. This is the default algorithm.
/// </summary>
Basic,
/// <summary>
/// Emulating a form of gravity where hit objects speed up over time.
/// </summary>
Gravity
}
}

View File

@ -12,8 +12,8 @@ using osu.Game.Graphics;
using osu.Game.Rulesets.Objects.Drawables;
using System;
using osu.Framework.Input.Bindings;
using osu.Game.Rulesets.UI;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.UI.Scrolling;
namespace osu.Game.Rulesets.Mania.UI
{
@ -45,7 +45,7 @@ namespace osu.Game.Rulesets.Mania.UI
private const float opacity_pressed = 0.25f;
public Column()
: base(Axes.Y)
: base(ScrollingDirection.Up)
{
Width = column_width;

View File

@ -3,7 +3,6 @@
using osu.Framework.Graphics;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.UI;
using OpenTK;
using OpenTK.Graphics;
using osu.Framework.Graphics.Containers;
@ -17,6 +16,7 @@ using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Mania.Objects.Drawables;
using osu.Framework.Graphics.Shapes;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.UI.Scrolling;
namespace osu.Game.Rulesets.Mania.UI
{
@ -58,7 +58,7 @@ namespace osu.Game.Rulesets.Mania.UI
private readonly int columnCount;
public ManiaPlayfield(int columnCount)
: base(Axes.Y)
: base(ScrollingDirection.Up)
{
this.columnCount = columnCount;

View File

@ -16,13 +16,12 @@ using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.Objects.Drawables;
using osu.Game.Rulesets.Mania.Replays;
using osu.Game.Rulesets.Mania.Scoring;
using osu.Game.Rulesets.Mania.Timing;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Replays;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.Timing;
using osu.Game.Rulesets.UI;
using osu.Game.Rulesets.UI.Scrolling;
namespace osu.Game.Rulesets.Mania.UI
{
@ -98,8 +97,6 @@ namespace osu.Game.Rulesets.Mania.UI
protected override Vector2 GetPlayfieldAspectAdjust() => new Vector2(1, 0.8f);
protected override SpeedAdjustmentContainer CreateSpeedAdjustmentContainer(MultiplierControlPoint controlPoint) => new ManiaSpeedAdjustmentContainer(controlPoint, ScrollingAlgorithm.Basic);
protected override FramedReplayInputHandler CreateReplayInputHandler(Replay replay) => new ManiaFramedReplayInputHandler(replay, this);
}
}

View File

@ -64,7 +64,6 @@
<Compile Include="Judgements\HoldNoteTickJudgement.cs" />
<Compile Include="Judgements\ManiaJudgement.cs" />
<Compile Include="ManiaDifficultyCalculator.cs" />
<Compile Include="Mods\IGenerateSpeedAdjustments.cs" />
<Compile Include="Mods\ManiaKeyMod.cs" />
<Compile Include="Mods\ManiaModAutoplay.cs" />
<Compile Include="Mods\ManiaModDaycore.cs" />
@ -114,8 +113,6 @@
<Compile Include="Tests\TestCaseManiaHitObjects.cs" />
<Compile Include="Tests\TestCaseManiaPlayfield.cs" />
<Compile Include="Tests\TestCasePerformancePoints.cs" />
<Compile Include="Timing\GravityScrollingContainer.cs" />
<Compile Include="Timing\ScrollingAlgorithm.cs" />
<Compile Include="UI\Column.cs" />
<Compile Include="UI\DrawableManiaJudgement.cs" />
<Compile Include="UI\HitExplosion.cs" />
@ -123,9 +120,7 @@
<Compile Include="UI\ManiaPlayfield.cs" />
<Compile Include="ManiaRuleset.cs" />
<Compile Include="Mods\ManiaModNoFail.cs" />
<Compile Include="Mods\ManiaModGravity.cs" />
<Compile Include="UI\SpecialColumnPosition.cs" />
<Compile Include="Timing\ManiaSpeedAdjustmentContainer.cs" />
</ItemGroup>
<ItemGroup>
<None Include="app.config" />

View File

@ -11,7 +11,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
/// <summary>
/// A line that scrolls alongside hit objects in the playfield and visualises control points.
/// </summary>
public class DrawableBarLine : DrawableScrollingHitObject<TaikoHitObject>
public class DrawableBarLine : DrawableHitObject<TaikoHitObject>
{
/// <summary>
/// The width of the line tracker.

View File

@ -34,15 +34,10 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
public DrawableDrumRoll(DrumRoll drumRoll)
: base(drumRoll)
{
Width = (float)HitObject.Duration;
RelativeSizeAxes = Axes.Y;
Container<DrawableDrumRollTick> tickContainer;
MainPiece.Add(tickContainer = new Container<DrawableDrumRollTick>
{
RelativeSizeAxes = Axes.Both,
RelativeChildOffset = new Vector2((float)HitObject.StartTime, 0),
RelativeChildSize = new Vector2((float)HitObject.Duration, 1)
});
MainPiece.Add(tickContainer = new Container<DrawableDrumRollTick> { RelativeSizeAxes = Axes.Both });
foreach (var tick in drumRoll.NestedHitObjects.OfType<DrumRollTick>())
{
@ -100,6 +95,13 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
protected override void UpdateState(ArmedState state)
{
switch (state)
{
case ArmedState.Hit:
case ArmedState.Miss:
this.FadeOut(100).Expire();
break;
}
}
}
}

View File

@ -15,23 +15,11 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
public DrawableDrumRollTick(DrumRollTick tick)
: base(tick)
{
// Because ticks aren't added by the ScrollingPlayfield, we need to set the following properties ourselves
RelativePositionAxes = Axes.X;
X = (float)tick.StartTime;
FillMode = FillMode.Fit;
}
public override bool DisplayJudgement => false;
protected override void LoadComplete()
{
base.LoadComplete();
// We need to set this here because RelativeSizeAxes won't/can't set our size by default with a different RelativeChildSize
Width *= Parent.RelativeChildSize.X;
}
protected override TaikoPiece CreateMainPiece() => new TickPiece
{
Filled = HitObject.FirstTick
@ -55,7 +43,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
switch (state)
{
case ArmedState.Hit:
Content.ScaleTo(0, 100, Easing.OutQuint);
Content.ScaleTo(0, 100, Easing.OutQuint).Expire();
break;
}
}

View File

@ -192,7 +192,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
Size = BaseSize * Parent.RelativeChildSize;
// Make the swell stop at the hit target
X = (float)Math.Max(Time.Current, HitObject.StartTime);
X = Math.Max(0, X);
double t = Math.Min(HitObject.StartTime, Time.Current);
if (t == HitObject.StartTime && !hasStarted)

View File

@ -12,7 +12,7 @@ using System.Collections.Generic;
namespace osu.Game.Rulesets.Taiko.Objects.Drawables
{
public abstract class DrawableTaikoHitObject<TaikoHitType> : DrawableScrollingHitObject<TaikoHitObject>, IKeyBindingHandler<TaikoAction>
public abstract class DrawableTaikoHitObject<TaikoHitType> : DrawableHitObject<TaikoHitObject>, IKeyBindingHandler<TaikoAction>
where TaikoHitType : TaikoHitObject
{
public override Vector2 OriginPosition => new Vector2(DrawHeight / 2);

View File

@ -5,7 +5,6 @@ using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Shapes;
using osu.Game.Rulesets.Taiko.Objects;
using osu.Game.Rulesets.UI;
using OpenTK;
using OpenTK.Graphics;
using osu.Game.Rulesets.Taiko.Judgements;
@ -17,6 +16,7 @@ using System.Linq;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Taiko.Objects.Drawables;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.UI.Scrolling;
namespace osu.Game.Rulesets.Taiko.UI
{
@ -37,6 +37,7 @@ namespace osu.Game.Rulesets.Taiko.UI
/// </summary>
private const float left_area_size = 240;
protected override bool UserScrollSpeedAdjustment => false;
private readonly Container<HitExplosion> hitExplosionContainer;
private readonly Container<KiaiHitExplosion> kiaiExplosionContainer;
@ -56,7 +57,7 @@ namespace osu.Game.Rulesets.Taiko.UI
private readonly Box background;
public TaikoPlayfield(ControlPointInfo controlPoints)
: base(Axes.X)
: base(ScrollingDirection.Left)
{
AddRangeInternal(new Drawable[]
{

View File

@ -17,6 +17,7 @@ using osu.Game.Rulesets.Taiko.Replays;
using OpenTK;
using System.Linq;
using osu.Framework.Input;
using osu.Game.Rulesets.UI.Scrolling;
namespace osu.Game.Rulesets.Taiko.UI
{

View File

@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
using osu.Framework.Allocation;
using OpenTK;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
@ -18,26 +19,10 @@ namespace osu.Game.Tests.Visual
{
public override IReadOnlyList<Type> RequiredTypes => new[] { typeof(SelectionLayer) };
public TestCaseEditorSelectionLayer()
[BackgroundDependencyLoader]
private void load()
{
var playfield = new OsuEditPlayfield
{
new DrawableHitCircle(new HitCircle { Position = new Vector2(256, 192), Scale = 0.5f }),
new DrawableHitCircle(new HitCircle { Position = new Vector2(344, 148), Scale = 0.5f }),
new DrawableSlider(new Slider
{
ControlPoints = new List<Vector2>
{
new Vector2(128, 256),
new Vector2(344, 256),
},
Distance = 400,
Position = new Vector2(128, 256),
Velocity = 1,
TickDistance = 100,
Scale = 0.5f
})
};
var playfield = new OsuEditPlayfield();
Children = new Drawable[]
{
@ -49,6 +34,22 @@ namespace osu.Game.Tests.Visual
},
new SelectionLayer(playfield)
};
playfield.Add(new DrawableHitCircle(new HitCircle { Position = new Vector2(256, 192), Scale = 0.5f }));
playfield.Add(new DrawableHitCircle(new HitCircle { Position = new Vector2(344, 148), Scale = 0.5f }));
playfield.Add(new DrawableSlider(new Slider
{
ControlPoints = new List<Vector2>
{
new Vector2(128, 256),
new Vector2(344, 256),
},
Distance = 400,
Position = new Vector2(128, 256),
Velocity = 1,
TickDistance = 100,
Scale = 0.5f
}));
}
}
}

View File

@ -0,0 +1,184 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using System.Collections.Generic;
using osu.Framework.Extensions.IEnumerableExtensions;
using OpenTK;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Timing;
using osu.Game.Rulesets.UI;
using osu.Game.Rulesets.UI.Scrolling;
namespace osu.Game.Tests.Visual
{
public class TestCaseScrollingHitObjects : OsuTestCase
{
public override IReadOnlyList<Type> RequiredTypes => new[] { typeof(Playfield) };
private readonly TestPlayfield[] playfields = new TestPlayfield[4];
public TestCaseScrollingHitObjects()
{
Add(new GridContainer
{
RelativeSizeAxes = Axes.Both,
Content = new[]
{
new Drawable[]
{
playfields[0] = new TestPlayfield(ScrollingDirection.Up),
playfields[1] = new TestPlayfield(ScrollingDirection.Down)
},
new Drawable[]
{
playfields[2] = new TestPlayfield(ScrollingDirection.Left),
playfields[3] = new TestPlayfield(ScrollingDirection.Right)
}
}
});
AddSliderStep("Time range", 100, 10000, 5000, v => playfields.ForEach(p => p.VisibleTimeRange.Value = v));
AddStep("Add control point", () => addControlPoint(Time.Current + 5000));
}
protected override void LoadComplete()
{
base.LoadComplete();
playfields.ForEach(p => p.HitObjects.AddControlPoint(new MultiplierControlPoint(0)));
for (int i = 0; i <= 5000; i += 1000)
addHitObject(Time.Current + i);
Scheduler.AddDelayed(() => addHitObject(Time.Current + 5000), 1000, true);
}
private void addHitObject(double time)
{
playfields.ForEach(p =>
{
var hitObject = new TestDrawableHitObject(time);
setAnchor(hitObject, p);
p.Add(hitObject);
});
}
private void addControlPoint(double time)
{
playfields.ForEach(p =>
{
p.HitObjects.AddControlPoint(new MultiplierControlPoint(time) { DifficultyPoint = { SpeedMultiplier = 3 } });
p.HitObjects.AddControlPoint(new MultiplierControlPoint(time + 2000) { DifficultyPoint = { SpeedMultiplier = 2 } });
p.HitObjects.AddControlPoint(new MultiplierControlPoint(time + 3000) { DifficultyPoint = { SpeedMultiplier = 1 } });
TestDrawableControlPoint createDrawablePoint(double t)
{
var obj = new TestDrawableControlPoint(p.Direction, t);
setAnchor(obj, p);
return obj;
}
p.Add(createDrawablePoint(time));
p.Add(createDrawablePoint(time + 2000));
p.Add(createDrawablePoint(time + 3000));
});
}
private void setAnchor(DrawableHitObject obj, TestPlayfield playfield)
{
switch (playfield.Direction)
{
case ScrollingDirection.Up:
obj.Anchor = Anchor.TopCentre;
break;
case ScrollingDirection.Down:
obj.Anchor = Anchor.BottomCentre;
break;
case ScrollingDirection.Left:
obj.Anchor = Anchor.CentreLeft;
break;
case ScrollingDirection.Right:
obj.Anchor = Anchor.CentreRight;
break;
}
}
private class TestPlayfield : ScrollingPlayfield
{
public readonly ScrollingDirection Direction;
public TestPlayfield(ScrollingDirection direction)
: base(direction)
{
Direction = direction;
Padding = new MarginPadding(2);
ScaledContent.Masking = true;
AddInternal(new Box
{
RelativeSizeAxes = Axes.Both,
Alpha = 0.5f,
Depth = float.MaxValue
});
}
}
private class TestDrawableControlPoint : DrawableHitObject<HitObject>
{
public TestDrawableControlPoint(ScrollingDirection direction, double time)
: base(new HitObject { StartTime = time })
{
Origin = Anchor.Centre;
Add(new Box
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both
});
switch (direction)
{
case ScrollingDirection.Up:
case ScrollingDirection.Down:
RelativeSizeAxes = Axes.X;
Height = 2;
break;
case ScrollingDirection.Left:
case ScrollingDirection.Right:
RelativeSizeAxes = Axes.Y;
Width = 2;
break;
}
}
protected override void UpdateState(ArmedState state)
{
}
}
private class TestDrawableHitObject : DrawableHitObject<HitObject>
{
public TestDrawableHitObject(double time)
: base(new HitObject { StartTime = time })
{
Origin = Anchor.Centre;
AutoSizeAxes = Axes.Both;
Add(new Box { Size = new Vector2(75) });
}
protected override void UpdateState(ArmedState state)
{
}
}
}
}

View File

@ -1,219 +0,0 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using System.Collections.Generic;
using NUnit.Framework;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Input;
using osu.Framework.Timing;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.Timing;
using osu.Game.Rulesets.UI;
using osu.Game.Tests.Beatmaps;
using OpenTK;
namespace osu.Game.Tests.Visual
{
/// <summary>
/// The most minimal implementation of a playfield with scrolling hit objects.
/// </summary>
[TestFixture]
public class TestCaseScrollingPlayfield : OsuTestCase
{
public TestCaseScrollingPlayfield()
{
Clock = new FramedClock();
var objects = new List<HitObject>();
int time = 1500;
for (int i = 0; i < 50; i++)
{
objects.Add(new TestHitObject { StartTime = time });
time += 500;
}
Beatmap b = new Beatmap
{
HitObjects = objects,
BeatmapInfo = new BeatmapInfo
{
BaseDifficulty = new BeatmapDifficulty(),
Metadata = new BeatmapMetadata()
}
};
WorkingBeatmap beatmap = new TestWorkingBeatmap(b);
TestRulesetContainer horizontalRulesetContainer;
Add(horizontalRulesetContainer = new TestRulesetContainer(Axes.X, beatmap, true));
TestRulesetContainer verticalRulesetContainer;
Add(verticalRulesetContainer = new TestRulesetContainer(Axes.Y, beatmap, true));
AddStep("Reverse direction", () =>
{
horizontalRulesetContainer.Playfield.Reverse();
verticalRulesetContainer.Playfield.Reverse();
});
}
[Test]
public void TestSpeedAdjustmentOrdering()
{
var hitObjectContainer = new ScrollingPlayfield.ScrollingHitObjectContainer(Axes.X);
var speedAdjustments = new[]
{
new SpeedAdjustmentContainer(new MultiplierControlPoint()),
new SpeedAdjustmentContainer(new MultiplierControlPoint(1000)
{
TimingPoint = new TimingControlPoint { BeatLength = 500 }
}),
new SpeedAdjustmentContainer(new MultiplierControlPoint(2000)
{
TimingPoint = new TimingControlPoint { BeatLength = 1000 },
DifficultyPoint = new DifficultyControlPoint { SpeedMultiplier = 2}
}),
new SpeedAdjustmentContainer(new MultiplierControlPoint(3000)
{
TimingPoint = new TimingControlPoint { BeatLength = 1000 },
DifficultyPoint = new DifficultyControlPoint { SpeedMultiplier = 1}
}),
};
var hitObjects = new[]
{
new DrawableTestHitObject(Axes.X, new TestHitObject { StartTime = -1000 }),
new DrawableTestHitObject(Axes.X, new TestHitObject()),
new DrawableTestHitObject(Axes.X, new TestHitObject { StartTime = 1000 }),
new DrawableTestHitObject(Axes.X, new TestHitObject { StartTime = 2000 }),
new DrawableTestHitObject(Axes.X, new TestHitObject { StartTime = 3000 }),
new DrawableTestHitObject(Axes.X, new TestHitObject { StartTime = 4000 }),
};
hitObjects.ForEach(h => hitObjectContainer.Add(h));
speedAdjustments.ForEach(hitObjectContainer.AddSpeedAdjustment);
// The 0th index in hitObjectContainer.SpeedAdjustments is the "default" control point
// Check multiplier of the default speed adjustment
Assert.AreEqual(1, hitObjectContainer.SpeedAdjustments[0].ControlPoint.Multiplier);
Assert.AreEqual(1, speedAdjustments[0].ControlPoint.Multiplier);
Assert.AreEqual(2, speedAdjustments[1].ControlPoint.Multiplier);
Assert.AreEqual(2, speedAdjustments[2].ControlPoint.Multiplier);
Assert.AreEqual(1, speedAdjustments[3].ControlPoint.Multiplier);
// Check insertion of hit objects
Assert.IsTrue(hitObjectContainer.SpeedAdjustments[4].Contains(hitObjects[0]));
Assert.IsTrue(hitObjectContainer.SpeedAdjustments[3].Contains(hitObjects[1]));
Assert.IsTrue(hitObjectContainer.SpeedAdjustments[2].Contains(hitObjects[2]));
Assert.IsTrue(hitObjectContainer.SpeedAdjustments[1].Contains(hitObjects[3]));
Assert.IsTrue(hitObjectContainer.SpeedAdjustments[0].Contains(hitObjects[4]));
Assert.IsTrue(hitObjectContainer.SpeedAdjustments[0].Contains(hitObjects[5]));
hitObjectContainer.RemoveSpeedAdjustment(hitObjectContainer.SpeedAdjustments[3]);
// The hit object contained in this speed adjustment should be resorted into the one occuring before it
Assert.IsTrue(hitObjectContainer.SpeedAdjustments[3].Contains(hitObjects[1]));
}
private class TestRulesetContainer : ScrollingRulesetContainer<TestPlayfield, TestHitObject>
{
private readonly Axes scrollingAxes;
public TestRulesetContainer(Axes scrollingAxes, WorkingBeatmap beatmap, bool isForCurrentRuleset)
: base(null, beatmap, isForCurrentRuleset)
{
this.scrollingAxes = scrollingAxes;
}
public new TestPlayfield Playfield => base.Playfield;
public override ScoreProcessor CreateScoreProcessor() => new TestScoreProcessor();
public override PassThroughInputManager CreateInputManager() => new PassThroughInputManager();
protected override BeatmapConverter<TestHitObject> CreateBeatmapConverter() => new TestBeatmapConverter();
protected override Playfield CreatePlayfield() => new TestPlayfield(scrollingAxes);
protected override DrawableHitObject<TestHitObject> GetVisualRepresentation(TestHitObject h) => new DrawableTestHitObject(scrollingAxes, h);
}
private class TestScoreProcessor : ScoreProcessor<TestHitObject>
{
protected override void OnNewJudgement(Judgement judgement)
{
}
}
private class TestBeatmapConverter : BeatmapConverter<TestHitObject>
{
protected override IEnumerable<Type> ValidConversionTypes => new[] { typeof(HitObject) };
protected override IEnumerable<TestHitObject> ConvertHitObject(HitObject original, Beatmap beatmap)
{
yield return original as TestHitObject;
}
}
private class DrawableTestHitObject : DrawableScrollingHitObject<TestHitObject>
{
public DrawableTestHitObject(Axes scrollingAxes, TestHitObject hitObject)
: base(hitObject)
{
Anchor = scrollingAxes == Axes.Y ? Anchor.TopCentre : Anchor.CentreLeft;
Origin = Anchor.Centre;
AutoSizeAxes = Axes.Both;
Add(new Circle
{
Size = new Vector2(50)
});
}
protected override void UpdateState(ArmedState state)
{
}
}
private class TestPlayfield : ScrollingPlayfield
{
protected override Container<Drawable> Content => content;
private readonly Container<Drawable> content;
public TestPlayfield(Axes scrollingAxes)
: base(scrollingAxes)
{
InternalChildren = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Alpha = 0.2f
},
content = new Container { RelativeSizeAxes = Axes.Both }
};
}
public void Reverse() => Reversed.Toggle();
}
private class TestHitObject : HitObject
{
}
}
}

View File

@ -1,13 +1,26 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using System.Collections.Generic;
using osu.Game.Overlays;
using osu.Game.Overlays.Social;
using osu.Game.Users;
namespace osu.Game.Tests.Visual
{
public class TestCaseSocial : OsuTestCase
{
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(UserPanel),
typeof(SocialPanel),
typeof(FilterControl),
typeof(SocialOverlay),
typeof(SocialGridPanel),
typeof(SocialListPanel)
};
public TestCaseSocial()
{
SocialOverlay s = new SocialOverlay

View File

@ -141,7 +141,7 @@
<Compile Include="Visual\TestCaseResults.cs" />
<Compile Include="Visual\TestCaseRoomInspector.cs" />
<Compile Include="Visual\TestCaseScoreCounter.cs" />
<Compile Include="Visual\TestCaseScrollingPlayfield.cs" />
<Compile Include="Visual\TestCaseScrollingHitObjects.cs" />
<Compile Include="Visual\TestCaseSettings.cs" />
<Compile Include="Visual\TestCaseSkipButton.cs" />
<Compile Include="Visual\TestCaseSocial.cs" />

View File

@ -72,6 +72,8 @@ namespace osu.Game.Configuration
Set(OsuSetting.FloatingComments, false);
Set(OsuSetting.SpeedChangeVisualisation, SpeedChangeVisualisationMethod.Sequential);
// Update
Set(OsuSetting.ReleaseStream, ReleaseStream.Lazer);
@ -116,6 +118,7 @@ namespace osu.Game.Configuration
ShowFpsDisplay,
ChatDisplayHeight,
Version,
ShowConvertedBeatmaps
ShowConvertedBeatmaps,
SpeedChangeVisualisation
}
}

View File

@ -0,0 +1,15 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.ComponentModel;
namespace osu.Game.Configuration
{
public enum SpeedChangeVisualisationMethod
{
[Description("Sequential")]
Sequential,
[Description("Overlapping")]
Overlapping
}
}

View File

@ -0,0 +1,13 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Collections.Generic;
using osu.Game.Users;
namespace osu.Game.Online.API.Requests
{
public class GetFriendsRequest : APIRequest<List<User>>
{
protected override string Target => @"friends";
}
}

View File

@ -0,0 +1,26 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.Allocation;
using osu.Game.Configuration;
namespace osu.Game.Overlays.Settings.Sections.Gameplay
{
public class ScrollingSettings : SettingsSubsection
{
protected override string Header => "Scrolling";
[BackgroundDependencyLoader]
private void load(OsuConfigManager config)
{
Children = new[]
{
new SettingsEnumDropdown<SpeedChangeVisualisationMethod>
{
LabelText = "Visualise speed changes as",
Bindable = config.GetBindable<SpeedChangeVisualisationMethod>(OsuSetting.SpeedChangeVisualisation),
}
};
}
}
}

View File

@ -21,6 +21,7 @@ namespace osu.Game.Overlays.Settings.Sections
{
new GeneralSettings(),
new SongSelectSettings(),
new ScrollingSettings()
};
}

View File

@ -22,7 +22,8 @@ namespace osu.Game.Overlays.Social
public enum SocialSortCriteria
{
Rank,
//Location,
Name,
Location,
//[Description("Time Zone")]
//TimeZone,
//[Description("World Map")]

View File

@ -18,7 +18,8 @@ namespace osu.Game.Overlays.Social
protected override Color4 BackgroundColour => OsuColour.FromHex(@"38202e");
protected override float TabStripWidth => 438;
protected override SocialTab DefaultTab => SocialTab.OnlinePlayers;
protected override SocialTab DefaultTab => SocialTab.AllPlayers;
protected override FontAwesome Icon => FontAwesome.fa_users;
protected override Drawable CreateHeaderText()
@ -53,12 +54,12 @@ namespace osu.Game.Overlays.Social
public enum SocialTab
{
[Description("Online Players")]
OnlinePlayers,
//[Description("Online Friends")]
//OnlineFriends,
//[Description("Online Team Members")]
//OnlineTeamMembers,
[Description("All Players")]
AllPlayers,
[Description("Friends")]
Friends,
//[Description("Team Members")]
//TeamMembers,
//[Description("Chat Channels")]
//ChatChannels,
}

View File

@ -0,0 +1,15 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Users;
namespace osu.Game.Overlays.Social
{
public class SocialGridPanel : SocialPanel
{
public SocialGridPanel(User user) : base(user)
{
Width = 300;
}
}
}

View File

@ -0,0 +1,16 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.Graphics;
using osu.Game.Users;
namespace osu.Game.Overlays.Social
{
public class SocialListPanel : SocialPanel
{
public SocialListPanel(User user) : base(user)
{
RelativeSizeAxes = Axes.X;
}
}
}

View File

@ -0,0 +1,60 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using OpenTK;
using OpenTK.Graphics;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Input;
using osu.Game.Users;
namespace osu.Game.Overlays.Social
{
public class SocialPanel : UserPanel
{
private const double hover_transition_time = 400;
public SocialPanel(User user) : base(user)
{
}
private readonly EdgeEffectParameters edgeEffectNormal = new EdgeEffectParameters
{
Type = EdgeEffectType.Shadow,
Offset = new Vector2(0f, 1f),
Radius = 2f,
Colour = Color4.Black.Opacity(0.25f),
};
private readonly EdgeEffectParameters edgeEffectHovered = new EdgeEffectParameters
{
Type = EdgeEffectType.Shadow,
Offset = new Vector2(0f, 5f),
Radius = 10f,
Colour = Color4.Black.Opacity(0.3f),
};
protected override bool OnHover(InputState state)
{
Content.TweenEdgeEffectTo(edgeEffectHovered, hover_transition_time, Easing.OutQuint);
Content.MoveToY(-4, hover_transition_time, Easing.OutQuint);
return base.OnHover(state);
}
protected override void OnHoverLost(InputState state)
{
Content.TweenEdgeEffectTo(edgeEffectNormal, hover_transition_time, Easing.OutQuint);
Content.MoveToY(0, hover_transition_time, Easing.OutQuint);
base.OnHoverLost(state);
}
protected override void LoadComplete()
{
base.LoadComplete();
this.FadeInFromZero(200, Easing.Out);
}
}
}

View File

@ -9,19 +9,22 @@ using OpenTK.Graphics;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Graphics;
using osu.Game.Graphics.Cursor;
using osu.Game.Graphics.UserInterface;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests;
using osu.Game.Overlays.SearchableList;
using osu.Game.Overlays.Social;
using osu.Game.Users;
using osu.Framework.Configuration;
using osu.Framework.Threading;
namespace osu.Game.Overlays
{
public class SocialOverlay : SearchableListOverlay<SocialTab, SocialSortCriteria, SortDirection>, IOnlineComponent
{
private readonly FillFlowContainer<UserPanel> panelFlow;
private APIAccess api;
private readonly LoadingAnimation loading;
private FillFlowContainer<SocialPanel> panels;
protected override Color4 BackgroundColour => OsuColour.FromHex(@"60284b");
protected override Color4 TrianglesColourLight => OsuColour.FromHex(@"672b51");
@ -31,27 +34,15 @@ namespace osu.Game.Overlays
protected override SearchableListFilterControl<SocialSortCriteria, SortDirection> CreateFilterControl() => new FilterControl();
private IEnumerable<User> users;
private readonly LoadingAnimation loading;
public IEnumerable<User> Users
{
get { return users; }
set
{
if (users?.Equals(value) ?? false) return;
users = value;
if (users?.Equals(value) ?? false)
return;
if (users == null)
panelFlow.Clear();
else
{
panelFlow.ChildrenEnumerable = users.Select(u =>
{
var p = new UserPanel(u) { Width = 300 };
p.Status.BindTo(u.Status);
return p;
});
}
users = value?.ToList();
}
}
@ -62,57 +53,153 @@ namespace osu.Game.Overlays
ThirdWaveColour = OsuColour.FromHex(@"9b2b6e");
FourthWaveColour = OsuColour.FromHex(@"6d214d");
ScrollFlow.Children = new[]
Add(loading = new LoadingAnimation());
Filter.Search.Current.ValueChanged += text =>
{
new OsuContextMenuContainer
if (!string.IsNullOrEmpty(text))
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Child = panelFlow = new FillFlowContainer<UserPanel>
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Margin = new MarginPadding { Top = 20 },
Spacing = new Vector2(10f),
}
},
// force searching in players until searching for friends is supported
Header.Tabs.Current.Value = SocialTab.AllPlayers;
if (Filter.Tabs.Current.Value != SocialSortCriteria.Rank)
Filter.Tabs.Current.Value = SocialSortCriteria.Rank;
}
};
Add(loading = new LoadingAnimation());
Header.Tabs.Current.ValueChanged += tab => Scheduler.AddOnce(updateSearch);
Filter.Tabs.Current.ValueChanged += sortCriteria => Scheduler.AddOnce(updateSearch);
Filter.DisplayStyleControl.DisplayStyle.ValueChanged += recreatePanels;
Filter.DisplayStyleControl.Dropdown.Current.ValueChanged += sortOrder => Scheduler.AddOnce(updateSearch);
currentQuery.ValueChanged += query =>
{
queryChangedDebounce?.Cancel();
if (string.IsNullOrEmpty(query))
Scheduler.AddOnce(updateSearch);
else
queryChangedDebounce = Scheduler.AddDelayed(updateSearch, 500);
};
currentQuery.BindTo(Filter.Search.Current);
}
[BackgroundDependencyLoader]
private void load(APIAccess api)
{
if (Users == null)
reloadUsers(api);
this.api = api;
api.Register(this);
}
private void reloadUsers(APIAccess api)
private void recreatePanels(PanelDisplayStyle displayStyle)
{
Users = null;
clearPanels();
// no this is not the correct data source, but it's something.
var request = new GetUsersRequest();
request.Success += res =>
if (Users == null)
return;
var newPanels = new FillFlowContainer<SocialPanel>
{
Users = res.Select(e => e.User);
loading.Hide();
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Spacing = new Vector2(10f),
Margin = new MarginPadding { Top = 10 },
ChildrenEnumerable = Users.Select(u =>
{
SocialPanel panel;
switch (displayStyle)
{
case PanelDisplayStyle.Grid:
panel = new SocialGridPanel(u)
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre
};
break;
default:
panel = new SocialListPanel(u);
break;
}
panel.Status.BindTo(u.Status);
return panel;
})
};
api.Queue(request);
LoadComponentAsync(newPanels, f =>
{
if(panels != null)
ScrollFlow.Remove(panels);
ScrollFlow.Add(panels = newPanels);
});
}
private APIRequest getUsersRequest;
private readonly Bindable<string> currentQuery = new Bindable<string>();
private ScheduledDelegate queryChangedDebounce;
private void updateSearch()
{
queryChangedDebounce?.Cancel();
if (!IsLoaded)
return;
Users = null;
clearPanels();
loading.Hide();
getUsersRequest?.Cancel();
if (api?.IsLoggedIn == false)
return;
switch (Header.Tabs.Current.Value)
{
case SocialTab.Friends:
var friendRequest = new GetFriendsRequest(); // TODO filter arguments?
friendRequest.Success += updateUsers;
api.Queue(getUsersRequest = friendRequest);
break;
default:
var userRequest = new GetUsersRequest(); // TODO filter arguments!
userRequest.Success += response => updateUsers(response.Select(r => r.User));
api.Queue(getUsersRequest = userRequest);
break;
}
loading.Show();
}
private void updateUsers(IEnumerable<User> newUsers)
{
Users = newUsers;
loading.Hide();
recreatePanels(Filter.DisplayStyleControl.DisplayStyle.Value);
}
private void clearPanels()
{
if (panels != null)
{
panels.Expire();
panels = null;
}
}
public void APIStateChanged(APIAccess api, APIState state)
{
switch (state)
{
case APIState.Online:
reloadUsers(api);
Scheduler.AddOnce(updateSearch);
break;
default:
Users = null;
clearPanels();
break;
}
}
@ -120,7 +207,7 @@ namespace osu.Game.Overlays
public enum SortDirection
{
Descending,
Ascending,
Descending
}
}

View File

@ -36,12 +36,32 @@ namespace osu.Game.Rulesets.Objects.Drawables
public override bool RemoveCompletedTransforms => false;
public override bool RemoveWhenNotAlive => false;
protected override bool RequiresChildrenUpdate => true;
public virtual bool AllJudged => false;
protected DrawableHitObject(HitObject hitObject)
{
HitObject = hitObject;
}
/// <summary>
/// Processes this <see cref="DrawableHitObject"/>, checking if any judgements have occurred.
/// </summary>
/// <param name="userTriggered">Whether the user triggered this process.</param>
/// <returns>Whether a judgement has occurred from this <see cref="DrawableHitObject"/> or any nested <see cref="DrawableHitObject"/>s.</returns>
protected internal virtual bool UpdateJudgement(bool userTriggered) => false;
private List<DrawableHitObject> nestedHitObjects;
public IReadOnlyList<DrawableHitObject> NestedHitObjects => nestedHitObjects;
protected virtual void AddNested(DrawableHitObject h)
{
if (nestedHitObjects == null)
nestedHitObjects = new List<DrawableHitObject>();
nestedHitObjects.Add(h);
}
/// <summary>
/// The screen-space point that causes this <see cref="DrawableHitObject"/> to be selected in the Editor.
/// </summary>
@ -144,7 +164,7 @@ namespace osu.Game.Rulesets.Objects.Drawables
/// <summary>
/// Whether this <see cref="DrawableHitObject"/> and all of its nested <see cref="DrawableHitObject"/>s have been judged.
/// </summary>
public bool AllJudged => (!ProvidesJudgement || judgementFinalized) && (NestedHitObjects?.All(h => h.AllJudged) ?? true);
public sealed override bool AllJudged => (!ProvidesJudgement || judgementFinalized) && (NestedHitObjects?.All(h => h.AllJudged) ?? true);
/// <summary>
/// Notifies that a new judgement has occurred for this <see cref="DrawableHitObject"/>.
@ -180,7 +200,7 @@ namespace osu.Game.Rulesets.Objects.Drawables
/// </summary>
/// <param name="userTriggered">Whether the user triggered this process.</param>
/// <returns>Whether a judgement has occurred from this <see cref="DrawableHitObject"/> or any nested <see cref="DrawableHitObject"/>s.</returns>
protected bool UpdateJudgement(bool userTriggered)
protected internal sealed override bool UpdateJudgement(bool userTriggered)
{
judgementOccurred = false;
@ -237,18 +257,16 @@ namespace osu.Game.Rulesets.Objects.Drawables
UpdateJudgement(false);
}
private List<DrawableHitObject<TObject>> nestedHitObjects;
protected IEnumerable<DrawableHitObject<TObject>> NestedHitObjects => nestedHitObjects;
protected virtual void AddNested(DrawableHitObject<TObject> h)
protected override void AddNested(DrawableHitObject h)
{
if (nestedHitObjects == null)
nestedHitObjects = new List<DrawableHitObject<TObject>>();
base.AddNested(h);
h.OnJudgement += (d, j) => OnJudgement?.Invoke(d, j);
h.OnJudgementRemoved += (d, j) => OnJudgementRemoved?.Invoke(d, j);
h.ApplyCustomUpdateState += (d, s) => ApplyCustomUpdateState?.Invoke(d, s);
nestedHitObjects.Add(h);
if (!(h is DrawableHitObject<TObject> hWithJudgement))
return;
hWithJudgement.OnJudgement += (d, j) => OnJudgement?.Invoke(d, j);
hWithJudgement.OnJudgementRemoved += (d, j) => OnJudgementRemoved?.Invoke(d, j);
hWithJudgement.ApplyCustomUpdateState += (d, s) => ApplyCustomUpdateState?.Invoke(d, s);
}
/// <summary>

View File

@ -1,67 +0,0 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.Configuration;
using osu.Framework.Graphics;
using osu.Game.Rulesets.Objects.Types;
namespace osu.Game.Rulesets.Objects.Drawables
{
/// <summary>
/// A basic class that overrides <see cref="DrawableHitObject{TObject}"/> and implements <see cref="IScrollingHitObject"/>.
/// This object does not need to have its <see cref="Drawable.RelativePositionAxes"/> set to be able to scroll, as this will
/// will be set by the scrolling container that contains it.
/// </summary>
public abstract class DrawableScrollingHitObject<TObject> : DrawableHitObject<TObject>, IScrollingHitObject
where TObject : HitObject
{
public BindableDouble LifetimeOffset { get; } = new BindableDouble();
Axes IScrollingHitObject.ScrollingAxes
{
set
{
RelativePositionAxes |= value;
if ((value & Axes.X) > 0)
X = (float)HitObject.StartTime;
if ((value & Axes.Y) > 0)
Y = (float)HitObject.StartTime;
}
}
public override bool RemoveWhenNotAlive => false;
protected override bool RequiresChildrenUpdate => true;
protected DrawableScrollingHitObject(TObject hitObject)
: base(hitObject)
{
}
private double? lifetimeStart;
public override double LifetimeStart
{
get { return lifetimeStart ?? HitObject.StartTime - LifetimeOffset; }
set { lifetimeStart = value; }
}
private double? lifetimeEnd;
public override double LifetimeEnd
{
get
{
var endTime = (HitObject as IHasEndTime)?.EndTime ?? HitObject.StartTime;
return lifetimeEnd ?? endTime + LifetimeOffset;
}
set { lifetimeEnd = value; }
}
protected override void AddNested(DrawableHitObject<TObject> h)
{
var scrollingHitObject = h as IScrollingHitObject;
scrollingHitObject?.LifetimeOffset.BindTo(LifetimeOffset);
base.AddNested(h);
}
}
}

View File

@ -1,28 +0,0 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.Graphics;
namespace osu.Game.Rulesets.Timing
{
/// <summary>
/// A <see cref="ScrollingContainer"/> which scrolls linearly relative to the <see cref="MultiplierControlPoint"/> start time.
/// </summary>
public class LinearScrollingContainer : ScrollingContainer
{
private readonly MultiplierControlPoint controlPoint;
public LinearScrollingContainer(MultiplierControlPoint controlPoint)
{
this.controlPoint = controlPoint;
}
protected override void Update()
{
base.Update();
if ((ScrollingAxes & Axes.X) > 0) X = (float)(controlPoint.StartTime - Time.Current);
if ((ScrollingAxes & Axes.Y) > 0) Y = (float)(controlPoint.StartTime - Time.Current);
}
}
}

View File

@ -15,7 +15,7 @@ namespace osu.Game.Rulesets.Timing
/// <summary>
/// The time in milliseconds at which this <see cref="MultiplierControlPoint"/> starts.
/// </summary>
public readonly double StartTime;
public double StartTime;
/// <summary>
/// The multiplier which this <see cref="MultiplierControlPoint"/> provides.

View File

@ -1,93 +0,0 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using System.Linq;
using osu.Framework.Caching;
using osu.Framework.Configuration;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Objects.Drawables;
using OpenTK;
using osu.Game.Rulesets.Objects.Types;
namespace osu.Game.Rulesets.Timing
{
/// <summary>
/// A container that scrolls relative to the current time. Will autosize to the total duration of all contained hit objects along the scrolling axes.
/// </summary>
public abstract class ScrollingContainer : Container<DrawableHitObject>
{
/// <summary>
/// Gets or sets the range of time that is visible by the length of the scrolling axes.
/// </summary>
public readonly BindableDouble VisibleTimeRange = new BindableDouble { Default = 1000 };
/// <summary>
/// The axes through which this <see cref="ScrollingContainer"/> scrolls. This is set by the <see cref="SpeedAdjustmentContainer"/>.
/// </summary>
internal Axes ScrollingAxes;
public override bool RemoveWhenNotAlive => false;
protected override bool RequiresChildrenUpdate => true;
/// <summary>
/// The control point that defines the speed adjustments for this container. This is set by the <see cref="SpeedAdjustmentContainer"/>.
/// </summary>
internal MultiplierControlPoint ControlPoint;
private Cached<double> durationBacking;
/// <summary>
/// Creates a new <see cref="ScrollingContainer"/>.
/// </summary>
protected ScrollingContainer()
{
RelativeSizeAxes = Axes.Both;
RelativePositionAxes = Axes.Both;
}
protected override int Compare(Drawable x, Drawable y)
{
var hX = (DrawableHitObject)x;
var hY = (DrawableHitObject)y;
int result = hY.HitObject.StartTime.CompareTo(hX.HitObject.StartTime);
if (result != 0)
return result;
return base.Compare(y, x);
}
public override void Add(DrawableHitObject drawable)
{
durationBacking.Invalidate();
base.Add(drawable);
}
public override bool Remove(DrawableHitObject drawable)
{
durationBacking.Invalidate();
return base.Remove(drawable);
}
// Todo: This may underestimate the size of the hit object in some cases, but won't be too much of a problem for now
private double computeDuration() => Math.Max(0, Children.Select(c => (c.HitObject as IHasEndTime)?.EndTime ?? c.HitObject.StartTime).DefaultIfEmpty().Max() - ControlPoint.StartTime) + 1000;
/// <summary>
/// An approximate total duration of this scrolling container.
/// </summary>
public double Duration => durationBacking.IsValid ? durationBacking : (durationBacking.Value = computeDuration());
protected override void Update()
{
base.Update();
RelativeChildOffset = new Vector2((ScrollingAxes & Axes.X) > 0 ? (float)ControlPoint.StartTime : 0, (ScrollingAxes & Axes.Y) > 0 ? (float)ControlPoint.StartTime : 0);
// We want our size and position-space along the scrolling axes to span our duration to completely enclose all the hit objects
Size = new Vector2((ScrollingAxes & Axes.X) > 0 ? (float)Duration : Size.X, (ScrollingAxes & Axes.Y) > 0 ? (float)Duration : Size.Y);
// And we need to make sure the hit object's position-space doesn't change due to our resizing
RelativeChildSize = Size;
}
}
}

View File

@ -1,114 +0,0 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.Configuration;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Objects.Drawables;
using OpenTK;
namespace osu.Game.Rulesets.Timing
{
/// <summary>
/// A container that provides the speed adjustments defined by <see cref="MultiplierControlPoint"/>s to affect the scroll speed
/// of container <see cref="DrawableHitObject"/>s.
/// </summary>
public class SpeedAdjustmentContainer : Container<DrawableHitObject>
{
/// <summary>
/// Gets or sets the range of time that is visible by the length of the scrolling axes.
/// </summary>
public readonly Bindable<double> VisibleTimeRange = new Bindable<double> { Default = 1000 };
/// <summary>
/// Whether to reverse the scrolling direction is reversed.
/// </summary>
public readonly BindableBool Reversed = new BindableBool();
protected override Container<DrawableHitObject> Content => content;
private readonly Container<DrawableHitObject> content;
/// <summary>
/// The axes which the content of this container will scroll through.
/// </summary>
public Axes ScrollingAxes
{
get { return scrollingContainer.ScrollingAxes; }
set { scrollingContainer.ScrollingAxes = value; }
}
public override bool RemoveWhenNotAlive => false;
protected override bool RequiresChildrenUpdate => true;
/// <summary>
/// The <see cref="MultiplierControlPoint"/> that defines the speed adjustments.
/// </summary>
public readonly MultiplierControlPoint ControlPoint;
private readonly ScrollingContainer scrollingContainer;
/// <summary>
/// Creates a new <see cref="SpeedAdjustmentContainer"/>.
/// </summary>
/// <param name="controlPoint">The <see cref="MultiplierControlPoint"/> that defines the speed adjustments.</param>
public SpeedAdjustmentContainer(MultiplierControlPoint controlPoint)
{
ControlPoint = controlPoint;
RelativeSizeAxes = Axes.Both;
scrollingContainer = CreateScrollingContainer();
scrollingContainer.ControlPoint = ControlPoint;
scrollingContainer.VisibleTimeRange.BindTo(VisibleTimeRange);
AddInternal(content = scrollingContainer);
}
protected override void Update()
{
float multiplier = (float)ControlPoint.Multiplier;
// The speed adjustment happens by modifying our size by the multiplier while maintaining the visible time range as the relatve size for our children
Size = new Vector2((ScrollingAxes & Axes.X) > 0 ? multiplier : 1, (ScrollingAxes & Axes.Y) > 0 ? multiplier : 1);
if (Reversed)
{
RelativeChildSize = new Vector2((ScrollingAxes & Axes.X) > 0 ? (float)-VisibleTimeRange : 1, (ScrollingAxes & Axes.Y) > 0 ? (float)-VisibleTimeRange : 1);
RelativeChildOffset = new Vector2((ScrollingAxes & Axes.X) > 0 ? (float)VisibleTimeRange : 0, (ScrollingAxes & Axes.Y) > 0 ? (float)VisibleTimeRange : 0);
Origin = Anchor = Anchor.BottomRight;
}
else
{
RelativeChildSize = new Vector2((ScrollingAxes & Axes.X) > 0 ? (float)VisibleTimeRange : 1, (ScrollingAxes & Axes.Y) > 0 ? (float)VisibleTimeRange : 1);
RelativeChildOffset = Vector2.Zero;
Origin = Anchor = Anchor.TopLeft;
}
}
public override double LifetimeStart => ControlPoint.StartTime - VisibleTimeRange;
public override double LifetimeEnd => ControlPoint.StartTime + scrollingContainer.Duration + VisibleTimeRange;
public override void Add(DrawableHitObject drawable)
{
var scrollingHitObject = drawable as IScrollingHitObject;
if (scrollingHitObject != null)
{
scrollingHitObject.LifetimeOffset.BindTo(VisibleTimeRange);
scrollingHitObject.ScrollingAxes = ScrollingAxes;
}
base.Add(drawable);
}
/// <summary>
/// Whether a point in time falls within this <see cref="SpeedAdjustmentContainer"/>s affecting timespan.
/// </summary>
public bool CanContain(double startTime) => ControlPoint.StartTime <= startTime;
/// <summary>
/// Creates the <see cref="ScrollingContainer"/> which contains the scrolling <see cref="DrawableHitObject"/>s of this container.
/// </summary>
/// <returns>The <see cref="ScrollingContainer"/>.</returns>
protected virtual ScrollingContainer CreateScrollingContainer() => new LinearScrollingContainer(ControlPoint);
}
}

View File

@ -0,0 +1,19 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Objects.Drawables;
namespace osu.Game.Rulesets.UI
{
public class HitObjectContainer : CompositeDrawable
{
public virtual IEnumerable<DrawableHitObject> Objects => InternalChildren.Cast<DrawableHitObject>();
public virtual IEnumerable<DrawableHitObject> AliveObjects => AliveInternalChildren.Cast<DrawableHitObject>();
public virtual void Add(DrawableHitObject hitObject) => AddInternal(hitObject);
public virtual bool Remove(DrawableHitObject hitObject) => RemoveInternal(hitObject);
}
}

View File

@ -8,8 +8,6 @@ using osu.Game.Rulesets.Objects.Drawables;
using OpenTK;
using osu.Game.Rulesets.Judgements;
using osu.Framework.Allocation;
using System.Collections.Generic;
using System.Linq;
namespace osu.Game.Rulesets.UI
{
@ -18,7 +16,7 @@ namespace osu.Game.Rulesets.UI
/// <summary>
/// The HitObjects contained in this Playfield.
/// </summary>
public HitObjectContainer HitObjects { get; protected set; }
public HitObjectContainer HitObjects { get; private set; }
public Container<Drawable> ScaledContent;
@ -51,16 +49,14 @@ namespace osu.Game.Rulesets.UI
}
}
});
HitObjects = new HitObjectContainer
{
RelativeSizeAxes = Axes.Both,
};
}
[BackgroundDependencyLoader]
private void load()
{
HitObjects = CreateHitObjectContainer();
HitObjects.RelativeSizeAxes = Axes.Both;
Add(HitObjects);
}
@ -94,12 +90,10 @@ namespace osu.Game.Rulesets.UI
/// <param name="judgement">The <see cref="Judgement"/> that occurred.</param>
public virtual void OnJudgement(DrawableHitObject judgedObject, Judgement judgement) { }
public class HitObjectContainer : CompositeDrawable
{
public virtual IEnumerable<DrawableHitObject> Objects => InternalChildren.OfType<DrawableHitObject>();
public virtual void Add(DrawableHitObject hitObject) => AddInternal(hitObject);
public virtual bool Remove(DrawableHitObject hitObject) => RemoveInternal(hitObject);
}
/// <summary>
/// Creates the container that will be used to contain the <see cref="DrawableHitObject"/>s.
/// </summary>
protected virtual HitObjectContainer CreateHitObjectContainer() => new HitObjectContainer();
private class ScaledContainer : Container
{

View File

@ -0,0 +1,25 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
namespace osu.Game.Rulesets.UI.Scrolling
{
public enum ScrollingDirection
{
/// <summary>
/// Hitobjects will scroll vertically from the bottom of the hitobject container.
/// </summary>
Up,
/// <summary>
/// Hitobjects will scroll vertically from the top of the hitobject container.
/// </summary>
Down,
/// <summary>
/// Hitobjects will scroll horizontally from the right of the hitobject container.
/// </summary>
Left,
/// <summary>
/// Hitobjects will scroll horizontally from the left of the hitobject container.
/// </summary>
Right
}
}

View File

@ -0,0 +1,116 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.Allocation;
using osu.Framework.Caching;
using osu.Framework.Configuration;
using osu.Framework.Graphics;
using osu.Framework.Lists;
using osu.Game.Configuration;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Timing;
using osu.Game.Rulesets.UI.Scrolling.Visualisers;
namespace osu.Game.Rulesets.UI.Scrolling
{
public class ScrollingHitObjectContainer : HitObjectContainer
{
/// <summary>
/// The duration required to scroll through one length of the <see cref="ScrollingHitObjectContainer"/> before any control point adjustments.
/// </summary>
public readonly BindableDouble TimeRange = new BindableDouble
{
MinValue = 0,
MaxValue = double.MaxValue
};
/// <summary>
/// The control points that adjust the scrolling speed.
/// </summary>
protected readonly SortedList<MultiplierControlPoint> ControlPoints = new SortedList<MultiplierControlPoint>();
private readonly ScrollingDirection direction;
private Cached initialStateCache = new Cached();
public ScrollingHitObjectContainer(ScrollingDirection direction)
{
this.direction = direction;
RelativeSizeAxes = Axes.Both;
TimeRange.ValueChanged += v => initialStateCache.Invalidate();
}
private ISpeedChangeVisualiser speedChangeVisualiser;
[BackgroundDependencyLoader]
private void load(OsuConfigManager config)
{
switch (config.Get<SpeedChangeVisualisationMethod>(OsuSetting.SpeedChangeVisualisation))
{
case SpeedChangeVisualisationMethod.Sequential:
speedChangeVisualiser = new SequentialSpeedChangeVisualiser(ControlPoints);
break;
case SpeedChangeVisualisationMethod.Overlapping:
speedChangeVisualiser = new OverlappingSpeedChangeVisualiser(ControlPoints);
break;
}
}
public override void Add(DrawableHitObject hitObject)
{
initialStateCache.Invalidate();
base.Add(hitObject);
}
public override bool Remove(DrawableHitObject hitObject)
{
var result = base.Remove(hitObject);
if (result)
initialStateCache.Invalidate();
return result;
}
public void AddControlPoint(MultiplierControlPoint controlPoint)
{
ControlPoints.Add(controlPoint);
initialStateCache.Invalidate();
}
public bool RemoveControlPoint(MultiplierControlPoint controlPoint)
{
var result = ControlPoints.Remove(controlPoint);
if (result)
initialStateCache.Invalidate();
return result;
}
public override bool Invalidate(Invalidation invalidation = Invalidation.All, Drawable source = null, bool shallPropagate = true)
{
if ((invalidation & (Invalidation.RequiredParentSizeToFit | Invalidation.DrawInfo)) > 0)
initialStateCache.Invalidate();
return base.Invalidate(invalidation, source, shallPropagate);
}
protected override void Update()
{
base.Update();
if (!initialStateCache.IsValid)
{
speedChangeVisualiser.ComputeInitialStates(Objects, direction, TimeRange, DrawSize);
initialStateCache.Validate();
}
}
protected override void UpdateAfterChildrenLife()
{
base.UpdateAfterChildrenLife();
// We need to calculate this as soon as possible after lifetimes so that hitobjects get the final say in their positions
speedChangeVisualiser.ComputePositions(AliveObjects, direction, Time.Current, TimeRange, DrawSize);
}
}
}

View File

@ -0,0 +1,142 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Collections.Generic;
using osu.Framework.Allocation;
using osu.Framework.Configuration;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Transforms;
using osu.Framework.Input;
using osu.Framework.MathUtils;
using osu.Game.Rulesets.Objects.Drawables;
using OpenTK.Input;
namespace osu.Game.Rulesets.UI.Scrolling
{
/// <summary>
/// A type of <see cref="Playfield"/> specialized towards scrolling <see cref="DrawableHitObject"/>s.
/// </summary>
public abstract class ScrollingPlayfield : Playfield
{
/// <summary>
/// The default span of time visible by the length of the scrolling axes.
/// This is clamped between <see cref="time_span_min"/> and <see cref="time_span_max"/>.
/// </summary>
private const double time_span_default = 1500;
/// <summary>
/// The minimum span of time that may be visible by the length of the scrolling axes.
/// </summary>
private const double time_span_min = 50;
/// <summary>
/// The maximum span of time that may be visible by the length of the scrolling axes.
/// </summary>
private const double time_span_max = 10000;
/// <summary>
/// The step increase/decrease of the span of time visible by the length of the scrolling axes.
/// </summary>
private const double time_span_step = 50;
/// <summary>
/// The span of time that is visible by the length of the scrolling axes.
/// For example, only hit objects with start time less than or equal to 1000 will be visible with <see cref="VisibleTimeRange"/> = 1000.
/// </summary>
public readonly BindableDouble VisibleTimeRange = new BindableDouble(time_span_default)
{
Default = time_span_default,
MinValue = time_span_min,
MaxValue = time_span_max
};
/// <summary>
/// Whether the player can change <see cref="VisibleTimeRange"/>.
/// </summary>
protected virtual bool UserScrollSpeedAdjustment => true;
/// <summary>
/// The container that contains the <see cref="SpeedAdjustmentContainer"/>s and <see cref="DrawableHitObject"/>s.
/// </summary>
public new ScrollingHitObjectContainer HitObjects => (ScrollingHitObjectContainer)base.HitObjects;
private readonly ScrollingDirection direction;
/// <summary>
/// Creates a new <see cref="ScrollingPlayfield"/>.
/// </summary>
/// <param name="scrollingAxes">The axes on which <see cref="DrawableHitObject"/>s in this container should scroll.</param>
/// <param name="customWidth">Whether we want our internal coordinate system to be scaled to a specified width</param>
protected ScrollingPlayfield(ScrollingDirection direction, float? customWidth = null)
: base(customWidth)
{
this.direction = direction;
}
[BackgroundDependencyLoader]
private void load()
{
HitObjects.TimeRange.BindTo(VisibleTimeRange);
}
private List<ScrollingPlayfield> nestedPlayfields;
/// <summary>
/// All the <see cref="ScrollingPlayfield"/>s nested inside this playfield.
/// </summary>
public IEnumerable<ScrollingPlayfield> NestedPlayfields => nestedPlayfields;
/// <summary>
/// Adds a <see cref="ScrollingPlayfield"/> to this playfield. The nested <see cref="ScrollingPlayfield"/>
/// will be given all of the same speed adjustments as this playfield.
/// </summary>
/// <param name="otherPlayfield">The <see cref="ScrollingPlayfield"/> to add.</param>
protected void AddNested(ScrollingPlayfield otherPlayfield)
{
if (nestedPlayfields == null)
nestedPlayfields = new List<ScrollingPlayfield>();
nestedPlayfields.Add(otherPlayfield);
}
protected override bool OnKeyDown(InputState state, KeyDownEventArgs args)
{
if (!UserScrollSpeedAdjustment)
return false;
if (state.Keyboard.ControlPressed)
{
switch (args.Key)
{
case Key.Minus:
transformVisibleTimeRangeTo(VisibleTimeRange + time_span_step, 200, Easing.OutQuint);
break;
case Key.Plus:
transformVisibleTimeRangeTo(VisibleTimeRange - time_span_step, 200, Easing.OutQuint);
break;
}
}
return false;
}
private void transformVisibleTimeRangeTo(double newTimeRange, double duration = 0, Easing easing = Easing.None)
{
this.TransformTo(this.PopulateTransform(new TransformVisibleTimeRange(), newTimeRange, duration, easing));
}
protected sealed override HitObjectContainer CreateHitObjectContainer() => new ScrollingHitObjectContainer(direction);
private class TransformVisibleTimeRange : Transform<double, ScrollingPlayfield>
{
private double valueAt(double time)
{
if (time < StartTime) return StartValue;
if (time >= EndTime) return EndValue;
return Interpolation.ValueAt(time, StartValue, EndValue, StartTime, EndTime, Easing);
}
public override string TargetMember => "VisibleTimeRange.Value";
protected override void Apply(ScrollingPlayfield d, double time) => d.VisibleTimeRange.Value = valueAt(time);
protected override void ReadIntoStartValue(ScrollingPlayfield d) => StartValue = d.VisibleTimeRange.Value;
}
}
}

View File

@ -13,7 +13,7 @@ using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Timing;
namespace osu.Game.Rulesets.UI
namespace osu.Game.Rulesets.UI.Scrolling
{
/// <summary>
/// A type of <see cref="RulesetContainer{TPlayfield,TObject}"/> that supports a <see cref="ScrollingPlayfield"/>.
@ -70,12 +70,10 @@ namespace osu.Game.Rulesets.UI
// Perform some post processing of the timing changes
timingChanges = timingChanges
// Collapse sections after the last hit object
.Where(s => s.StartTime <= lastObjectTime)
// Collapse sections with the same start time
.GroupBy(s => s.StartTime).Select(g => g.Last()).OrderBy(s => s.StartTime)
// Collapse sections with the same beat length
.GroupBy(s => s.TimingPoint.BeatLength * s.DifficultyPoint.SpeedMultiplier).Select(g => g.First());
// Collapse sections after the last hit object
.Where(s => s.StartTime <= lastObjectTime)
// Collapse sections with the same start time
.GroupBy(s => s.StartTime).Select(g => g.Last()).OrderBy(s => s.StartTime);
DefaultControlPoints.AddRange(timingChanges);
@ -88,7 +86,7 @@ namespace osu.Game.Rulesets.UI
private void applySpeedAdjustment(MultiplierControlPoint controlPoint, ScrollingPlayfield playfield)
{
playfield.HitObjects.AddSpeedAdjustment(CreateSpeedAdjustmentContainer(controlPoint));
playfield.HitObjects.AddControlPoint(controlPoint);
playfield.NestedPlayfields.ForEach(p => applySpeedAdjustment(controlPoint, p));
}
@ -108,12 +106,5 @@ namespace osu.Game.Rulesets.UI
return new MultiplierControlPoint(time, DefaultControlPoints[index].DeepClone());
}
/// <summary>
/// Creates a <see cref="SpeedAdjustmentContainer"/> that facilitates the movement of hit objects.
/// </summary>
/// <param name="controlPoint">The <see cref="MultiplierControlPoint"/> that provides the speed adjustments for the hitobjects.</param>
/// <returns>The <see cref="SpeedAdjustmentContainer"/>.</returns>
protected virtual SpeedAdjustmentContainer CreateSpeedAdjustmentContainer(MultiplierControlPoint controlPoint) => new SpeedAdjustmentContainer(controlPoint);
}
}

View File

@ -0,0 +1,33 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Collections.Generic;
using osu.Game.Rulesets.Objects.Drawables;
using OpenTK;
namespace osu.Game.Rulesets.UI.Scrolling.Visualisers
{
public interface ISpeedChangeVisualiser
{
/// <summary>
/// Computes the states of <see cref="DrawableHitObject"/>s that are constant, such as lifetime and spatial length.
/// This is invoked once whenever <paramref name="timeRange"/> or <paramref name="length"/> changes.
/// </summary>
/// <param name="hitObjects">The <see cref="DrawableHitObject"/>s whose states should be computed.</param>
/// <param name="direction">The scrolling direction.</param>
/// <param name="timeRange">The duration required to scroll through one length of the screen before any control point adjustments.</param>
/// <param name="length">The length of the screen that is scrolled through.</param>
void ComputeInitialStates(IEnumerable<DrawableHitObject> hitObjects, ScrollingDirection direction, double timeRange, Vector2 length);
/// <summary>
/// Computes the states of <see cref="DrawableHitObject"/>s that change depending on <paramref name="currentTime"/>, such as position.
/// This is invoked once per frame.
/// </summary>
/// <param name="hitObjects">The <see cref="DrawableHitObject"/>s whose states should be computed.</param>
/// <param name="direction">The scrolling direction.</param>
/// <param name="currentTime">The current time.</param>
/// <param name="timeRange">The duration required to scroll through one length of the screen before any control point adjustments.</param>
/// <param name="length">The length of the screen that is scrolled through.</param>
void ComputePositions(IEnumerable<DrawableHitObject> hitObjects, ScrollingDirection direction, double currentTime, double timeRange, Vector2 length);
}
}

View File

@ -0,0 +1,80 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Collections.Generic;
using osu.Framework.Lists;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Timing;
using OpenTK;
namespace osu.Game.Rulesets.UI.Scrolling.Visualisers
{
public class OverlappingSpeedChangeVisualiser : ISpeedChangeVisualiser
{
private readonly SortedList<MultiplierControlPoint> controlPoints;
public OverlappingSpeedChangeVisualiser(SortedList<MultiplierControlPoint> controlPoints)
{
this.controlPoints = controlPoints;
}
public void ComputeInitialStates(IEnumerable<DrawableHitObject> hitObjects, ScrollingDirection direction, double timeRange, Vector2 length)
{
foreach (var obj in hitObjects)
{
var controlPoint = controlPointAt(obj.HitObject.StartTime);
obj.LifetimeStart = obj.HitObject.StartTime - timeRange / controlPoint.Multiplier;
if (obj.NestedHitObjects != null)
{
ComputeInitialStates(obj.NestedHitObjects, direction, timeRange, length);
ComputePositions(obj.NestedHitObjects, direction, obj.HitObject.StartTime, timeRange, length);
}
}
}
public void ComputePositions(IEnumerable<DrawableHitObject> hitObjects, ScrollingDirection direction, double currentTime, double timeRange, Vector2 length)
{
foreach (var obj in hitObjects)
{
var controlPoint = controlPointAt(obj.HitObject.StartTime);
var position = (obj.HitObject.StartTime - currentTime) * controlPoint.Multiplier / timeRange;
switch (direction)
{
case ScrollingDirection.Up:
obj.Y = (float)(position * length.Y);
break;
case ScrollingDirection.Down:
obj.Y = (float)(-position * length.Y);
break;
case ScrollingDirection.Left:
obj.X = (float)(position * length.X);
break;
case ScrollingDirection.Right:
obj.X = (float)(-position * length.X);
break;
}
}
}
private readonly MultiplierControlPoint searchPoint = new MultiplierControlPoint();
private MultiplierControlPoint controlPointAt(double time)
{
if (controlPoints.Count == 0)
return new MultiplierControlPoint(double.NegativeInfinity);
if (time < controlPoints[0].StartTime)
return controlPoints[0];
searchPoint.StartTime = time;
int index = controlPoints.BinarySearch(searchPoint);
if (index < 0)
index = ~index - 1;
return controlPoints[index];
}
}
}

View File

@ -0,0 +1,103 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using System.Collections.Generic;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Timing;
using OpenTK;
namespace osu.Game.Rulesets.UI.Scrolling.Visualisers
{
public class SequentialSpeedChangeVisualiser : ISpeedChangeVisualiser
{
private readonly Dictionary<DrawableHitObject, double> hitObjectPositions = new Dictionary<DrawableHitObject, double>();
private readonly IReadOnlyList<MultiplierControlPoint> controlPoints;
public SequentialSpeedChangeVisualiser(IReadOnlyList<MultiplierControlPoint> controlPoints)
{
this.controlPoints = controlPoints;
}
public void ComputeInitialStates(IEnumerable<DrawableHitObject> hitObjects, ScrollingDirection direction, double timeRange, Vector2 length)
{
foreach (var obj in hitObjects)
{
var startPosition = hitObjectPositions[obj] = positionAt(obj.HitObject.StartTime, timeRange);
obj.LifetimeStart = obj.HitObject.StartTime - timeRange - 1000;
if (obj.HitObject is IHasEndTime endTime)
{
var diff = positionAt(endTime.EndTime, timeRange) - startPosition;
switch (direction)
{
case ScrollingDirection.Up:
case ScrollingDirection.Down:
obj.Height = (float)(diff * length.Y);
break;
case ScrollingDirection.Left:
case ScrollingDirection.Right:
obj.Width = (float)(diff * length.X);
break;
}
}
if (obj.NestedHitObjects != null)
{
ComputeInitialStates(obj.NestedHitObjects, direction, timeRange, length);
ComputePositions(obj.NestedHitObjects, direction, obj.HitObject.StartTime, timeRange, length);
}
}
}
public void ComputePositions(IEnumerable<DrawableHitObject> hitObjects, ScrollingDirection direction, double currentTime, double timeRange, Vector2 length)
{
var timelinePosition = positionAt(currentTime, timeRange);
foreach (var obj in hitObjects)
{
var finalPosition = hitObjectPositions[obj] - timelinePosition;
switch (direction)
{
case ScrollingDirection.Up:
obj.Y = (float)(finalPosition * length.Y);
break;
case ScrollingDirection.Down:
obj.Y = (float)(-finalPosition * length.Y);
break;
case ScrollingDirection.Left:
obj.X = (float)(finalPosition * length.X);
break;
case ScrollingDirection.Right:
obj.X = (float)(-finalPosition * length.X);
break;
}
}
}
private double positionAt(double time, double timeRange)
{
double length = 0;
for (int i = 0; i < controlPoints.Count; i++)
{
var current = controlPoints[i];
var next = i < controlPoints.Count - 1 ? controlPoints[i + 1] : null;
if (i > 0 && current.StartTime > time)
continue;
// Duration of the current control point
var currentDuration = (next?.StartTime ?? double.PositiveInfinity) - current.StartTime;
length += Math.Min(currentDuration, time - current.StartTime) * current.Multiplier / timeRange;
}
return length;
}
}
}

View File

@ -1,266 +0,0 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using System.Collections.Generic;
using System.Linq;
using OpenTK.Input;
using osu.Framework.Configuration;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Transforms;
using osu.Framework.Input;
using osu.Framework.MathUtils;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Timing;
namespace osu.Game.Rulesets.UI
{
/// <summary>
/// A type of <see cref="Playfield"/> specialized towards scrolling <see cref="DrawableHitObject"/>s.
/// </summary>
public class ScrollingPlayfield : Playfield
{
/// <summary>
/// The default span of time visible by the length of the scrolling axes.
/// This is clamped between <see cref="time_span_min"/> and <see cref="time_span_max"/>.
/// </summary>
private const double time_span_default = 1500;
/// <summary>
/// The minimum span of time that may be visible by the length of the scrolling axes.
/// </summary>
private const double time_span_min = 50;
/// <summary>
/// The maximum span of time that may be visible by the length of the scrolling axes.
/// </summary>
private const double time_span_max = 10000;
/// <summary>
/// The step increase/decrease of the span of time visible by the length of the scrolling axes.
/// </summary>
private const double time_span_step = 50;
/// <summary>
/// The span of time that is visible by the length of the scrolling axes.
/// For example, only hit objects with start time less than or equal to 1000 will be visible with <see cref="VisibleTimeRange"/> = 1000.
/// </summary>
public readonly BindableDouble VisibleTimeRange = new BindableDouble(time_span_default)
{
Default = time_span_default,
MinValue = time_span_min,
MaxValue = time_span_max
};
/// <summary>
/// Whether to reverse the scrolling direction is reversed. Note that this does _not_ invert the hit objects.
/// </summary>
protected readonly BindableBool Reversed = new BindableBool();
/// <summary>
/// The container that contains the <see cref="SpeedAdjustmentContainer"/>s and <see cref="DrawableHitObject"/>s.
/// </summary>
public new readonly ScrollingHitObjectContainer HitObjects;
/// <summary>
/// Creates a new <see cref="ScrollingPlayfield"/>.
/// </summary>
/// <param name="scrollingAxes">The axes on which <see cref="DrawableHitObject"/>s in this container should scroll.</param>
/// <param name="customWidth">Whether we want our internal coordinate system to be scaled to a specified width</param>
protected ScrollingPlayfield(Axes scrollingAxes, float? customWidth = null)
: base(customWidth)
{
base.HitObjects = HitObjects = new ScrollingHitObjectContainer(scrollingAxes) { RelativeSizeAxes = Axes.Both };
HitObjects.VisibleTimeRange.BindTo(VisibleTimeRange);
HitObjects.Reversed.BindTo(Reversed);
}
private List<ScrollingPlayfield> nestedPlayfields;
/// <summary>
/// All the <see cref="ScrollingPlayfield"/>s nested inside this playfield.
/// </summary>
public IEnumerable<ScrollingPlayfield> NestedPlayfields => nestedPlayfields;
/// <summary>
/// Adds a <see cref="ScrollingPlayfield"/> to this playfield. The nested <see cref="ScrollingPlayfield"/>
/// will be given all of the same speed adjustments as this playfield.
/// </summary>
/// <param name="otherPlayfield">The <see cref="ScrollingPlayfield"/> to add.</param>
protected void AddNested(ScrollingPlayfield otherPlayfield)
{
if (nestedPlayfields == null)
nestedPlayfields = new List<ScrollingPlayfield>();
nestedPlayfields.Add(otherPlayfield);
}
protected override bool OnKeyDown(InputState state, KeyDownEventArgs args)
{
if (state.Keyboard.ControlPressed)
{
switch (args.Key)
{
case Key.Minus:
transformVisibleTimeRangeTo(VisibleTimeRange + time_span_step, 200, Easing.OutQuint);
break;
case Key.Plus:
transformVisibleTimeRangeTo(VisibleTimeRange - time_span_step, 200, Easing.OutQuint);
break;
}
}
return false;
}
private void transformVisibleTimeRangeTo(double newTimeRange, double duration = 0, Easing easing = Easing.None)
{
this.TransformTo(this.PopulateTransform(new TransformVisibleTimeRange(), newTimeRange, duration, easing));
}
private class TransformVisibleTimeRange : Transform<double, ScrollingPlayfield>
{
private double valueAt(double time)
{
if (time < StartTime) return StartValue;
if (time >= EndTime) return EndValue;
return Interpolation.ValueAt(time, StartValue, EndValue, StartTime, EndTime, Easing);
}
public override string TargetMember => "VisibleTimeRange.Value";
protected override void Apply(ScrollingPlayfield d, double time) => d.VisibleTimeRange.Value = valueAt(time);
protected override void ReadIntoStartValue(ScrollingPlayfield d) => StartValue = d.VisibleTimeRange.Value;
}
/// <summary>
/// A container that provides the foundation for sorting <see cref="DrawableHitObject"/>s into <see cref="SpeedAdjustmentContainer"/>s.
/// </summary>
public class ScrollingHitObjectContainer : HitObjectContainer
{
/// <summary>
/// Gets or sets the range of time that is visible by the length of the scrolling axes.
/// For example, only hit objects with start time less than or equal to 1000 will be visible with <see cref="VisibleTimeRange"/> = 1000.
/// </summary>
public readonly BindableDouble VisibleTimeRange = new BindableDouble { Default = 1000 };
/// <summary>
/// Whether to reverse the scrolling direction is reversed.
/// </summary>
public readonly BindableBool Reversed = new BindableBool();
private readonly SortedContainer speedAdjustments;
public IReadOnlyList<SpeedAdjustmentContainer> SpeedAdjustments => speedAdjustments;
private readonly SpeedAdjustmentContainer defaultSpeedAdjustment;
private readonly Axes scrollingAxes;
/// <summary>
/// Creates a new <see cref="ScrollingHitObjectContainer"/>.
/// </summary>
/// <param name="scrollingAxes">The axes upon which hit objects should appear to scroll inside this container.</param>
public ScrollingHitObjectContainer(Axes scrollingAxes)
{
this.scrollingAxes = scrollingAxes;
AddInternal(speedAdjustments = new SortedContainer { RelativeSizeAxes = Axes.Both });
// Default speed adjustment
AddSpeedAdjustment(defaultSpeedAdjustment = new SpeedAdjustmentContainer(new MultiplierControlPoint(0)));
}
/// <summary>
/// Adds a <see cref="SpeedAdjustmentContainer"/> to this container, re-sorting all hit objects
/// in the last <see cref="SpeedAdjustmentContainer"/> that occurred (time-wise) before it.
/// </summary>
/// <param name="speedAdjustment">The <see cref="SpeedAdjustmentContainer"/>.</param>
public void AddSpeedAdjustment(SpeedAdjustmentContainer speedAdjustment)
{
speedAdjustment.ScrollingAxes = scrollingAxes;
speedAdjustment.VisibleTimeRange.BindTo(VisibleTimeRange);
speedAdjustment.Reversed.BindTo(Reversed);
if (speedAdjustments.Count > 0)
{
// We need to re-sort all hit objects in the speed adjustment container prior to figure out if they
// should now lie within this one
var existingAdjustment = adjustmentContainerAt(speedAdjustment.ControlPoint.StartTime);
for (int i = 0; i < existingAdjustment.Count; i++)
{
DrawableHitObject hitObject = existingAdjustment[i];
if (!speedAdjustment.CanContain(hitObject.HitObject.StartTime))
continue;
existingAdjustment.Remove(hitObject);
speedAdjustment.Add(hitObject);
i--;
}
}
speedAdjustments.Add(speedAdjustment);
}
/// <summary>
/// Removes a <see cref="SpeedAdjustmentContainer"/> from this container, re-sorting all hit objects
/// which it contained into new <see cref="SpeedAdjustmentContainer"/>s.
/// </summary>
/// <param name="speedAdjustment">The <see cref="SpeedAdjustmentContainer"/> to remove.</param>
public void RemoveSpeedAdjustment(SpeedAdjustmentContainer speedAdjustment)
{
if (speedAdjustment == defaultSpeedAdjustment)
throw new InvalidOperationException($"The default {nameof(SpeedAdjustmentContainer)} must not be removed.");
if (!speedAdjustments.Remove(speedAdjustment))
return;
while (speedAdjustment.Count > 0)
{
DrawableHitObject hitObject = speedAdjustment[0];
speedAdjustment.Remove(hitObject);
Add(hitObject);
}
}
public override IEnumerable<DrawableHitObject> Objects => speedAdjustments.SelectMany(s => s.Children);
/// <summary>
/// Adds a hit object to this <see cref="ScrollingHitObjectContainer"/>. The hit objects will be queued to be processed
/// new <see cref="SpeedAdjustmentContainer"/>s are added to this <see cref="ScrollingHitObjectContainer"/>.
/// </summary>
/// <param name="hitObject">The hit object to add.</param>
public override void Add(DrawableHitObject hitObject)
{
if (!(hitObject is IScrollingHitObject))
throw new InvalidOperationException($"Hit objects added to a {nameof(ScrollingHitObjectContainer)} must implement {nameof(IScrollingHitObject)}.");
adjustmentContainerAt(hitObject.HitObject.StartTime).Add(hitObject);
}
public override bool Remove(DrawableHitObject hitObject) => speedAdjustments.Any(s => s.Remove(hitObject));
/// <summary>
/// Finds the <see cref="SpeedAdjustmentContainer"/> which provides the speed adjustment active at a time.
/// If there is no <see cref="SpeedAdjustmentContainer"/> active at the time, then the first (time-wise) speed adjustment is returned.
/// </summary>
/// <param name="time">The time to find the active <see cref="SpeedAdjustmentContainer"/> at.</param>
/// <returns>The <see cref="SpeedAdjustmentContainer"/> active at <paramref name="time"/>. Null if there are no speed adjustments.</returns>
private SpeedAdjustmentContainer adjustmentContainerAt(double time) => speedAdjustments.FirstOrDefault(c => c.CanContain(time)) ?? defaultSpeedAdjustment;
private class SortedContainer : Container<SpeedAdjustmentContainer>
{
protected override int Compare(Drawable x, Drawable y)
{
var sX = (SpeedAdjustmentContainer)x;
var sY = (SpeedAdjustmentContainer)y;
int result = sY.ControlPoint.StartTime.CompareTo(sX.ControlPoint.StartTime);
if (result != 0)
return result;
return base.Compare(y, x);
}
}
}
}
}

View File

@ -394,9 +394,8 @@ namespace osu.Game.Screens.Play
.FadeColour(OsuColour.Gray(opacity), duration, Easing.OutQuint)
.FadeTo(storyboardVisible && opacity > 0 ? 1 : 0, duration, Easing.OutQuint);
(Background as BackgroundScreenBeatmap)?
.BlurTo(new Vector2((float)blurLevel.Value * 25), duration, Easing.OutQuint)?
.FadeTo(!storyboardVisible || beatmap.Background == null ? opacity : 0, duration, Easing.OutQuint);
(Background as BackgroundScreenBeatmap)?.BlurTo(new Vector2((float)blurLevel.Value * 25), duration, Easing.OutQuint);
Background?.FadeTo(!storyboardVisible || beatmap.Background == null ? opacity : 0, duration, Easing.OutQuint);
}
private void fadeOut()

View File

@ -44,7 +44,7 @@ namespace osu.Game.Users
new Avatar(user)
{
RelativeSizeAxes = Axes.Both,
OnLoadComplete = d => d.FadeInFromZero(200),
OnLoadComplete = d => d.FadeInFromZero(300, Easing.OutQuint),
})
);
}

View File

@ -39,10 +39,14 @@ namespace osu.Game.Users
public string AvatarUrl;
[JsonProperty(@"cover_url")]
public string CoverUrl;
public string CoverUrl
{
get { return Cover?.Url; }
set { Cover = new UserCover { Url = value }; }
}
//[JsonProperty(@"cover")]
//public UserCover Cover;
[JsonProperty(@"cover")]
public UserCover Cover;
public class UserCover
{

View File

@ -28,9 +28,12 @@ namespace osu.Game.Users
private const float content_padding = 10;
private const float status_height = 30;
private readonly Container statusBar;
private readonly Box statusBg;
private readonly OsuSpriteText statusMessage;
private Container statusBar;
private Box statusBg;
private OsuSpriteText statusMessage;
private Container content;
protected override Container<Drawable> Content => content;
public readonly Bindable<UserStatus> Status = new Bindable<UserStatus>();
@ -45,133 +48,7 @@ namespace osu.Game.Users
this.user = user;
FillFlowContainer infoContainer;
Height = height - status_height;
Masking = true;
CornerRadius = 5;
EdgeEffect = new EdgeEffectParameters
{
Type = EdgeEffectType.Shadow,
Colour = Color4.Black.Opacity(0.25f),
Radius = 4,
};
Children = new Drawable[]
{
new DelayedLoadWrapper(new UserCoverBackground(user)
{
RelativeSizeAxes = Axes.Both,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
FillMode = FillMode.Fill,
OnLoadComplete = d => d.FadeInFromZero(200),
}, 0) { RelativeSizeAxes = Axes.Both },
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Color4.Black.Opacity(0.7f),
},
new Container
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Padding = new MarginPadding { Top = content_padding, Left = content_padding, Right = content_padding },
Children = new Drawable[]
{
new UpdateableAvatar
{
Size = new Vector2(height - status_height - content_padding * 2),
User = user,
Masking = true,
CornerRadius = 5,
EdgeEffect = new EdgeEffectParameters
{
Type = EdgeEffectType.Shadow,
Colour = Color4.Black.Opacity(0.25f),
Radius = 4,
},
},
new Container
{
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding { Left = height - status_height - content_padding },
Children = new Drawable[]
{
new OsuSpriteText
{
Text = user.Username,
TextSize = 18,
Font = @"Exo2.0-SemiBoldItalic",
},
infoContainer = new FillFlowContainer
{
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
AutoSizeAxes = Axes.X,
Height = 20f,
Direction = FillDirection.Horizontal,
Spacing = new Vector2(5f, 0f),
Children = new Drawable[]
{
new DrawableFlag(user.Country)
{
Width = 30f,
RelativeSizeAxes = Axes.Y,
},
},
},
},
},
},
},
statusBar = new Container
{
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
RelativeSizeAxes = Axes.X,
Alpha = 0f,
Children = new Drawable[]
{
statusBg = new Box
{
RelativeSizeAxes = Axes.Both,
Alpha = 0.5f,
},
new FillFlowContainer
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
AutoSizeAxes = Axes.Both,
Spacing = new Vector2(5f, 0f),
Children = new Drawable[]
{
new SpriteIcon
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
Icon = FontAwesome.fa_circle_o,
Shadow = true,
Size = new Vector2(14),
},
statusMessage = new OsuSpriteText
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
Font = @"Exo2.0-Semibold",
},
},
},
},
},
};
if (user.IsSupporter)
infoContainer.Add(new SupporterIcon
{
RelativeSizeAxes = Axes.Y,
Width = 20f,
});
}
[BackgroundDependencyLoader(permitNulls: true)]
@ -180,6 +57,139 @@ namespace osu.Game.Users
if (colours == null)
throw new ArgumentNullException(nameof(colours));
FillFlowContainer infoContainer;
AddInternal(content = new Container
{
RelativeSizeAxes = Axes.Both,
Masking = true,
CornerRadius = 5,
EdgeEffect = new EdgeEffectParameters
{
Type = EdgeEffectType.Shadow,
Colour = Color4.Black.Opacity(0.25f),
Radius = 4,
},
Children = new Drawable[]
{
new DelayedLoadWrapper(new UserCoverBackground(user)
{
RelativeSizeAxes = Axes.Both,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
FillMode = FillMode.Fill,
OnLoadComplete = d => d.FadeInFromZero(400, Easing.Out)
}, 300) { RelativeSizeAxes = Axes.Both },
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Color4.Black.Opacity(0.7f),
},
new Container
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Padding = new MarginPadding { Top = content_padding, Horizontal = content_padding },
Children = new Drawable[]
{
new UpdateableAvatar
{
Size = new Vector2(height - status_height - content_padding * 2),
User = user,
Masking = true,
CornerRadius = 5,
EdgeEffect = new EdgeEffectParameters
{
Type = EdgeEffectType.Shadow,
Colour = Color4.Black.Opacity(0.25f),
Radius = 4,
},
},
new Container
{
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding { Left = height - status_height - content_padding },
Children = new Drawable[]
{
new OsuSpriteText
{
Text = user.Username,
TextSize = 18,
Font = @"Exo2.0-SemiBoldItalic",
},
infoContainer = new FillFlowContainer
{
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
AutoSizeAxes = Axes.X,
Height = 20f,
Direction = FillDirection.Horizontal,
Spacing = new Vector2(5f, 0f),
Children = new Drawable[]
{
new DrawableFlag(user.Country)
{
Width = 30f,
RelativeSizeAxes = Axes.Y,
},
},
},
},
},
},
},
statusBar = new Container
{
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
RelativeSizeAxes = Axes.X,
Alpha = 0f,
Children = new Drawable[]
{
statusBg = new Box
{
RelativeSizeAxes = Axes.Both,
Alpha = 0.5f,
},
new FillFlowContainer
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
AutoSizeAxes = Axes.Both,
Spacing = new Vector2(5f, 0f),
Children = new Drawable[]
{
new SpriteIcon
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
Icon = FontAwesome.fa_circle_o,
Shadow = true,
Size = new Vector2(14),
},
statusMessage = new OsuSpriteText
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
Font = @"Exo2.0-Semibold",
},
},
},
},
},
}
});
if (user.IsSupporter)
{
infoContainer.Add(new SupporterIcon
{
RelativeSizeAxes = Axes.Y,
Width = 20f,
});
}
Status.ValueChanged += displayStatus;
Status.ValueChanged += status => statusBg.FadeColour(status?.GetAppropriateColour(colours) ?? colours.Gray5, 500, Easing.OutQuint);

View File

@ -265,10 +265,12 @@
<Compile Include="Beatmaps\Formats\JsonBeatmapDecoder.cs" />
<Compile Include="Beatmaps\Formats\LegacyDecoder.cs" />
<Compile Include="Beatmaps\Formats\LegacyStoryboardDecoder.cs" />
<Compile Include="Configuration\SpeedChangeVisualisationMethod.cs" />
<Compile Include="Database\DatabaseContextFactory.cs" />
<Compile Include="Database\IHasPrimaryKey.cs" />
<Compile Include="Graphics\Textures\LargeTextureStore.cs" />
<Compile Include="Overlays\Profile\SupporterIcon.cs" />
<Compile Include="Online\API\Requests\GetFriendsRequest.cs" />
<Compile Include="Overlays\Settings\DangerousSettingsButton.cs" />
<Compile Include="Graphics\UserInterface\HoverClickSounds.cs" />
<Compile Include="Graphics\UserInterface\HoverSounds.cs" />
@ -310,11 +312,23 @@
<Compile Include="Overlays\Profile\Sections\Ranks\PaginatedScoreContainer.cs" />
<Compile Include="Overlays\Profile\Sections\Ranks\DrawableTotalScore.cs" />
<Compile Include="Overlays\Profile\Sections\Ranks\ScoreModsContainer.cs" />
<Compile Include="Overlays\Settings\Sections\Gameplay\ScrollingSettings.cs" />
<Compile Include="Overlays\Settings\Sections\Maintenance\DeleteAllBeatmapsDialog.cs" />
<Compile Include="Rulesets\Mods\IApplicableFailOverride.cs" />
<Compile Include="Rulesets\Mods\IApplicableMod.cs" />
<Compile Include="Rulesets\Mods\IApplicableToBeatmapConverter.cs" />
<Compile Include="Overlays\Social\SocialGridPanel.cs" />
<Compile Include="Overlays\Social\SocialListPanel.cs" />
<Compile Include="Overlays\Social\SocialPanel.cs" />
<Compile Include="Rulesets\Mods\IApplicableToDrawableHitObject.cs" />
<Compile Include="Rulesets\UI\HitObjectContainer.cs" />
<Compile Include="Rulesets\UI\Scrolling\Visualisers\SequentialSpeedChangeVisualiser.cs" />
<Compile Include="Rulesets\UI\Scrolling\Visualisers\ISpeedChangeVisualiser.cs" />
<Compile Include="Rulesets\UI\Scrolling\Visualisers\OverlappingSpeedChangeVisualiser.cs" />
<Compile Include="Rulesets\UI\Scrolling\ScrollingDirection.cs" />
<Compile Include="Rulesets\UI\Scrolling\ScrollingHitObjectContainer.cs" />
<Compile Include="Rulesets\UI\Scrolling\ScrollingPlayfield.cs" />
<Compile Include="Rulesets\UI\Scrolling\ScrollingRulesetContainer.cs" />
<Compile Include="Screens\Select\ImportFromStablePopup.cs" />
<Compile Include="Overlays\Settings\SettingsButton.cs" />
<Compile Include="Rulesets\Edit\Layers\Selection\OriginHandle.cs" />
@ -615,7 +629,6 @@
<Compile Include="Rulesets\Objects\CircularArcApproximator.cs" />
<Compile Include="Rulesets\Objects\Drawables\ArmedState.cs" />
<Compile Include="Rulesets\Objects\Drawables\DrawableHitObject.cs" />
<Compile Include="Rulesets\Objects\Drawables\DrawableScrollingHitObject.cs" />
<Compile Include="Rulesets\Scoring\HitResult.cs" />
<Compile Include="Rulesets\Objects\Drawables\IDrawableHitObjectWithProxiedApproach.cs" />
<Compile Include="Rulesets\Objects\Drawables\IScrollingHitObject.cs" />
@ -666,16 +679,11 @@
<Compile Include="Rulesets\Scoring\ScoreProcessor.cs" />
<Compile Include="Rulesets\Scoring\ScoreRank.cs" />
<Compile Include="Rulesets\Scoring\ScoreStore.cs" />
<Compile Include="Rulesets\Timing\LinearScrollingContainer.cs" />
<Compile Include="Rulesets\Timing\MultiplierControlPoint.cs" />
<Compile Include="Rulesets\Timing\ScrollingContainer.cs" />
<Compile Include="Rulesets\Timing\SpeedAdjustmentContainer.cs" />
<Compile Include="Rulesets\UI\ModIcon.cs" />
<Compile Include="Rulesets\UI\Playfield.cs" />
<Compile Include="Rulesets\UI\RulesetContainer.cs" />
<Compile Include="Rulesets\UI\RulesetInputManager.cs" />
<Compile Include="Rulesets\UI\ScrollingPlayfield.cs" />
<Compile Include="Rulesets\UI\ScrollingRulesetContainer.cs" />
<Compile Include="Screens\BackgroundScreen.cs" />
<Compile Include="Screens\Backgrounds\BackgroundScreenBeatmap.cs" />
<Compile Include="Screens\Backgrounds\BackgroundScreenCustom.cs" />