1
0
mirror of https://github.com/ppy/osu.git synced 2025-01-27 17:53:15 +08:00

Merge remote-tracking branch 'upstream/master' into ranks-section

This commit is contained in:
Dean Herbert 2017-10-13 18:46:43 +09:00
commit 520b806305
163 changed files with 7946 additions and 956 deletions

23
.vscode/launch.json vendored
View File

@ -1,7 +1,7 @@
{ {
"version": "0.2.0", "version": "0.2.0",
"configurations": [{ "configurations": [{
"name": "osu! (VisualTests)", "name": "osu! VisualTests (Debug)",
"windows": { "windows": {
"type": "clr" "type": "clr"
}, },
@ -18,7 +18,24 @@
"console": "internalConsole" "console": "internalConsole"
}, },
{ {
"name": "osu! (debug)", "name": "osu! VisualTests (Release)",
"windows": {
"type": "clr"
},
"type": "mono",
"request": "launch",
"program": "${workspaceRoot}/osu.Game/bin/Release/osu!.exe",
"args": [
"--tests"
],
"cwd": "${workspaceRoot}",
"preLaunchTask": "Build (Release)",
"runtimeExecutable": null,
"env": {},
"console": "internalConsole"
},
{
"name": "osu! (Debug)",
"windows": { "windows": {
"type": "clr" "type": "clr"
}, },
@ -32,7 +49,7 @@
"console": "internalConsole" "console": "internalConsole"
}, },
{ {
"name": "osu! (release)", "name": "osu! (Release)",
"windows": { "windows": {
"type": "clr" "type": "clr"
}, },

1
.vscode/tasks.json vendored
View File

@ -23,6 +23,7 @@
}, },
{ {
"taskName": "Build (Release)", "taskName": "Build (Release)",
"group": "build",
"args": [ "args": [
"/property:Configuration=Release" "/property:Configuration=Release"
], ],

@ -1 +1 @@
Subproject commit e1352a8b0b5d1ba8acd9335a56c714d2ccc2f6a6 Subproject commit 3760443ea9bf682a1bbf6cfa6aa00ea9541c12aa

View File

@ -5,9 +5,9 @@ using osu.Game.Beatmaps;
using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.Objects;
using System.Collections.Generic; using System.Collections.Generic;
using System; using System;
using osu.Game.Rulesets.Catch.UI;
using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Osu.UI;
namespace osu.Game.Rulesets.Catch.Beatmaps namespace osu.Game.Rulesets.Catch.Beatmaps
{ {
@ -17,14 +17,37 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
protected override IEnumerable<CatchBaseHit> ConvertHitObject(HitObject obj, Beatmap beatmap) protected override IEnumerable<CatchBaseHit> ConvertHitObject(HitObject obj, Beatmap beatmap)
{ {
if (!(obj is IHasXPosition)) var curveData = obj as IHasCurve;
var positionData = obj as IHasPosition;
var comboData = obj as IHasCombo;
if (positionData == null)
yield break; yield break;
if (curveData != null)
{
yield return new JuiceStream
{
StartTime = obj.StartTime,
Samples = obj.Samples,
ControlPoints = curveData.ControlPoints,
CurveType = curveData.CurveType,
Distance = curveData.Distance,
RepeatSamples = curveData.RepeatSamples,
RepeatCount = curveData.RepeatCount,
X = positionData.X / CatchPlayfield.BASE_WIDTH,
NewCombo = comboData?.NewCombo ?? false
};
yield break;
}
yield return new Fruit yield return new Fruit
{ {
StartTime = obj.StartTime, StartTime = obj.StartTime,
NewCombo = (obj as IHasCombo)?.NewCombo ?? false, Samples = obj.Samples,
X = ((IHasXPosition)obj).X / OsuPlayfield.BASE_SIZE.X NewCombo = comboData?.NewCombo ?? false,
X = positionData.X / CatchPlayfield.BASE_WIDTH
}; };
} }
} }

View File

@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Catch.Mods
public class CatchModHidden : ModHidden public class CatchModHidden : ModHidden
{ {
public override string Description => @"Play with no approach circles and fading notes for a slight score advantage."; public override string Description => @"Play with fading notes for a slight score advantage.";
public override double ScoreMultiplier => 1.06; public override double ScoreMultiplier => 1.06;
} }

View File

@ -0,0 +1,60 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using osu.Framework.Graphics;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects.Drawables;
namespace osu.Game.Rulesets.Catch.Objects.Drawable
{
public abstract class DrawableCatchHitObject<TObject> : DrawableCatchHitObject
where TObject : CatchBaseHit
{
public new TObject HitObject;
protected DrawableCatchHitObject(TObject hitObject)
: base(hitObject)
{
HitObject = hitObject;
}
}
public abstract class DrawableCatchHitObject : DrawableScrollingHitObject<CatchBaseHit>
{
protected DrawableCatchHitObject(CatchBaseHit hitObject)
: base(hitObject)
{
RelativePositionAxes = Axes.Both;
X = hitObject.X;
Y = (float)HitObject.StartTime;
}
public Func<CatchBaseHit, bool> CheckPosition;
protected override void CheckForJudgements(bool userTriggered, double timeOffset)
{
if (timeOffset > 0)
AddJudgement(new Judgement { Result = CheckPosition?.Invoke(HitObject) ?? false ? HitResult.Perfect : HitResult.Miss });
}
private const float preempt = 1000;
protected override void UpdateState(ArmedState state)
{
using (BeginAbsoluteSequence(HitObject.StartTime - preempt))
{
// animation
this.FadeIn(200);
}
switch (state)
{
case ArmedState.Miss:
using (BeginAbsoluteSequence(HitObject.StartTime, true))
this.FadeOut(250).RotateTo(Rotation * 2, 250, Easing.Out);
break;
}
}
}
}

View File

@ -0,0 +1,34 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Game.Rulesets.Catch.Objects.Drawable.Pieces;
using OpenTK;
namespace osu.Game.Rulesets.Catch.Objects.Drawable
{
public class DrawableDroplet : DrawableCatchHitObject<Droplet>
{
public DrawableDroplet(Droplet h)
: base(h)
{
Origin = Anchor.Centre;
Size = new Vector2(Pulp.PULP_SIZE);
AccentColour = h.ComboColour;
Masking = false;
}
[BackgroundDependencyLoader]
private void load()
{
Child = new Pulp
{
AccentColour = AccentColour,
Scale = new Vector2(0.8f),
};
}
}
}

View File

@ -1,72 +1,29 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.MathUtils; using osu.Framework.MathUtils;
using osu.Game.Graphics; using osu.Game.Rulesets.Catch.Objects.Drawable.Pieces;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects.Drawables;
using OpenTK; using OpenTK;
using OpenTK.Graphics;
namespace osu.Game.Rulesets.Catch.Objects.Drawable namespace osu.Game.Rulesets.Catch.Objects.Drawable
{ {
public class DrawableFruit : DrawableScrollingHitObject<CatchBaseHit> public class DrawableFruit : DrawableCatchHitObject<Fruit>
{ {
private const float pulp_size = 20; public DrawableFruit(Fruit h)
private class Pulp : Circle, IHasAccentColour
{
public Pulp()
{
Size = new Vector2(pulp_size);
Blending = BlendingMode.Additive;
Colour = Color4.White.Opacity(0.9f);
}
private Color4 accentColour;
public Color4 AccentColour
{
get { return accentColour; }
set
{
accentColour = value;
EdgeEffect = new EdgeEffectParameters
{
Type = EdgeEffectType.Glow,
Radius = 5,
Colour = accentColour.Lighten(100),
};
}
}
}
public DrawableFruit(CatchBaseHit h)
: base(h) : base(h)
{ {
Origin = Anchor.Centre; Origin = Anchor.Centre;
Size = new Vector2(pulp_size * 2.2f, pulp_size * 2.8f);
RelativePositionAxes = Axes.Both;
X = h.X;
Size = new Vector2(Pulp.PULP_SIZE * 2.2f, Pulp.PULP_SIZE * 2.8f);
AccentColour = HitObject.ComboColour; AccentColour = HitObject.ComboColour;
Masking = false; Masking = false;
Rotation = (float)(RNG.NextDouble() - 0.5f) * 40; Rotation = (float)(RNG.NextDouble() - 0.5f) * 40;
} }
public Func<CatchBaseHit, bool> CheckPosition;
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load() private void load()
{ {
@ -114,30 +71,5 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
} }
}; };
} }
private const float preempt = 1000;
protected override void CheckForJudgements(bool userTriggered, double timeOffset)
{
if (timeOffset > 0)
AddJudgement(new Judgement { Result = CheckPosition?.Invoke(HitObject) ?? false ? HitResult.Perfect : HitResult.Miss });
}
protected override void UpdateState(ArmedState state)
{
using (BeginAbsoluteSequence(HitObject.StartTime - preempt))
{
// animation
this.FadeIn(200);
}
switch (state)
{
case ArmedState.Miss:
using (BeginAbsoluteSequence(HitObject.StartTime, true))
this.FadeOut(250).RotateTo(Rotation * 2, 250, Easing.Out);
break;
}
}
} }
} }

View File

@ -0,0 +1,54 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using OpenTK;
using osu.Game.Rulesets.Objects.Drawables;
namespace osu.Game.Rulesets.Catch.Objects.Drawable
{
public class DrawableJuiceStream : DrawableCatchHitObject<JuiceStream>
{
private readonly Container dropletContainer;
public DrawableJuiceStream(JuiceStream s) : base(s)
{
RelativeSizeAxes = Axes.Both;
Height = (float)HitObject.Duration;
X = 0;
Child = dropletContainer = new Container
{
RelativeSizeAxes = Axes.Both,
RelativeChildOffset = new Vector2(0, (float)HitObject.StartTime),
RelativeChildSize = new Vector2(1, (float)HitObject.Duration)
};
foreach (CatchBaseHit tick in s.Ticks)
{
TinyDroplet tiny = tick as TinyDroplet;
if (tiny != null)
{
AddNested(new DrawableDroplet(tiny) { Scale = new Vector2(0.5f) });
continue;
}
Droplet droplet = tick as Droplet;
if (droplet != null)
AddNested(new DrawableDroplet(droplet));
Fruit fruit = tick as Fruit;
if (fruit != null)
AddNested(new DrawableFruit(fruit));
}
}
protected override void AddNested(DrawableHitObject<CatchBaseHit> h)
{
((DrawableCatchHitObject)h).CheckPosition = o => CheckPosition?.Invoke(o) ?? false;
dropletContainer.Add(h);
base.AddNested(h);
}
}
}

View File

@ -0,0 +1,43 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Game.Graphics;
using OpenTK;
using OpenTK.Graphics;
namespace osu.Game.Rulesets.Catch.Objects.Drawable.Pieces
{
public class Pulp : Circle, IHasAccentColour
{
public const float PULP_SIZE = 20;
public Pulp()
{
Size = new Vector2(PULP_SIZE);
Blending = BlendingMode.Additive;
Colour = Color4.White.Opacity(0.9f);
}
private Color4 accentColour;
public Color4 AccentColour
{
get { return accentColour; }
set
{
accentColour = value;
EdgeEffect = new EdgeEffectParameters
{
Type = EdgeEffectType.Glow,
Radius = 5,
Colour = accentColour.Lighten(100),
};
}
}
}
}

View File

@ -0,0 +1,169 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using System.Collections.Generic;
using System.Linq;
using osu.Game.Audio;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Catch.UI;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
using OpenTK;
using osu.Framework.Lists;
namespace osu.Game.Rulesets.Catch.Objects
{
public class JuiceStream : CatchBaseHit, IHasCurve
{
/// <summary>
/// Positional distance that results in a duration of one second, before any speed adjustments.
/// </summary>
private const float base_scoring_distance = 100;
public readonly SliderCurve Curve = new SliderCurve();
public int RepeatCount { get; set; } = 1;
public double Velocity;
public double TickDistance;
public override void ApplyDefaults(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty)
{
base.ApplyDefaults(controlPointInfo, difficulty);
TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(StartTime);
DifficultyControlPoint difficultyPoint = controlPointInfo.DifficultyPointAt(StartTime);
double scoringDistance = base_scoring_distance * difficulty.SliderMultiplier * difficultyPoint.SpeedMultiplier;
Velocity = scoringDistance / timingPoint.BeatLength;
TickDistance = scoringDistance / difficulty.SliderTickRate;
}
public IEnumerable<CatchBaseHit> Ticks
{
get
{
SortedList<CatchBaseHit> ticks = new SortedList<CatchBaseHit>((a, b) => a.StartTime.CompareTo(b.StartTime));
if (TickDistance == 0)
return ticks;
var length = Curve.Distance;
var tickDistance = Math.Min(TickDistance, length);
var repeatDuration = length / Velocity;
var minDistanceFromEnd = Velocity * 0.01;
ticks.Add(new Fruit
{
Samples = Samples,
ComboColour = ComboColour,
StartTime = StartTime,
X = X
});
for (var repeat = 0; repeat < RepeatCount; repeat++)
{
var repeatStartTime = StartTime + repeat * repeatDuration;
var reversed = repeat % 2 == 1;
for (var d = tickDistance; d <= length; d += tickDistance)
{
if (d > length - minDistanceFromEnd)
break;
var timeProgress = d / length;
var distanceProgress = reversed ? 1 - timeProgress : timeProgress;
var lastTickTime = repeatStartTime + timeProgress * repeatDuration;
ticks.Add(new Droplet
{
StartTime = lastTickTime,
ComboColour = ComboColour,
X = Curve.PositionAt(distanceProgress).X / CatchPlayfield.BASE_WIDTH,
Samples = new SampleInfoList(Samples.Select(s => new SampleInfo
{
Bank = s.Bank,
Name = @"slidertick",
Volume = s.Volume
}))
});
}
double tinyTickInterval = tickDistance / length * repeatDuration;
while (tinyTickInterval > 100)
tinyTickInterval /= 2;
for (double t = 0; t < repeatDuration; t += tinyTickInterval)
{
double progress = reversed ? 1 - t / repeatDuration : t / repeatDuration;
ticks.Add(new TinyDroplet
{
StartTime = repeatStartTime + t,
ComboColour = ComboColour,
X = Curve.PositionAt(progress).X / CatchPlayfield.BASE_WIDTH,
Samples = new SampleInfoList(Samples.Select(s => new SampleInfo
{
Bank = s.Bank,
Name = @"slidertick",
Volume = s.Volume
}))
});
}
ticks.Add(new Fruit
{
Samples = Samples,
ComboColour = ComboColour,
StartTime = repeatStartTime + repeatDuration,
X = Curve.PositionAt(reversed ? 0 : 1).X / CatchPlayfield.BASE_WIDTH
});
}
return ticks;
}
}
public double EndTime => StartTime + RepeatCount * Curve.Distance / Velocity;
public float EndX => Curve.PositionAt(ProgressAt(1)).X / CatchPlayfield.BASE_WIDTH;
public double Duration => EndTime - StartTime;
public double Distance
{
get { return Curve.Distance; }
set { Curve.Distance = value; }
}
public List<Vector2> ControlPoints
{
get { return Curve.ControlPoints; }
set { Curve.ControlPoints = value; }
}
public List<SampleInfoList> RepeatSamples { get; set; } = new List<SampleInfoList>();
public CurveType CurveType
{
get { return Curve.CurveType; }
set { Curve.CurveType = value; }
}
public Vector2 PositionAt(double progress) => Curve.PositionAt(ProgressAt(progress));
public double ProgressAt(double progress)
{
double p = progress * RepeatCount % 1;
if (RepeatAt(progress) % 2 == 1)
p = 1 - p;
return p;
}
public int RepeatAt(double progress) => (int)(progress * RepeatCount);
}
}

View File

@ -0,0 +1,9 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
namespace osu.Game.Rulesets.Catch.Objects
{
public class TinyDroplet : Droplet
{
}
}

View File

@ -21,6 +21,19 @@ namespace osu.Game.Rulesets.Catch.Scoring
{ {
foreach (var obj in beatmap.HitObjects) foreach (var obj in beatmap.HitObjects)
{ {
var stream = obj as JuiceStream;
if (stream != null)
{
AddJudgement(new CatchJudgement { Result = HitResult.Perfect });
AddJudgement(new CatchJudgement { Result = HitResult.Perfect });
foreach (var unused in stream.Ticks)
AddJudgement(new CatchJudgement { Result = HitResult.Perfect });
continue;
}
var fruit = obj as Fruit; var fruit = obj as Fruit;
if (fruit != null) if (fruit != null)

View File

@ -2,22 +2,14 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using NUnit.Framework; using NUnit.Framework;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Catch.Objects;
namespace osu.Game.Rulesets.Catch.Tests namespace osu.Game.Rulesets.Catch.Tests
{ {
[TestFixture] [TestFixture]
public class TestCaseCatchPlayer : Game.Tests.Visual.TestCasePlayer public class TestCaseCatchPlayer : Game.Tests.Visual.TestCasePlayer
{ {
protected override Beatmap CreateBeatmap() public TestCaseCatchPlayer() : base(typeof(CatchRuleset))
{ {
var beatmap = new Beatmap();
for (int i = 0; i < 256; i++)
beatmap.HitObjects.Add(new Fruit { X = 0.5f, StartTime = i * 100, NewCombo = i % 8 == 0 });
return beatmap;
} }
} }
} }

View File

@ -0,0 +1,27 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using NUnit.Framework;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Catch.Objects;
namespace osu.Game.Rulesets.Catch.Tests
{
[TestFixture]
public class TestCaseCatchStacker : Game.Tests.Visual.TestCasePlayer
{
public TestCaseCatchStacker() : base(typeof(CatchRuleset))
{
}
protected override Beatmap CreateBeatmap()
{
var beatmap = new Beatmap();
for (int i = 0; i < 256; i++)
beatmap.HitObjects.Add(new Fruit { X = 0.5f, StartTime = i * 100, NewCombo = i % 8 == 0 });
return beatmap;
}
}
}

View File

@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Catch.Tests
{ {
public override IReadOnlyList<Type> RequiredTypes => new[] public override IReadOnlyList<Type> RequiredTypes => new[]
{ {
typeof(CatcherArea), typeof(Catcher),
}; };
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Catch.Tests
new CatchInputManager(rulesets.GetRuleset(2)) new CatchInputManager(rulesets.GetRuleset(2))
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Child = new CatcherArea Child = new Catcher
{ {
RelativePositionAxes = Axes.Both, RelativePositionAxes = Axes.Both,
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,

View File

@ -1,10 +1,12 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI;
using OpenTK; using OpenTK;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Catch.Objects.Drawable; using osu.Game.Rulesets.Catch.Objects.Drawable;
using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Drawables;
@ -13,16 +15,20 @@ namespace osu.Game.Rulesets.Catch.UI
{ {
public class CatchPlayfield : ScrollingPlayfield public class CatchPlayfield : ScrollingPlayfield
{ {
public static readonly float BASE_WIDTH = 512;
protected override Container<Drawable> Content => content; protected override Container<Drawable> Content => content;
private readonly Container<Drawable> content; private readonly Container<Drawable> content;
private readonly CatcherArea catcherArea;
private readonly Container catcherContainer;
private readonly Catcher catcher;
public CatchPlayfield() public CatchPlayfield()
: base(Axes.Y) : base(Axes.Y)
{ {
Reversed.Value = true; Container explodingFruitContainer;
Size = new Vector2(1); Reversed.Value = true;
Anchor = Anchor.TopCentre; Anchor = Anchor.TopCentre;
Origin = Anchor.TopCentre; Origin = Anchor.TopCentre;
@ -33,24 +39,43 @@ namespace osu.Game.Rulesets.Catch.UI
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
}, },
catcherArea = new CatcherArea explodingFruitContainer = new Container
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
},
catcherContainer = new Container
{
RelativeSizeAxes = Axes.X,
Anchor = Anchor.BottomLeft, Anchor = Anchor.BottomLeft,
Origin = Anchor.TopLeft, Origin = Anchor.TopLeft,
Height = 0.3f Height = 180,
Child = catcher = new Catcher
{
ExplodingFruitTarget = explodingFruitContainer,
RelativePositionAxes = Axes.Both,
Origin = Anchor.TopCentre,
X = 0.5f,
}
} }
}; };
} }
protected override void Update()
{
base.Update();
catcher.Size = new Vector2(catcherContainer.DrawSize.Y);
}
public bool CheckIfWeCanCatch(CatchBaseHit obj) => Math.Abs(catcher.Position.X - obj.X) < catcher.DrawSize.X / DrawSize.X / 2;
public override void Add(DrawableHitObject h) public override void Add(DrawableHitObject h)
{ {
h.Depth = (float)h.HitObject.StartTime; h.Depth = (float)h.HitObject.StartTime;
base.Add(h); base.Add(h);
var fruit = (DrawableFruit)h; var fruit = (DrawableCatchHitObject)h;
fruit.CheckPosition = catcherArea.CheckIfWeCanCatch; fruit.CheckPosition = CheckIfWeCanCatch;
} }
public override void OnJudgement(DrawableHitObject judgedObject, Judgement judgement) public override void OnJudgement(DrawableHitObject judgedObject, Judgement judgement)
@ -58,8 +83,12 @@ namespace osu.Game.Rulesets.Catch.UI
if (judgement.IsHit) if (judgement.IsHit)
{ {
Vector2 screenPosition = judgedObject.ScreenSpaceDrawQuad.Centre; Vector2 screenPosition = judgedObject.ScreenSpaceDrawQuad.Centre;
Remove(judgedObject);
catcherArea.Add(judgedObject, screenPosition); // todo: don't do this
(judgedObject.Parent as Container<DrawableHitObject>)?.Remove(judgedObject);
(judgedObject.Parent as Container)?.Remove(judgedObject);
catcher.Add(judgedObject, screenPosition);
} }
} }
} }

View File

@ -32,8 +32,13 @@ namespace osu.Game.Rulesets.Catch.UI
protected override DrawableHitObject<CatchBaseHit> GetVisualRepresentation(CatchBaseHit h) protected override DrawableHitObject<CatchBaseHit> GetVisualRepresentation(CatchBaseHit h)
{ {
if (h is Fruit) var fruit = h as Fruit;
return new DrawableFruit(h); if (fruit != null)
return new DrawableFruit(fruit);
var stream = h as JuiceStream;
if (stream != null)
return new DrawableJuiceStream(stream);
return null; return null;
} }

View File

@ -0,0 +1,193 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Textures;
using osu.Framework.Input.Bindings;
using osu.Framework.MathUtils;
using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Objects.Drawables;
using OpenTK;
namespace osu.Game.Rulesets.Catch.UI
{
public class Catcher : Container, IKeyBindingHandler<CatchAction>
{
private Texture texture;
private Container<DrawableHitObject> caughtFruit;
public Container ExplodingFruitTarget;
[BackgroundDependencyLoader]
private void load(TextureStore textures)
{
texture = textures.Get(@"Play/Catch/fruit-catcher-idle");
Children = new Drawable[]
{
createCatcherSprite(),
caughtFruit = new Container<DrawableHitObject>
{
Anchor = Anchor.TopCentre,
Origin = Anchor.BottomCentre,
}
};
}
private int currentDirection;
private bool dashing;
protected bool Dashing
{
get { return dashing; }
set
{
if (value == dashing) return;
dashing = value;
if (dashing)
Schedule(addAdditiveSprite);
}
}
private void addAdditiveSprite()
{
if (!dashing) return;
var additive = createCatcherSprite();
additive.RelativePositionAxes = Axes.Both;
additive.Blending = BlendingMode.Additive;
additive.Position = Position;
additive.Scale = Scale;
((Container)Parent).Add(additive);
additive.FadeTo(0.4f).FadeOut(800, Easing.OutQuint).Expire();
Scheduler.AddDelayed(addAdditiveSprite, 50);
}
private Sprite createCatcherSprite() => new Sprite
{
RelativeSizeAxes = Axes.Both,
FillMode = FillMode.Fit,
Texture = texture,
OriginPosition = new Vector2(DrawWidth / 2, 10) //temporary until the sprite is aligned correctly.
};
public bool OnPressed(CatchAction action)
{
switch (action)
{
case CatchAction.MoveLeft:
currentDirection--;
return true;
case CatchAction.MoveRight:
currentDirection++;
return true;
case CatchAction.Dash:
Dashing = true;
return true;
}
return false;
}
public bool OnReleased(CatchAction action)
{
switch (action)
{
case CatchAction.MoveLeft:
currentDirection++;
return true;
case CatchAction.MoveRight:
currentDirection--;
return true;
case CatchAction.Dash:
Dashing = false;
return true;
}
return false;
}
/// <summary>
/// The relative space to cover in 1 millisecond. based on 1 game pixel per millisecond as in osu-stable.
/// </summary>
private const double base_speed = 1.0 / 512;
protected override void Update()
{
base.Update();
if (currentDirection == 0) return;
double dashModifier = Dashing ? 1 : 0.5;
Scale = new Vector2(Math.Sign(currentDirection), 1);
X = (float)MathHelper.Clamp(X + Math.Sign(currentDirection) * Clock.ElapsedFrameTime * base_speed * dashModifier, 0, 1);
}
public void Add(DrawableHitObject fruit, Vector2 absolutePosition)
{
fruit.RelativePositionAxes = Axes.None;
fruit.Position = new Vector2(ToLocalSpace(absolutePosition).X - DrawSize.X / 2, 0);
fruit.Anchor = Anchor.TopCentre;
fruit.Origin = Anchor.BottomCentre;
fruit.Scale *= 0.7f;
fruit.LifetimeEnd = double.MaxValue;
float distance = fruit.DrawSize.X / 2 * fruit.Scale.X;
while (caughtFruit.Any(f => f.LifetimeEnd == double.MaxValue && Vector2Extensions.DistanceSquared(f.Position, fruit.Position) < distance * distance))
{
fruit.X += RNG.Next(-5, 5);
fruit.Y -= RNG.Next(0, 5);
}
caughtFruit.Add(fruit);
if (((CatchBaseHit)fruit.HitObject).LastInCombo)
explode();
}
private void explode()
{
var fruit = caughtFruit.ToArray();
foreach (var f in fruit)
{
var originalX = f.X * Scale.X;
if (ExplodingFruitTarget != null)
{
f.Anchor = Anchor.TopLeft;
f.Position = caughtFruit.ToSpaceOfOtherDrawable(f.DrawPosition, ExplodingFruitTarget);
caughtFruit.Remove(f);
ExplodingFruitTarget.Add(f);
}
f.MoveToY(f.Y - 50, 250, Easing.OutSine)
.Then()
.MoveToY(f.Y + 50, 500, Easing.InSine);
f.MoveToX(f.X + originalX * 6, 1000);
f.FadeOut(750);
f.Expire();
}
}
}
}

View File

@ -1,229 +0,0 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Textures;
using osu.Framework.Input.Bindings;
using osu.Framework.MathUtils;
using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Objects.Drawables;
using OpenTK;
namespace osu.Game.Rulesets.Catch.UI
{
public class CatcherArea : Container
{
private Catcher catcher;
private Container explodingFruitContainer;
public void Add(DrawableHitObject fruit, Vector2 screenPosition) => catcher.AddToStack(fruit, screenPosition);
public bool CheckIfWeCanCatch(CatchBaseHit obj) => Math.Abs(catcher.Position.X - obj.X) < catcher.DrawSize.X / DrawSize.X / 2;
[BackgroundDependencyLoader]
private void load()
{
Children = new Drawable[]
{
explodingFruitContainer = new Container
{
RelativeSizeAxes = Axes.Both,
},
catcher = new Catcher
{
RelativePositionAxes = Axes.Both,
ExplodingFruitTarget = explodingFruitContainer,
Origin = Anchor.TopCentre,
X = 0.5f,
}
};
}
protected override void Update()
{
base.Update();
catcher.Size = new Vector2(DrawSize.Y);
}
private class Catcher : Container, IKeyBindingHandler<CatchAction>
{
private Texture texture;
private Container<DrawableHitObject> caughtFruit;
[BackgroundDependencyLoader]
private void load(TextureStore textures)
{
texture = textures.Get(@"Play/Catch/fruit-catcher-idle");
Children = new Drawable[]
{
createCatcherSprite(),
caughtFruit = new Container<DrawableHitObject>
{
Anchor = Anchor.TopCentre,
Origin = Anchor.BottomCentre,
}
};
}
private int currentDirection;
private bool dashing;
public Container ExplodingFruitTarget;
protected bool Dashing
{
get { return dashing; }
set
{
if (value == dashing) return;
dashing = value;
if (dashing)
Schedule(addAdditiveSprite);
}
}
private void addAdditiveSprite()
{
if (!dashing) return;
var additive = createCatcherSprite();
additive.RelativePositionAxes = Axes.Both;
additive.Blending = BlendingMode.Additive;
additive.Position = Position;
additive.Scale = Scale;
((CatcherArea)Parent).Add(additive);
additive.FadeTo(0.4f).FadeOut(800, Easing.OutQuint).Expire();
Scheduler.AddDelayed(addAdditiveSprite, 50);
}
private Sprite createCatcherSprite() => new Sprite
{
RelativeSizeAxes = Axes.Both,
FillMode = FillMode.Fit,
Texture = texture,
OriginPosition = new Vector2(DrawWidth / 2, 10) //temporary until the sprite is aligned correctly.
};
public bool OnPressed(CatchAction action)
{
switch (action)
{
case CatchAction.MoveLeft:
currentDirection--;
return true;
case CatchAction.MoveRight:
currentDirection++;
return true;
case CatchAction.Dash:
Dashing = true;
return true;
}
return false;
}
public bool OnReleased(CatchAction action)
{
switch (action)
{
case CatchAction.MoveLeft:
currentDirection++;
return true;
case CatchAction.MoveRight:
currentDirection--;
return true;
case CatchAction.Dash:
Dashing = false;
return true;
}
return false;
}
/// <summary>
/// The relative space to cover in 1 millisecond. based on 1 game pixel per millisecond as in osu-stable.
/// </summary>
private const double base_speed = 1.0 / 512;
protected override void Update()
{
base.Update();
if (currentDirection == 0) return;
double dashModifier = Dashing ? 1 : 0.5;
Scale = new Vector2(Math.Sign(currentDirection), 1);
X = (float)MathHelper.Clamp(X + Math.Sign(currentDirection) * Clock.ElapsedFrameTime * base_speed * dashModifier, 0, 1);
}
public void AddToStack(DrawableHitObject fruit, Vector2 absolutePosition)
{
fruit.RelativePositionAxes = Axes.None;
fruit.Position = new Vector2(ToLocalSpace(absolutePosition).X - DrawSize.X / 2, 0);
fruit.Anchor = Anchor.TopCentre;
fruit.Origin = Anchor.BottomCentre;
fruit.Scale *= 0.7f;
fruit.LifetimeEnd = double.MaxValue;
float distance = fruit.DrawSize.X / 2 * fruit.Scale.X;
while (caughtFruit.Any(f => f.LifetimeEnd == double.MaxValue && Vector2Extensions.DistanceSquared(f.Position, fruit.Position) < distance * distance))
{
fruit.X += RNG.Next(-5, 5);
fruit.Y -= RNG.Next(0, 5);
}
caughtFruit.Add(fruit);
if (((CatchBaseHit)fruit.HitObject).LastInCombo)
explode();
}
private void explode()
{
var fruit = caughtFruit.ToArray();
foreach (var f in fruit)
{
var originalX = f.X * Scale.X;
if (ExplodingFruitTarget != null)
{
f.Anchor = Anchor.TopLeft;
f.Position = caughtFruit.ToSpaceOfOtherDrawable(f.DrawPosition, ExplodingFruitTarget);
caughtFruit.Remove(f);
ExplodingFruitTarget.Add(f);
}
f.MoveToY(f.Y - 50, 250, Easing.OutSine)
.Then()
.MoveToY(f.Y + 50, 500, Easing.InSine);
f.MoveToX(f.X + originalX * 6, 1000);
f.FadeOut(750);
f.Expire();
}
}
}
}
}

View File

@ -50,16 +50,23 @@
<Compile Include="Beatmaps\CatchBeatmapProcessor.cs" /> <Compile Include="Beatmaps\CatchBeatmapProcessor.cs" />
<Compile Include="CatchDifficultyCalculator.cs" /> <Compile Include="CatchDifficultyCalculator.cs" />
<Compile Include="CatchInputManager.cs" /> <Compile Include="CatchInputManager.cs" />
<Compile Include="Objects\Drawable\DrawableCatchHitObject.cs" />
<Compile Include="Objects\Drawable\DrawableDroplet.cs" />
<Compile Include="Objects\Drawable\DrawableJuiceStream.cs" />
<Compile Include="Objects\Drawable\Pieces\Pulp.cs" />
<Compile Include="Objects\JuiceStream.cs" />
<Compile Include="Scoring\CatchScoreProcessor.cs" /> <Compile Include="Scoring\CatchScoreProcessor.cs" />
<Compile Include="Judgements\CatchJudgement.cs" /> <Compile Include="Judgements\CatchJudgement.cs" />
<Compile Include="Objects\CatchBaseHit.cs" /> <Compile Include="Objects\CatchBaseHit.cs" />
<Compile Include="Objects\Drawable\DrawableFruit.cs" /> <Compile Include="Objects\Drawable\DrawableFruit.cs" />
<Compile Include="Objects\Droplet.cs" /> <Compile Include="Objects\Droplet.cs" />
<Compile Include="Objects\Fruit.cs" /> <Compile Include="Objects\Fruit.cs" />
<Compile Include="Objects\TinyDroplet.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Tests\TestCaseCatcher.cs" /> <Compile Include="Tests\TestCaseCatcher.cs" />
<Compile Include="Tests\TestCaseCatchStacker.cs" />
<Compile Include="Tests\TestCaseCatchPlayer.cs" /> <Compile Include="Tests\TestCaseCatchPlayer.cs" />
<Compile Include="UI\CatcherArea.cs" /> <Compile Include="UI\Catcher.cs" />
<Compile Include="UI\CatchRulesetContainer.cs" /> <Compile Include="UI\CatchRulesetContainer.cs" />
<Compile Include="UI\CatchPlayfield.cs" /> <Compile Include="UI\CatchPlayfield.cs" />
<Compile Include="CatchRuleset.cs" /> <Compile Include="CatchRuleset.cs" />
@ -79,11 +86,6 @@
<Name>osu.Framework</Name> <Name>osu.Framework</Name>
<Private>False</Private> <Private>False</Private>
</ProjectReference> </ProjectReference>
<ProjectReference Include="..\osu.Game.Rulesets.Osu\osu.Game.Rulesets.Osu.csproj">
<Project>{C92A607B-1FDD-4954-9F92-03FF547D9080}</Project>
<Name>osu.Game.Rulesets.Osu</Name>
<Private>False</Private>
</ProjectReference>
<ProjectReference Include="..\osu.Game\osu.Game.csproj"> <ProjectReference Include="..\osu.Game\osu.Game.csproj">
<Project>{2a66dd92-adb1-4994-89e2-c94e04acda0d}</Project> <Project>{2a66dd92-adb1-4994-89e2-c94e04acda0d}</Project>
<Name>osu.Game</Name> <Name>osu.Game</Name>

View File

@ -10,7 +10,7 @@ using osu.Game.Tests.Visual;
using OpenTK; using OpenTK;
using OpenTK.Graphics; using OpenTK.Graphics;
namespace osu.Game.Rulesets.Mania.Testing namespace osu.Game.Rulesets.Mania.Tests
{ {
[TestFixture] [TestFixture]
internal class TestCaseManiaHitObjects : OsuTestCase internal class TestCaseManiaHitObjects : OsuTestCase

View File

@ -17,7 +17,7 @@ using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Timing; using osu.Game.Rulesets.Timing;
using osu.Game.Tests.Visual; using osu.Game.Tests.Visual;
namespace osu.Game.Rulesets.Mania.Testing namespace osu.Game.Rulesets.Mania.Tests
{ {
[TestFixture] [TestFixture]
internal class TestCaseManiaPlayfield : OsuTestCase internal class TestCaseManiaPlayfield : OsuTestCase

View File

@ -80,8 +80,8 @@
<Compile Include="Objects\Note.cs" /> <Compile Include="Objects\Note.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="ManiaInputManager.cs" /> <Compile Include="ManiaInputManager.cs" />
<Compile Include="Testing\TestCaseManiaHitObjects.cs" /> <Compile Include="Tests\TestCaseManiaHitObjects.cs" />
<Compile Include="Testing\TestCaseManiaPlayfield.cs" /> <Compile Include="Tests\TestCaseManiaPlayfield.cs" />
<Compile Include="Timing\GravityScrollingContainer.cs" /> <Compile Include="Timing\GravityScrollingContainer.cs" />
<Compile Include="Timing\ScrollingAlgorithm.cs" /> <Compile Include="Timing\ScrollingAlgorithm.cs" />
<Compile Include="UI\Column.cs" /> <Compile Include="UI\Column.cs" />

View File

@ -22,6 +22,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
public override bool RemoveWhenNotAlive => false; public override bool RemoveWhenNotAlive => false;
public override bool DisplayJudgement => false;
public DrawableSliderTick(SliderTick sliderTick) : base(sliderTick) public DrawableSliderTick(SliderTick sliderTick) : base(sliderTick)
{ {
this.sliderTick = sliderTick; this.sliderTick = sliderTick;

View File

@ -22,6 +22,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
private readonly SpinnerDisc disc; private readonly SpinnerDisc disc;
private readonly SpinnerTicks ticks; private readonly SpinnerTicks ticks;
private readonly SpinnerSpmCounter spmCounter;
private readonly Container mainContainer; private readonly Container mainContainer;
@ -103,6 +104,13 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
}, },
} }
}, },
spmCounter = new SpinnerSpmCounter
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Y = 120,
Alpha = 0
}
}; };
} }
@ -157,6 +165,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
protected override void Update() protected override void Update()
{ {
disc.Tracking = OsuActionInputManager.PressedActions.Any(x => x == OsuAction.LeftButton || x == OsuAction.RightButton); disc.Tracking = OsuActionInputManager.PressedActions.Any(x => x == OsuAction.LeftButton || x == OsuAction.RightButton);
if (!spmCounter.IsPresent && disc.Tracking)
spmCounter.FadeIn(TIME_FADEIN);
base.Update(); base.Update();
} }
@ -167,6 +177,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
circle.Rotation = disc.Rotation; circle.Rotation = disc.Rotation;
ticks.Rotation = disc.Rotation; ticks.Rotation = disc.Rotation;
spmCounter.SetRotation(disc.RotationAbsolute);
float relativeCircleScale = spinner.Scale * circle.DrawHeight / mainContainer.DrawHeight; float relativeCircleScale = spinner.Scale * circle.DrawHeight / mainContainer.DrawHeight;
disc.ScaleTo(relativeCircleScale + (1 - relativeCircleScale) * Progress, 200, Easing.OutQuint); disc.ScaleTo(relativeCircleScale + (1 - relativeCircleScale) * Progress, 200, Easing.OutQuint);

View File

@ -77,7 +77,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
private float lastAngle; private float lastAngle;
private float currentRotation; private float currentRotation;
public float RotationAbsolute; public float RotationAbsolute;
private int completeTick; private int completeTick;
private bool updateCompleteTick() => completeTick != (completeTick = (int)(RotationAbsolute / 360)); private bool updateCompleteTick() => completeTick != (completeTick = (int)(RotationAbsolute / 360));

View File

@ -0,0 +1,75 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using System.Collections.Generic;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Graphics.Sprites;
namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
{
public class SpinnerSpmCounter : Container
{
private readonly OsuSpriteText spmText;
public SpinnerSpmCounter()
{
Children = new Drawable[]
{
spmText = new OsuSpriteText
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
Text = @"0",
Font = @"Venera",
TextSize = 24
},
new OsuSpriteText
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
Text = @"SPINS PER MINUTE",
Font = @"Venera",
TextSize = 12,
Y = 30
}
};
}
private double spm;
public double SpinsPerMinute
{
get { return spm; }
private set
{
if (value == spm) return;
spm = value;
spmText.Text = Math.Truncate(value).ToString(@"#0");
}
}
private struct RotationRecord
{
public float Rotation;
public double Time;
}
private readonly Queue<RotationRecord> records = new Queue<RotationRecord>();
private const double spm_count_duration = 595; // not using hundreds to avoid frame rounding issues
public void SetRotation(float currentRotation)
{
if (records.Count > 0)
{
var record = records.Peek();
while (records.Count > 0 && Time.Current - records.Peek().Time > spm_count_duration)
record = records.Dequeue();
SpinsPerMinute = (currentRotation - record.Rotation) / (Time.Current - record.Time) * 1000 * 60 / 360;
}
records.Enqueue(new RotationRecord { Rotation = currentRotation, Time = Time.Current });
}
}
}

View File

@ -91,6 +91,9 @@ namespace osu.Game.Rulesets.Osu.UI
var osuJudgement = (OsuJudgement)judgement; var osuJudgement = (OsuJudgement)judgement;
var osuObject = (OsuHitObject)judgedObject.HitObject; var osuObject = (OsuHitObject)judgedObject.HitObject;
if (!judgedObject.DisplayJudgement)
return;
DrawableOsuJudgement explosion = new DrawableOsuJudgement(osuJudgement) DrawableOsuJudgement explosion = new DrawableOsuJudgement(osuJudgement)
{ {
Origin = Anchor.Centre, Origin = Anchor.Centre,

View File

@ -68,6 +68,7 @@
<Compile Include="Objects\Drawables\Pieces\RingPiece.cs" /> <Compile Include="Objects\Drawables\Pieces\RingPiece.cs" />
<Compile Include="Objects\Drawables\Pieces\SliderBouncer.cs" /> <Compile Include="Objects\Drawables\Pieces\SliderBouncer.cs" />
<Compile Include="Objects\Drawables\Pieces\SpinnerDisc.cs" /> <Compile Include="Objects\Drawables\Pieces\SpinnerDisc.cs" />
<Compile Include="Objects\Drawables\Pieces\SpinnerSpmCounter.cs" />
<Compile Include="Objects\Drawables\Pieces\SpinnerTicks.cs" /> <Compile Include="Objects\Drawables\Pieces\SpinnerTicks.cs" />
<Compile Include="Objects\Drawables\Pieces\TrianglesPiece.cs" /> <Compile Include="Objects\Drawables\Pieces\TrianglesPiece.cs" />
<Compile Include="Objects\Drawables\Pieces\SliderBall.cs" /> <Compile Include="Objects\Drawables\Pieces\SliderBall.cs" />

View File

@ -55,14 +55,17 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
Beatmap<TaikoHitObject> converted = base.ConvertBeatmap(original); Beatmap<TaikoHitObject> converted = base.ConvertBeatmap(original);
// Post processing step to transform hit objects with the same start time into strong hits if (original.BeatmapInfo.RulesetID == 3)
converted.HitObjects = converted.HitObjects.GroupBy(t => t.StartTime).Select(x =>
{ {
TaikoHitObject first = x.First(); // Post processing step to transform mania hit objects with the same start time into strong hits
if (x.Skip(1).Any()) converted.HitObjects = converted.HitObjects.GroupBy(t => t.StartTime).Select(x =>
first.IsStrong = true; {
return first; TaikoHitObject first = x.First();
}).ToList(); if (x.Skip(1).Any())
first.IsStrong = true;
return first;
}).ToList();
}
return converted; return converted;
} }

View File

@ -21,6 +21,8 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
FillMode = FillMode.Fit; FillMode = FillMode.Fit;
} }
public override bool DisplayJudgement => false;
protected override void LoadComplete() protected override void LoadComplete()
{ {
base.LoadComplete(); base.LoadComplete();

View File

@ -221,7 +221,7 @@ namespace osu.Game.Rulesets.Taiko.UI
public override void OnJudgement(DrawableHitObject judgedObject, Judgement judgement) public override void OnJudgement(DrawableHitObject judgedObject, Judgement judgement)
{ {
if (judgementContainer.FirstOrDefault(j => j.JudgedObject == judgedObject) == null) if (judgedObject.DisplayJudgement && judgementContainer.FirstOrDefault(j => j.JudgedObject == judgedObject) == null)
{ {
judgementContainer.Add(new DrawableTaikoJudgement(judgedObject, judgement) judgementContainer.Add(new DrawableTaikoJudgement(judgedObject, judgement)
{ {

View File

@ -0,0 +1,146 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.IO;
using NUnit.Framework;
using OpenTK;
using OpenTK.Graphics;
using osu.Game.Beatmaps.Formats;
using osu.Game.Tests.Resources;
using System.Linq;
using osu.Game.Audio;
using osu.Game.Rulesets.Objects.Types;
namespace osu.Game.Tests.Beatmaps.Formats
{
[TestFixture]
public class OsuLegacyDecoderTest
{
[Test]
public void TestDecodeMetadata()
{
var decoder = new OsuLegacyDecoder();
using (var stream = Resource.OpenResource("Soleily - Renatus (Gamu) [Insane].osu"))
{
var beatmap = decoder.Decode(new StreamReader(stream));
var meta = beatmap.BeatmapInfo.Metadata;
Assert.AreEqual(241526, meta.OnlineBeatmapSetID);
Assert.AreEqual("Soleily", meta.Artist);
Assert.AreEqual("Soleily", meta.ArtistUnicode);
Assert.AreEqual("03. Renatus - Soleily 192kbps.mp3", meta.AudioFile);
Assert.AreEqual("Gamu", meta.Author);
Assert.AreEqual("machinetop_background.jpg", meta.BackgroundFile);
Assert.AreEqual(164471, meta.PreviewTime);
Assert.AreEqual(string.Empty, meta.Source);
Assert.AreEqual("MBC7 Unisphere 地球ヤバイEP Chikyu Yabai", meta.Tags);
Assert.AreEqual("Renatus", meta.Title);
Assert.AreEqual("Renatus", meta.TitleUnicode);
}
}
[Test]
public void TestDecodeGeneral()
{
var decoder = new OsuLegacyDecoder();
using (var stream = Resource.OpenResource("Soleily - Renatus (Gamu) [Insane].osu"))
{
var beatmapInfo = decoder.Decode(new StreamReader(stream)).BeatmapInfo;
Assert.AreEqual(0, beatmapInfo.AudioLeadIn);
Assert.AreEqual(false, beatmapInfo.Countdown);
Assert.AreEqual(0.7f, beatmapInfo.StackLeniency);
Assert.AreEqual(false, beatmapInfo.SpecialStyle);
Assert.IsTrue(beatmapInfo.RulesetID == 0);
Assert.AreEqual(false, beatmapInfo.LetterboxInBreaks);
Assert.AreEqual(false, beatmapInfo.WidescreenStoryboard);
}
}
[Test]
public void TestDecodeEditor()
{
var decoder = new OsuLegacyDecoder();
using (var stream = Resource.OpenResource("Soleily - Renatus (Gamu) [Insane].osu"))
{
var beatmap = decoder.Decode(new StreamReader(stream)).BeatmapInfo;
int[] expectedBookmarks =
{
11505, 22054, 32604, 43153, 53703, 64252, 74802, 85351,
95901, 106450, 116999, 119637, 130186, 140735, 151285,
161834, 164471, 175020, 185570, 196119, 206669, 209306
};
Assert.AreEqual(expectedBookmarks.Length, beatmap.Bookmarks.Length);
for (int i = 0; i < expectedBookmarks.Length; i++)
Assert.AreEqual(expectedBookmarks[i], beatmap.Bookmarks[i]);
Assert.AreEqual(1.8, beatmap.DistanceSpacing);
Assert.AreEqual(4, beatmap.BeatDivisor);
Assert.AreEqual(4, beatmap.GridSize);
Assert.AreEqual(2, beatmap.TimelineZoom);
}
}
[Test]
public void TestDecodeDifficulty()
{
var decoder = new OsuLegacyDecoder();
using (var stream = Resource.OpenResource("Soleily - Renatus (Gamu) [Insane].osu"))
{
var beatmap = decoder.Decode(new StreamReader(stream));
var difficulty = beatmap.BeatmapInfo.Difficulty;
Assert.AreEqual(6.5f, difficulty.DrainRate);
Assert.AreEqual(4, difficulty.CircleSize);
Assert.AreEqual(8, difficulty.OverallDifficulty);
Assert.AreEqual(9, difficulty.ApproachRate);
Assert.AreEqual(1.8f, difficulty.SliderMultiplier);
Assert.AreEqual(2, difficulty.SliderTickRate);
}
}
[Test]
public void TestDecodeColors()
{
var decoder = new OsuLegacyDecoder();
using (var stream = Resource.OpenResource("Soleily - Renatus (Gamu) [Insane].osu"))
{
var beatmap = decoder.Decode(new StreamReader(stream));
Color4[] expected =
{
new Color4(142, 199, 255, 255),
new Color4(255, 128, 128, 255),
new Color4(128, 255, 255, 255),
new Color4(128, 255, 128, 255),
new Color4(255, 187, 255, 255),
new Color4(255, 177, 140, 255),
};
Assert.AreEqual(expected.Length, beatmap.ComboColors.Count);
for (int i = 0; i < expected.Length; i++)
Assert.AreEqual(expected[i], beatmap.ComboColors[i]);
}
}
[Test]
public void TestDecodeHitObjects()
{
var decoder = new OsuLegacyDecoder();
using (var stream = Resource.OpenResource("Soleily - Renatus (Gamu) [Insane].osu"))
{
var beatmap = decoder.Decode(new StreamReader(stream));
var curveData = beatmap.HitObjects[0] as IHasCurve;
var positionData = beatmap.HitObjects[0] as IHasPosition;
Assert.IsNotNull(positionData);
Assert.IsNotNull(curveData);
Assert.AreEqual(new Vector2(192, 168), positionData.Position);
Assert.AreEqual(956, beatmap.HitObjects[0].StartTime);
Assert.IsTrue(beatmap.HitObjects[0].Samples.Any(s => s.Name == SampleInfo.HIT_NORMAL));
positionData = beatmap.HitObjects[1] as IHasPosition;
Assert.IsNotNull(positionData);
Assert.AreEqual(new Vector2(304, 56), positionData.Position);
Assert.AreEqual(1285, beatmap.HitObjects[1].StartTime);
Assert.IsTrue(beatmap.HitObjects[1].Samples.Any(s => s.Name == SampleInfo.HIT_CLAP));
}
}
}
}

View File

@ -0,0 +1,164 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using System.IO;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using NUnit.Framework;
using osu.Framework.Platform;
using osu.Game.IPC;
using osu.Framework.Allocation;
using osu.Game.Beatmaps;
namespace osu.Game.Tests.Beatmaps.IO
{
[TestFixture]
public class ImportBeatmapTest
{
private const string osz_path = @"../../../osu-resources/osu.Game.Resources/Beatmaps/241526 Soleily - Renatus.osz";
[Test]
public void TestImportWhenClosed()
{
//unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
using (HeadlessGameHost host = new HeadlessGameHost("TestImportWhenClosed"))
{
var osu = loadOsu(host);
var temp = prepareTempCopy(osz_path);
Assert.IsTrue(File.Exists(temp));
osu.Dependencies.Get<BeatmapManager>().Import(temp);
ensureLoaded(osu);
waitForOrAssert(() => !File.Exists(temp), "Temporary file still exists after standard import", 5000);
}
}
[Test]
public void TestImportOverIPC()
{
using (HeadlessGameHost host = new HeadlessGameHost("host", true))
using (HeadlessGameHost client = new HeadlessGameHost("client", true))
{
Assert.IsTrue(host.IsPrimaryInstance);
Assert.IsTrue(!client.IsPrimaryInstance);
var osu = loadOsu(host);
var temp = prepareTempCopy(osz_path);
Assert.IsTrue(File.Exists(temp));
var importer = new BeatmapIPCChannel(client);
if (!importer.ImportAsync(temp).Wait(10000))
Assert.Fail(@"IPC took too long to send");
ensureLoaded(osu);
waitForOrAssert(() => !File.Exists(temp), "Temporary still exists after IPC import", 5000);
}
}
[Test]
public void TestImportWhenFileOpen()
{
using (HeadlessGameHost host = new HeadlessGameHost("TestImportWhenFileOpen"))
{
var osu = loadOsu(host);
var temp = prepareTempCopy(osz_path);
Assert.IsTrue(File.Exists(temp), "Temporary file copy never substantiated");
using (File.OpenRead(temp))
osu.Dependencies.Get<BeatmapManager>().Import(temp);
ensureLoaded(osu);
File.Delete(temp);
Assert.IsFalse(File.Exists(temp), "We likely held a read lock on the file when we shouldn't");
}
}
private string prepareTempCopy(string path)
{
var temp = Path.GetTempFileName();
return new FileInfo(path).CopyTo(temp, true).FullName;
}
private OsuGameBase loadOsu(GameHost host)
{
host.Storage.DeleteDatabase(@"client");
var osu = new OsuGameBase();
Task.Run(() => host.Run(osu));
waitForOrAssert(() => osu.IsLoaded, @"osu! failed to start in a reasonable amount of time");
return osu;
}
private void ensureLoaded(OsuGameBase osu, int timeout = 60000)
{
IEnumerable<BeatmapSetInfo> resultSets = null;
var store = osu.Dependencies.Get<BeatmapManager>();
waitForOrAssert(() => (resultSets = store.QueryBeatmapSets(s => s.OnlineBeatmapSetID == 241526)).Any(),
@"BeatmapSet did not import to the database in allocated time.", timeout);
//ensure we were stored to beatmap database backing...
Assert.IsTrue(resultSets.Count() == 1, $@"Incorrect result count found ({resultSets.Count()} but should be 1).");
Func<IEnumerable<BeatmapInfo>> queryBeatmaps = () => store.QueryBeatmaps(s => s.OnlineBeatmapSetID == 241526 && s.BaseDifficultyID > 0);
Func<IEnumerable<BeatmapSetInfo>> queryBeatmapSets = () => store.QueryBeatmapSets(s => s.OnlineBeatmapSetID == 241526);
//if we don't re-check here, the set will be inserted but the beatmaps won't be present yet.
waitForOrAssert(() => queryBeatmaps().Count() == 12,
@"Beatmaps did not import to the database in allocated time", timeout);
waitForOrAssert(() => queryBeatmapSets().Count() == 1,
@"BeatmapSet did not import to the database in allocated time", timeout);
int countBeatmapSetBeatmaps = 0;
int countBeatmaps = 0;
waitForOrAssert(() =>
(countBeatmapSetBeatmaps = queryBeatmapSets().First().Beatmaps.Count) ==
(countBeatmaps = queryBeatmaps().Count()),
$@"Incorrect database beatmap count post-import ({countBeatmaps} but should be {countBeatmapSetBeatmaps}).", timeout);
var set = queryBeatmapSets().First();
foreach (BeatmapInfo b in set.Beatmaps)
Assert.IsTrue(set.Beatmaps.Any(c => c.OnlineBeatmapID == b.OnlineBeatmapID));
Assert.IsTrue(set.Beatmaps.Count > 0);
var beatmap = store.GetWorkingBeatmap(set.Beatmaps.First(b => b.RulesetID == 0))?.Beatmap;
Assert.IsTrue(beatmap?.HitObjects.Count > 0);
beatmap = store.GetWorkingBeatmap(set.Beatmaps.First(b => b.RulesetID == 1))?.Beatmap;
Assert.IsTrue(beatmap?.HitObjects.Count > 0);
beatmap = store.GetWorkingBeatmap(set.Beatmaps.First(b => b.RulesetID == 2))?.Beatmap;
Assert.IsTrue(beatmap?.HitObjects.Count > 0);
beatmap = store.GetWorkingBeatmap(set.Beatmaps.First(b => b.RulesetID == 3))?.Beatmap;
Assert.IsTrue(beatmap?.HitObjects.Count > 0);
}
private void waitForOrAssert(Func<bool> result, string failureMessage, int timeout = 60000)
{
Action waitAction = () => { while (!result()) Thread.Sleep(20); };
Assert.IsTrue(waitAction.BeginInvoke(null, null).AsyncWaitHandle.WaitOne(timeout), failureMessage);
}
}
}

View File

@ -0,0 +1,83 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.IO;
using System.Linq;
using NUnit.Framework;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.IO;
using osu.Game.Tests.Resources;
using osu.Game.Beatmaps.Formats;
namespace osu.Game.Tests.Beatmaps.IO
{
[TestFixture]
public class OszArchiveReaderTest
{
[Test]
public void TestReadBeatmaps()
{
using (var osz = Resource.OpenResource("Beatmaps.241526 Soleily - Renatus.osz"))
{
var reader = new OszArchiveReader(osz);
string[] expected =
{
"Soleily - Renatus (Deif) [Platter].osu",
"Soleily - Renatus (Deif) [Rain].osu",
"Soleily - Renatus (Deif) [Salad].osu",
"Soleily - Renatus (ExPew) [Another].osu",
"Soleily - Renatus (ExPew) [Hyper].osu",
"Soleily - Renatus (ExPew) [Normal].osu",
"Soleily - Renatus (Gamu) [Hard].osu",
"Soleily - Renatus (Gamu) [Insane].osu",
"Soleily - Renatus (Gamu) [Normal].osu",
"Soleily - Renatus (MMzz) [Futsuu].osu",
"Soleily - Renatus (MMzz) [Muzukashii].osu",
"Soleily - Renatus (MMzz) [Oni].osu"
};
var maps = reader.Filenames.ToArray();
foreach (var map in expected)
Assert.Contains(map, maps);
}
}
[Test]
public void TestReadMetadata()
{
using (var osz = Resource.OpenResource("Beatmaps.241526 Soleily - Renatus.osz"))
{
var reader = new OszArchiveReader(osz);
BeatmapMetadata meta;
using (var stream = new StreamReader(reader.GetStream("Soleily - Renatus (Deif) [Platter].osu")))
meta = BeatmapDecoder.GetDecoder(stream).Decode(stream).Metadata;
Assert.AreEqual(241526, meta.OnlineBeatmapSetID);
Assert.AreEqual("Soleily", meta.Artist);
Assert.AreEqual("Soleily", meta.ArtistUnicode);
Assert.AreEqual("03. Renatus - Soleily 192kbps.mp3", meta.AudioFile);
Assert.AreEqual("Deif", meta.Author);
Assert.AreEqual("machinetop_background.jpg", meta.BackgroundFile);
Assert.AreEqual(164471, meta.PreviewTime);
Assert.AreEqual(string.Empty, meta.Source);
Assert.AreEqual("MBC7 Unisphere 地球ヤバイEP Chikyu Yabai", meta.Tags);
Assert.AreEqual("Renatus", meta.Title);
Assert.AreEqual("Renatus", meta.TitleUnicode);
}
}
[Test]
public void TestReadFile()
{
using (var osz = Resource.OpenResource("Beatmaps.241526 Soleily - Renatus.osz"))
{
var reader = new OszArchiveReader(osz);
using (var stream = new StreamReader(
reader.GetStream("Soleily - Renatus (Deif) [Platter].osu")))
{
Assert.AreEqual("osu file format v13", stream.ReadLine()?.Trim());
}
}
}
}
}

View File

@ -0,0 +1,25 @@
<configuration>
<dllmap os="linux" dll="opengl32.dll" target="libGL.so.1"/>
<dllmap os="linux" dll="glu32.dll" target="libGLU.so.1"/>
<dllmap os="linux" dll="openal32.dll" target="libopenal.so.1"/>
<dllmap os="linux" dll="alut.dll" target="libalut.so.0"/>
<dllmap os="linux" dll="opencl.dll" target="libOpenCL.so"/>
<dllmap os="linux" dll="libX11" target="libX11.so.6"/>
<dllmap os="linux" dll="libXi" target="libXi.so.6"/>
<dllmap os="linux" dll="SDL2.dll" target="libSDL2-2.0.so.0"/>
<dllmap os="osx" dll="opengl32.dll" target="/System/Library/Frameworks/OpenGL.framework/OpenGL"/>
<dllmap os="osx" dll="openal32.dll" target="/System/Library/Frameworks/OpenAL.framework/OpenAL" />
<dllmap os="osx" dll="alut.dll" target="/System/Library/Frameworks/OpenAL.framework/OpenAL" />
<dllmap os="osx" dll="libGLES.dll" target="/System/Library/Frameworks/OpenGLES.framework/OpenGLES" />
<dllmap os="osx" dll="libGLESv1_CM.dll" target="/System/Library/Frameworks/OpenGLES.framework/OpenGLES" />
<dllmap os="osx" dll="libGLESv2.dll" target="/System/Library/Frameworks/OpenGLES.framework/OpenGLES" />
<dllmap os="osx" dll="opencl.dll" target="/System/Library/Frameworks/OpenCL.framework/OpenCL"/>
<dllmap os="osx" dll="SDL2.dll" target="libSDL2.dylib"/>
<!-- XQuartz compatibility (X11 on Mac) -->
<dllmap os="osx" dll="libGL.so.1" target="/usr/X11/lib/libGL.dylib"/>
<dllmap os="osx" dll="libX11" target="/usr/X11/lib/libX11.dylib"/>
<dllmap os="osx" dll="libXcursor.so.1" target="/usr/X11/lib/libXcursor.dylib"/>
<dllmap os="osx" dll="libXi" target="/usr/X11/lib/libXi.dylib"/>
<dllmap os="osx" dll="libXinerama" target="/usr/X11/lib/libXinerama.dylib"/>
<dllmap os="osx" dll="libXrandr.so.2" target="/usr/X11/lib/libXrandr.dylib"/>
</configuration>

View File

@ -0,0 +1,20 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using System.IO;
using System.Reflection;
namespace osu.Game.Tests.Resources
{
public static class Resource
{
public static Stream OpenResource(string name)
{
var localPath = Path.GetDirectoryName(Uri.UnescapeDataString(new UriBuilder(Assembly.GetExecutingAssembly().CodeBase).Path));
return Assembly.GetExecutingAssembly().GetManifestResourceStream($@"osu.Game.Tests.Resources.{name}") ??
Assembly.LoadFrom(Path.Combine(localPath, @"osu.Game.Resources.dll")).GetManifestResourceStream($@"osu.Game.Resources.{name}");
}
}
}

File diff suppressed because it is too large Load Diff

11
osu.Game.Tests/app.config Normal file
View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-10.0.0.0" newVersion="10.0.0.0" />
</dependentAssembly>
</assemblyBinding>
</runtime>
</configuration>

View File

@ -0,0 +1,103 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{54377672-20B1-40AF-8087-5CF73BF3953A}</ProjectGuid>
<OutputType>Library</OutputType>
<RootNamespace>osu.Game.Tests</RootNamespace>
<AssemblyName>osu.Game.Tests</AssemblyName>
<TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug</OutputPath>
<DefineConstants>DEBUG;</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<ConsolePause>false</ConsolePause>
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
<LangVersion>6</LangVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<Optimize>true</Optimize>
<OutputPath>bin\Release</OutputPath>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<ConsolePause>false</ConsolePause>
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
</PropertyGroup>
<ItemGroup>
<Reference Include="nunit.framework, Version=3.8.1.0, Culture=neutral, PublicKeyToken=2638cd05610744eb, processorArchitecture=MSIL">
<HintPath>$(SolutionDir)\packages\NUnit.3.8.1\lib\net45\nunit.framework.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="OpenTK, Version=3.0.0.0, Culture=neutral, PublicKeyToken=bad199fe84eb3df4, processorArchitecture=MSIL">
<HintPath>$(SolutionDir)\packages\OpenTK.3.0.0-git00009\lib\net20\OpenTK.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System" />
<Reference Include="SQLite.Net">
<HintPath>$(SolutionDir)\packages\SQLite.Net.Core-PCL.3.1.1\lib\portable-win8+net45+wp8+wpa81+MonoAndroid1+MonoTouch1\SQLite.Net.dll</HintPath>
</Reference>
<Reference Include="SQLite.Net.Platform.Win32">
<HintPath>$(SolutionDir)\packages\SQLite.Net-PCL.3.1.1\lib\net4\SQLite.Net.Platform.Win32.dll</HintPath>
</Reference>
<Reference Include="SQLite.Net.Platform.Generic">
<HintPath>$(SolutionDir)\packages\SQLite.Net-PCL.3.1.1\lib\net40\SQLite.Net.Platform.Generic.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<None Include="..\osu.licenseheader">
<Link>osu.licenseheader</Link>
</None>
<None Include="app.config" />
<None Include="packages.config" />
<None Include="OpenTK.dll.config" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\osu-framework\osu.Framework\osu.Framework.csproj">
<Project>{c76bf5b3-985e-4d39-95fe-97c9c879b83a}</Project>
<Name>osu.Framework</Name>
</ProjectReference>
<ProjectReference Include="..\osu.Game.Rulesets.Osu\osu.Game.Rulesets.Osu.csproj">
<Project>{c92a607b-1fdd-4954-9f92-03ff547d9080}</Project>
<Name>osu.Game.Rulesets.Osu</Name>
</ProjectReference>
<ProjectReference Include="..\osu.Game.Rulesets.Catch\osu.Game.Rulesets.Catch.csproj">
<Project>{58f6c80c-1253-4a0e-a465-b8c85ebeadf3}</Project>
<Name>osu.Game.Rulesets.Catch</Name>
</ProjectReference>
<ProjectReference Include="..\osu.Game.Rulesets.Mania\osu.Game.Rulesets.Mania.csproj">
<Project>{48f4582b-7687-4621-9cbe-5c24197cb536}</Project>
<Name>osu.Game.Rulesets.Mania</Name>
</ProjectReference>
<ProjectReference Include="..\osu.Game.Rulesets.Taiko\osu.Game.Rulesets.Taiko.csproj">
<Project>{f167e17a-7de6-4af5-b920-a5112296c695}</Project>
<Name>osu.Game.Rulesets.Taiko</Name>
</ProjectReference>
<ProjectReference Include="..\osu.Game\osu.Game.csproj">
<Project>{0D3FBF8A-7464-4CF7-8C90-3E7886DF2D4D}</Project>
<Name>osu.Game</Name>
</ProjectReference>
<ProjectReference Include="..\osu-resources\osu.Game.Resources\osu.Game.Resources.csproj">
<Project>{D9A367C9-4C1A-489F-9B05-A0CEA2B53B58}</Project>
<Name>osu.Game.Resources</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" />
</ItemGroup>
<ItemGroup>
<Compile Include="Beatmaps\IO\OszArchiveReaderTest.cs" />
<Compile Include="Beatmaps\IO\ImportBeatmapTest.cs" />
<Compile Include="Resources\Resource.cs" />
<Compile Include="Beatmaps\Formats\OsuLegacyDecoderTest.cs" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Resources\Soleily - Renatus %28Gamu%29 [Insane].osu" />
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
</Project>

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
-->
<packages>
<package id="NUnit" version="3.8.1" targetFramework="net461" />
<package id="OpenTK" version="3.0.0-git00009" targetFramework="net461" />
<package id="SQLite.Net.Core-PCL" version="3.1.1" targetFramework="net45" />
<package id="SQLite.Net-PCL" version="3.1.1" targetFramework="net45" />
</packages>

View File

@ -58,6 +58,23 @@ namespace osu.Game.Beatmaps
ComboColors = original?.ComboColors ?? ComboColors; ComboColors = original?.ComboColors ?? ComboColors;
HitObjects = original?.HitObjects ?? HitObjects; HitObjects = original?.HitObjects ?? HitObjects;
Storyboard = original?.Storyboard ?? Storyboard; Storyboard = original?.Storyboard ?? Storyboard;
if (original == null && Metadata == null)
{
// we may have no metadata in cases we weren't sourced from the database.
// let's fill it (and other related fields) so we don't need to null-check it in future usages.
BeatmapInfo = new BeatmapInfo
{
Metadata = new BeatmapMetadata
{
Artist = @"Unknown",
Title = @"Unknown",
Author = @"Unknown Creator",
},
Version = @"Normal",
Difficulty = new BeatmapDifficulty()
};
}
} }
} }

View File

@ -80,15 +80,29 @@ namespace osu.Game.Beatmaps
// Editor // Editor
// This bookmarks stuff is necessary because DB doesn't know how to store int[] // This bookmarks stuff is necessary because DB doesn't know how to store int[]
public string StoredBookmarks { get; set; } [JsonIgnore]
public string StoredBookmarks
{
get { return string.Join(",", Bookmarks); }
set
{
if (string.IsNullOrEmpty(value))
{
Bookmarks = new int[0];
return;
}
Bookmarks = value.Split(',').Select(v =>
{
int val;
bool result = int.TryParse(v, out val);
return new { result, val };
}).Where(p => p.result).Select(p => p.val).ToArray();
}
}
[Ignore] [Ignore]
[JsonIgnore] public int[] Bookmarks { get; set; } = new int[0];
public int[] Bookmarks
{
get { return StoredBookmarks.Split(',').Select(int.Parse).ToArray(); }
set { StoredBookmarks = string.Join(",", value); }
}
public double DistanceSpacing { get; set; } public double DistanceSpacing { get; set; }
public int BeatDivisor { get; set; } public int BeatDivisor { get; set; }

View File

@ -550,7 +550,7 @@ namespace osu.Game.Beatmaps
catch { return null; } catch { return null; }
} }
private string getPathForFile(string filename) => BeatmapSetInfo.Files.First(f => f.Filename == filename).FileInfo.StoragePath; private string getPathForFile(string filename) => BeatmapSetInfo.Files.First(f => string.Equals(f.Filename, filename, StringComparison.InvariantCultureIgnoreCase)).FileInfo.StoragePath;
protected override Texture GetBackground() protected override Texture GetBackground()
{ {
@ -573,6 +573,8 @@ namespace osu.Game.Beatmaps
} }
catch { return new TrackVirtual(); } catch { return new TrackVirtual(); }
} }
protected override Waveform GetWaveform() => new Waveform(store.GetStream(getPathForFile(Metadata.AudioFile)));
} }
/// <summary> /// <summary>

View File

@ -10,6 +10,28 @@ namespace osu.Game.Beatmaps
/// </summary> /// </summary>
public class BeatmapOnlineInfo public class BeatmapOnlineInfo
{ {
/// <summary>
/// The length in milliseconds of this beatmap's song.
/// </summary>
public double Length { get; set; }
/// <summary>
/// Whether or not this beatmap has a background video.
/// </summary>
public bool HasVideo { get; set; }
/// <summary>
/// The amount of circles in this beatmap.
/// </summary>
[JsonProperty(@"count_circles")]
public int CircleCount { get; set; }
/// <summary>
/// The amount of sliders in this beatmap.
/// </summary>
[JsonProperty(@"count_sliders")]
public int SliderCount { get; set; }
/// <summary> /// <summary>
/// The amount of plays this beatmap has. /// The amount of plays this beatmap has.
/// </summary> /// </summary>

View File

@ -1,7 +1,9 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using Newtonsoft.Json; using Newtonsoft.Json;
using osu.Game.Users;
namespace osu.Game.Beatmaps namespace osu.Game.Beatmaps
{ {
@ -10,6 +12,26 @@ namespace osu.Game.Beatmaps
/// </summary> /// </summary>
public class BeatmapSetOnlineInfo public class BeatmapSetOnlineInfo
{ {
/// <summary>
/// The author of the beatmaps in this set.
/// </summary>
public User Author;
/// <summary>
/// The date this beatmap set was submitted to the online listing.
/// </summary>
public DateTimeOffset Submitted { get; set; }
/// <summary>
/// The date this beatmap set was ranked.
/// </summary>
public DateTimeOffset? Ranked { get; set; }
/// <summary>
/// The date this beatmap set was last updated.
/// </summary>
public DateTimeOffset? LastUpdated { get; set; }
/// <summary> /// <summary>
/// The different sizes of cover art for this beatmap set. /// The different sizes of cover art for this beatmap set.
/// </summary> /// </summary>
@ -22,6 +44,11 @@ namespace osu.Game.Beatmaps
[JsonProperty(@"previewUrl")] [JsonProperty(@"previewUrl")]
public string Preview { get; set; } public string Preview { get; set; }
/// <summary>
/// The beats per minute of this beatmap set's song.
/// </summary>
public double BPM { get; set; }
/// <summary> /// <summary>
/// The amount of plays this beatmap set has. /// The amount of plays this beatmap set has.
/// </summary> /// </summary>

View File

@ -31,6 +31,8 @@ namespace osu.Game.Beatmaps.Drawables
public Action<BeatmapInfo> HideDifficultyRequested; public Action<BeatmapInfo> HideDifficultyRequested;
public Action<BeatmapInfo> EditRequested;
public BeatmapSetHeader Header; public BeatmapSetHeader Header;
public List<BeatmapPanel> BeatmapPanels; public List<BeatmapPanel> BeatmapPanels;
@ -87,7 +89,8 @@ namespace osu.Game.Beatmaps.Drawables
Alpha = 0, Alpha = 0,
GainedSelection = panelGainedSelection, GainedSelection = panelGainedSelection,
HideRequested = p => HideDifficultyRequested?.Invoke(p), HideRequested = p => HideDifficultyRequested?.Invoke(p),
StartRequested = p => { StartRequested?.Invoke(p.Beatmap); }, StartRequested = p => StartRequested?.Invoke(p.Beatmap),
EditRequested = p => EditRequested?.Invoke(p.Beatmap),
RelativeSizeAxes = Axes.X, RelativeSizeAxes = Axes.X,
}).ToList(); }).ToList();

View File

@ -613,7 +613,7 @@ namespace osu.Game.Beatmaps.Formats
string line; string line;
while ((line = stream.ReadLine()) != null) while ((line = stream.ReadLine()) != null)
{ {
if (string.IsNullOrEmpty(line)) if (string.IsNullOrWhiteSpace(line))
continue; continue;
if (line.StartsWith("//")) if (line.StartsWith("//"))
@ -679,10 +679,12 @@ namespace osu.Game.Beatmaps.Formats
private KeyValuePair<string, string> splitKeyVal(string line, char separator) private KeyValuePair<string, string> splitKeyVal(string line, char separator)
{ {
var split = line.Trim().Split(new[] { separator }, 2);
return new KeyValuePair<string, string> return new KeyValuePair<string, string>
( (
line.Remove(line.IndexOf(separator)).Trim(), split[0].Trim(),
line.Substring(line.IndexOf(separator) + 1).Trim() split.Length > 1 ? split[1].Trim() : string.Empty
); );
} }

View File

@ -8,7 +8,7 @@ namespace osu.Game.Beatmaps.Timing
/// <summary> /// <summary>
/// The minimum duration required for a break to have any effect. /// The minimum duration required for a break to have any effect.
/// </summary> /// </summary>
private const double min_break_duration = 650; public const double MIN_BREAK_DURATION = 650;
/// <summary> /// <summary>
/// The break start time. /// The break start time.
@ -28,6 +28,6 @@ namespace osu.Game.Beatmaps.Timing
/// <summary> /// <summary>
/// Whether the break has any effect. Breaks that are too short are culled before they are added to the beatmap. /// Whether the break has any effect. Breaks that are too short are culled before they are added to the beatmap.
/// </summary> /// </summary>
public bool HasEffect => Duration >= min_break_duration; public bool HasEffect => Duration >= MIN_BREAK_DURATION;
} }
} }

View File

@ -43,6 +43,7 @@ namespace osu.Game.Beatmaps
protected abstract Beatmap GetBeatmap(); protected abstract Beatmap GetBeatmap();
protected abstract Texture GetBackground(); protected abstract Texture GetBackground();
protected abstract Track GetTrack(); protected abstract Track GetTrack();
protected virtual Waveform GetWaveform() => new Waveform();
private Beatmap beatmap; private Beatmap beatmap;
private readonly object beatmapLock = new object(); private readonly object beatmapLock = new object();
@ -96,6 +97,17 @@ namespace osu.Game.Beatmaps
} }
} }
private Waveform waveform;
private readonly object waveformLock = new object();
public Waveform Waveform
{
get
{
lock (waveformLock)
return waveform ?? (waveform = GetWaveform());
}
}
public bool TrackLoaded => track != null; public bool TrackLoaded => track != null;
public void TransferTo(WorkingBeatmap other) public void TransferTo(WorkingBeatmap other)
@ -114,6 +126,8 @@ namespace osu.Game.Beatmaps
{ {
background?.Dispose(); background?.Dispose();
background = null; background = null;
waveform?.Dispose();
} }
public void DisposeTrack() public void DisposeTrack()

View File

@ -54,6 +54,9 @@ namespace osu.Game.Configuration
// Graphics // Graphics
Set(OsuSetting.ShowFpsDisplay, false); Set(OsuSetting.ShowFpsDisplay, false);
Set(OsuSetting.ShowStoryboard, true);
Set(OsuSetting.CursorRotation, true);
Set(OsuSetting.MenuParallax, true); Set(OsuSetting.MenuParallax, true);
Set(OsuSetting.SnakingInSliders, true); Set(OsuSetting.SnakingInSliders, true);
@ -87,6 +90,7 @@ namespace osu.Game.Configuration
GameplayCursorSize, GameplayCursorSize,
AutoCursorSize, AutoCursorSize,
DimLevel, DimLevel,
ShowStoryboard,
KeyOverlay, KeyOverlay,
FloatingComments, FloatingComments,
PlaybackSpeed, PlaybackSpeed,
@ -96,6 +100,7 @@ namespace osu.Game.Configuration
AudioOffset, AudioOffset,
MenuMusic, MenuMusic,
MenuVoice, MenuVoice,
CursorRotation,
MenuParallax, MenuParallax,
BeatmapDetailTab, BeatmapDetailTab,
Username, Username,

View File

@ -20,13 +20,14 @@ namespace osu.Game.Graphics.Cursor
{ {
protected override Drawable CreateCursor() => new Cursor(); protected override Drawable CreateCursor() => new Cursor();
private Bindable<bool> cursorRotate;
private bool dragging; private bool dragging;
private bool startRotation; private bool startRotation;
protected override bool OnMouseMove(InputState state) protected override bool OnMouseMove(InputState state)
{ {
if (dragging) if (cursorRotate && dragging)
{ {
Debug.Assert(state.Mouse.PositionMouseDown != null); Debug.Assert(state.Mouse.PositionMouseDown != null);
@ -102,6 +103,12 @@ namespace osu.Game.Graphics.Cursor
ActiveCursor.ScaleTo(0, 500, Easing.In); ActiveCursor.ScaleTo(0, 500, Easing.In);
} }
[BackgroundDependencyLoader]
private void load(OsuConfigManager config)
{
cursorRotate = config.GetBindable<bool>(OsuSetting.CursorRotation);
}
public class Cursor : Container public class Cursor : Container
{ {
private Container cursorContainer; private Container cursorContainer;

View File

@ -1,27 +0,0 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using osu.Framework.Graphics.Containers;
using OpenTK;
using osu.Framework.Graphics;
namespace osu.Game.Graphics.Processing
{
internal class RatioAdjust : Container
{
public RatioAdjust()
{
RelativeSizeAxes = Axes.Both;
}
protected override void Update()
{
base.Update();
Vector2 parent = Parent.DrawSize;
Scale = new Vector2(Math.Min(parent.Y / 768f, parent.X / 1024f));
Size = new Vector2(1 / Scale.X);
}
}
}

View File

@ -15,32 +15,91 @@ namespace osu.Game.Graphics.UserInterface
{ {
public class IconButton : OsuClickableContainer public class IconButton : OsuClickableContainer
{ {
private readonly SpriteIcon icon; private const float button_size = 30;
private readonly Box hover;
private readonly Container content;
private Color4? flashColour;
/// <summary>
/// The colour that should be flashed when the <see cref="IconButton"/> is clicked.
/// </summary>
public Color4 FlashColour
{
get { return flashColour ?? Color4.White; }
set { flashColour = value; }
}
private Color4? iconColour;
/// <summary>
/// The icon colour. This does not affect <see cref="IconButton.Colour"/>.
/// </summary>
public Color4 IconColour
{
get { return iconColour ?? Color4.White; }
set
{
iconColour = value;
icon.Colour = value;
}
}
private Color4? iconHoverColour;
/// <summary>
/// The icon colour while the <see cref="IconButton"/> is hovered.
/// </summary>
public Color4 IconHoverColour
{
get { return iconHoverColour ?? IconColour; }
set { iconHoverColour = value; }
}
private Color4? hoverColour;
/// <summary>
/// The background colour of the <see cref="IconButton"/> while it is hovered.
/// </summary>
public Color4 HoverColour
{
get { return hoverColour ?? Color4.White; }
set
{
hoverColour = value;
hover.Colour = value;
}
}
/// <summary>
/// The icon.
/// </summary>
public FontAwesome Icon public FontAwesome Icon
{ {
get { return icon.Icon; } get { return icon.Icon; }
set { icon.Icon = value; } set { icon.Icon = value; }
} }
private const float button_size = 30; /// <summary>
private Color4 flashColour; /// The icon scale. This does not affect <see cref="IconButton.Scale"/>.
/// </summary>
public Vector2 IconScale public Vector2 IconScale
{ {
get { return icon.Scale; } get { return icon.Scale; }
set { icon.Scale = value; } set { icon.Scale = value; }
} }
/// <summary>
/// The size of the <see cref="IconButton"/> while it is not being pressed.
/// </summary>
public Vector2 ButtonSize
{
get { return content.Size; }
set { content.Size = value; }
}
private readonly Container content;
private readonly SpriteIcon icon;
private readonly Box hover;
public IconButton() public IconButton()
{ {
AutoSizeAxes = Axes.Both; AutoSizeAxes = Axes.Both;
Origin = Anchor.Centre;
Anchor = Anchor.Centre;
Children = new Drawable[] Children = new Drawable[]
{ {
content = new Container content = new Container
@ -48,7 +107,6 @@ namespace osu.Game.Graphics.UserInterface
Origin = Anchor.Centre, Origin = Anchor.Centre,
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
Size = new Vector2(button_size), Size = new Vector2(button_size),
CornerRadius = 5, CornerRadius = 5,
Masking = true, Masking = true,
EdgeEffect = new EdgeEffectParameters EdgeEffect = new EdgeEffectParameters
@ -78,8 +136,11 @@ namespace osu.Game.Graphics.UserInterface
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(OsuColour colours) private void load(OsuColour colours)
{ {
hover.Colour = colours.Yellow.Opacity(0.6f); if (hoverColour == null)
flashColour = colours.Yellow; HoverColour = colours.Yellow.Opacity(0.6f);
if (flashColour == null)
FlashColour = colours.Yellow;
Enabled.ValueChanged += enabled => this.FadeColour(enabled ? Color4.White : colours.Gray9, 200, Easing.OutQuint); Enabled.ValueChanged += enabled => this.FadeColour(enabled ? Color4.White : colours.Gray9, 200, Easing.OutQuint);
} }
@ -87,18 +148,20 @@ namespace osu.Game.Graphics.UserInterface
protected override bool OnHover(InputState state) protected override bool OnHover(InputState state)
{ {
hover.FadeIn(500, Easing.OutQuint); hover.FadeIn(500, Easing.OutQuint);
icon.FadeColour(IconHoverColour, 500, Easing.OutQuint);
return base.OnHover(state); return base.OnHover(state);
} }
protected override void OnHoverLost(InputState state) protected override void OnHoverLost(InputState state)
{ {
hover.FadeOut(500, Easing.OutQuint); hover.FadeOut(500, Easing.OutQuint);
icon.FadeColour(IconColour, 500, Easing.OutQuint);
base.OnHoverLost(state); base.OnHoverLost(state);
} }
protected override bool OnClick(InputState state) protected override bool OnClick(InputState state)
{ {
hover.FlashColour(flashColour, 800, Easing.OutQuint); hover.FlashColour(FlashColour, 800, Easing.OutQuint);
return base.OnClick(state); return base.OnClick(state);
} }

View File

@ -59,7 +59,7 @@ namespace osu.Game.Graphics.UserInterface
public class OsuTabItem : TabItem<T>, IHasAccentColour public class OsuTabItem : TabItem<T>, IHasAccentColour
{ {
protected readonly SpriteText Text; protected readonly SpriteText Text;
private readonly Box box; protected readonly Box Bar;
private Color4 accentColour; private Color4 accentColour;
public Color4 AccentColour public Color4 AccentColour
@ -77,13 +77,13 @@ namespace osu.Game.Graphics.UserInterface
private void fadeActive() private void fadeActive()
{ {
box.FadeIn(transition_length, Easing.OutQuint); Bar.FadeIn(transition_length, Easing.OutQuint);
Text.FadeColour(Color4.White, transition_length, Easing.OutQuint); Text.FadeColour(Color4.White, transition_length, Easing.OutQuint);
} }
private void fadeInactive() private void fadeInactive()
{ {
box.FadeOut(transition_length, Easing.OutQuint); Bar.FadeOut(transition_length, Easing.OutQuint);
Text.FadeColour(AccentColour, transition_length, Easing.OutQuint); Text.FadeColour(AccentColour, transition_length, Easing.OutQuint);
} }
@ -123,7 +123,7 @@ namespace osu.Game.Graphics.UserInterface
TextSize = 14, TextSize = 14,
Font = @"Exo2.0-Bold", // Font should only turn bold when active? Font = @"Exo2.0-Bold", // Font should only turn bold when active?
}, },
box = new Box Bar = new Box
{ {
RelativeSizeAxes = Axes.X, RelativeSizeAxes = Axes.X,
Height = 1, Height = 1,

View File

@ -49,7 +49,7 @@ namespace osu.Game.IO.Legacy
int len = ReadInt32(); int len = ReadInt32();
if (len > 0) return ReadBytes(len); if (len > 0) return ReadBytes(len);
if (len < 0) return null; if (len < 0) return null;
return new byte[0]; return Array.Empty<byte>();
} }
/// <summary> Reads a char array from the buffer, handling nulls and the array length. </summary> /// <summary> Reads a char array from the buffer, handling nulls and the array length. </summary>
@ -58,7 +58,7 @@ namespace osu.Game.IO.Legacy
int len = ReadInt32(); int len = ReadInt32();
if (len > 0) return ReadChars(len); if (len > 0) return ReadChars(len);
if (len < 0) return null; if (len < 0) return null;
return new char[0]; return Array.Empty<char>();
} }
/// <summary> Reads a DateTime from the buffer. </summary> /// <summary> Reads a DateTime from the buffer. </summary>

View File

@ -101,18 +101,16 @@ namespace osu.Game.Online.API
} }
break; break;
case APIState.Offline: case APIState.Offline:
case APIState.Connecting:
//work to restore a connection... //work to restore a connection...
if (!HasLogin) if (!HasLogin)
{ {
//OsuGame.Scheduler.Add(() => { OsuGame.ShowLogin(); });
State = APIState.Offline; State = APIState.Offline;
Thread.Sleep(500); Thread.Sleep(50);
continue; continue;
} }
if (State < APIState.Connecting) State = APIState.Connecting;
State = APIState.Connecting;
if (!authentication.HasValidAccessToken && !authentication.AuthenticateWithLogin(Username, Password)) if (!authentication.HasValidAccessToken && !authentication.AuthenticateWithLogin(Username, Password))
{ {
@ -125,7 +123,8 @@ namespace osu.Game.Online.API
var userReq = new GetUserRequest(); var userReq = new GetUserRequest();
userReq.Success += u => { userReq.Success += u =>
{
LocalUser.Value = u; LocalUser.Value = u;
//we're connected! //we're connected!
State = APIState.Online; State = APIState.Online;
@ -133,16 +132,14 @@ namespace osu.Game.Online.API
}; };
if (!handleRequest(userReq)) if (!handleRequest(userReq))
{
State = APIState.Failing;
continue; continue;
}
break; break;
} }
//hard bail if we can't get a valid access token. //hard bail if we can't get a valid access token.
if (authentication.RequestAccessToken() == null) if (authentication.RequestAccessToken() == null)
{ {
Logout(false);
State = APIState.Offline; State = APIState.Offline;
continue; continue;
} }
@ -162,20 +159,12 @@ namespace osu.Game.Online.API
} }
} }
private void clearCredentials()
{
Username = null;
Password = null;
}
public void Login(string username, string password) public void Login(string username, string password)
{ {
Debug.Assert(State == APIState.Offline); Debug.Assert(State == APIState.Offline);
Username = username; Username = username;
Password = password; Password = password;
State = APIState.Connecting;
} }
/// <summary> /// <summary>
@ -204,7 +193,7 @@ namespace osu.Game.Online.API
switch (statusCode) switch (statusCode)
{ {
case HttpStatusCode.Unauthorized: case HttpStatusCode.Unauthorized:
State = APIState.Offline; Logout(false);
return true; return true;
case HttpStatusCode.RequestTimeout: case HttpStatusCode.RequestTimeout:
failureCount++; failureCount++;
@ -215,6 +204,7 @@ namespace osu.Game.Online.API
return false; return false;
State = APIState.Failing; State = APIState.Failing;
flushQueue();
return true; return true;
} }
@ -235,33 +225,21 @@ namespace osu.Game.Online.API
public APIState State public APIState State
{ {
get { return state; } get { return state; }
set private set
{ {
APIState oldState = state; APIState oldState = state;
APIState newState = value; APIState newState = value;
state = value; state = value;
switch (state)
{
case APIState.Failing:
case APIState.Offline:
flushQueue();
break;
}
if (oldState != newState) if (oldState != newState)
{ {
//OsuGame.Scheduler.Add(delegate log.Add($@"We just went {newState}!");
Scheduler.Add(delegate
{ {
//NotificationOverlay.ShowMessage($@"We just went {newState}!", newState == APIState.Online ? Color4.YellowGreen : Color4.OrangeRed, 5000); components.ForEach(c => c.APIStateChanged(this, newState));
log.Add($@"We just went {newState}!"); OnStateChange?.Invoke(oldState, newState);
Scheduler.Add(delegate });
{
components.ForEach(c => c.APIStateChanged(this, newState));
OnStateChange?.Invoke(oldState, newState);
});
}
} }
} }
} }
@ -292,11 +270,12 @@ namespace osu.Game.Online.API
} }
} }
public void Logout() public void Logout(bool clearUsername = true)
{ {
clearCredentials(); flushQueue();
if (clearUsername) Username = null;
Password = null;
authentication.Clear(); authentication.Clear();
State = APIState.Offline;
LocalUser.Value = createGuestUser(); LocalUser.Value = createGuestUser();
} }

View File

@ -2,7 +2,6 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System; using System;
using osu.Framework.Extensions;
using osu.Framework.IO.Network; using osu.Framework.IO.Network;
namespace osu.Game.Online.API namespace osu.Game.Online.API
@ -70,13 +69,11 @@ namespace osu.Game.Online.API
protected virtual string Uri => $@"{API.Endpoint}/api/v2/{Target}"; protected virtual string Uri => $@"{API.Endpoint}/api/v2/{Target}";
private double remainingTime => Math.Max(0, Timeout - (DateTime.Now.TotalMilliseconds() - (startTime ?? 0))); private double remainingTime => Math.Max(0, Timeout - (DateTimeOffset.UtcNow - (startTime ?? DateTimeOffset.MinValue)).TotalMilliseconds);
public bool ExceededTimeout => remainingTime == 0; public bool ExceededTimeout => remainingTime == 0;
private double? startTime; private DateTimeOffset? startTime;
public double StartTime => startTime ?? -1;
protected APIAccess API; protected APIAccess API;
protected WebRequest WebRequest; protected WebRequest WebRequest;
@ -96,7 +93,7 @@ namespace osu.Game.Online.API
return; return;
if (startTime == null) if (startTime == null)
startTime = DateTime.Now.TotalMilliseconds(); startTime = DateTimeOffset.UtcNow;
if (remainingTime <= 0) if (remainingTime <= 0)
throw new TimeoutException(@"API request timeout hit"); throw new TimeoutException(@"API request timeout hit");

View File

@ -4,7 +4,6 @@
using System; using System;
using System.Globalization; using System.Globalization;
using Newtonsoft.Json; using Newtonsoft.Json;
using osu.Framework.Extensions;
namespace osu.Game.Online.API namespace osu.Game.Online.API
{ {
@ -22,12 +21,12 @@ namespace osu.Game.Online.API
{ {
get get
{ {
return AccessTokenExpiry - DateTime.Now.ToUnixTimestamp(); return AccessTokenExpiry - DateTimeOffset.UtcNow.ToUnixTimeSeconds();
} }
set set
{ {
AccessTokenExpiry = DateTime.Now.AddSeconds(value).ToUnixTimestamp(); AccessTokenExpiry = DateTimeOffset.Now.AddSeconds(value).ToUnixTimeSeconds();
} }
} }

View File

@ -10,7 +10,7 @@ namespace osu.Game.Online.API.Requests
{ {
private readonly BeatmapInfo beatmap; private readonly BeatmapInfo beatmap;
private string lookupString => beatmap.OnlineBeatmapID > 0 ? beatmap.OnlineBeatmapID.ToString() : $@"lookup?checksum={beatmap.Hash}&filename={beatmap.Path}"; private string lookupString => beatmap.OnlineBeatmapID > 0 ? beatmap.OnlineBeatmapID.ToString() : $@"lookup?checksum={beatmap.Hash}&filename={System.Uri.EscapeUriString(beatmap.Path)}";
public GetBeatmapDetailsRequest(BeatmapInfo beatmap) public GetBeatmapDetailsRequest(BeatmapInfo beatmap)
{ {

View File

@ -8,6 +8,7 @@ using osu.Game.Beatmaps;
using osu.Game.Overlays; using osu.Game.Overlays;
using osu.Game.Overlays.Direct; using osu.Game.Overlays.Direct;
using osu.Game.Rulesets; using osu.Game.Rulesets;
using osu.Game.Users;
namespace osu.Game.Online.API.Requests namespace osu.Game.Online.API.Requests
{ {
@ -49,6 +50,12 @@ namespace osu.Game.Online.API.Requests
[JsonProperty(@"id")] [JsonProperty(@"id")]
private int onlineId { get; set; } private int onlineId { get; set; }
[JsonProperty(@"creator")]
private string creatorUsername { get; set; }
[JsonProperty(@"user_id")]
private long creatorId = 1;
[JsonProperty(@"beatmaps")] [JsonProperty(@"beatmaps")]
private IEnumerable<GetBeatmapSetsBeatmapResponse> beatmaps { get; set; } private IEnumerable<GetBeatmapSetsBeatmapResponse> beatmaps { get; set; }
@ -60,6 +67,11 @@ namespace osu.Game.Online.API.Requests
Metadata = this, Metadata = this,
OnlineInfo = new BeatmapSetOnlineInfo OnlineInfo = new BeatmapSetOnlineInfo
{ {
Author = new User
{
Id = creatorId,
Username = creatorUsername,
},
Covers = covers, Covers = covers,
Preview = preview, Preview = preview,
PlayCount = playCount, PlayCount = playCount,

View File

@ -96,7 +96,7 @@ namespace osu.Game.Online.API.Requests
} }
[JsonProperty(@"statistics")] [JsonProperty(@"statistics")]
private Dictionary<string, dynamic> jsonStats private Dictionary<string, object> jsonStats
{ {
set set
{ {

View File

@ -23,6 +23,7 @@ namespace osu.Game.Online.API.Requests
req.Method = HttpMethod.POST; req.Method = HttpMethod.POST;
req.AddParameter(@"target_type", message.TargetType.GetDescription()); req.AddParameter(@"target_type", message.TargetType.GetDescription());
req.AddParameter(@"target_id", message.TargetId.ToString()); req.AddParameter(@"target_id", message.TargetId.ToString());
req.AddParameter(@"is_action", message.IsAction.ToString().ToLower());
req.AddParameter(@"message", message.Content); req.AddParameter(@"message", message.Content);
return req; return req;
@ -30,4 +31,4 @@ namespace osu.Game.Online.API.Requests
protected override string Target => @"chat/messages"; protected override string Target => @"chat/messages";
} }
} }

View File

@ -1,25 +1,13 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using osu.Game.Users;
namespace osu.Game.Online.Chat namespace osu.Game.Online.Chat
{ {
public class ErrorMessage : Message public class ErrorMessage : InfoMessage
{ {
private static int errorId = -1; public ErrorMessage(string message) : base(message)
public ErrorMessage(string message) : base(errorId--)
{ {
Timestamp = DateTimeOffset.Now; Sender.Colour = @"ff0000";
Content = message;
Sender = new User
{
Username = @"system",
Colour = @"ff0000",
};
} }
} }
} }

View File

@ -0,0 +1,25 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using osu.Game.Users;
namespace osu.Game.Online.Chat
{
public class InfoMessage : Message
{
private static int infoID = -1;
public InfoMessage(string message) : base(infoID--)
{
Timestamp = DateTimeOffset.Now;
Content = message;
Sender = new User
{
Username = @"system",
Colour = @"0000ff",
};
}
}
}

View File

@ -23,6 +23,9 @@ namespace osu.Game.Online.Chat
[JsonProperty(@"target_id")] [JsonProperty(@"target_id")]
public int TargetId; public int TargetId;
[JsonProperty(@"is_action")]
public bool IsAction;
[JsonProperty(@"timestamp")] [JsonProperty(@"timestamp")]
public DateTimeOffset Timestamp; public DateTimeOffset Timestamp;

View File

@ -47,6 +47,8 @@ namespace osu.Game
private UserProfileOverlay userProfile; private UserProfileOverlay userProfile;
private BeatmapSetOverlay beatmapSetOverlay;
public virtual Storage GetStorageForStableInstall() => null; public virtual Storage GetStorageForStableInstall() => null;
private Intro intro private Intro intro
@ -186,10 +188,11 @@ namespace osu.Game
GetToolbarHeight = () => ToolbarOffset, GetToolbarHeight = () => ToolbarOffset,
Depth = -1 Depth = -1
}, overlayContent.Add); }, overlayContent.Add);
LoadComponentAsync(userProfile = new UserProfileOverlay { Depth = -2 }, mainContent.Add); LoadComponentAsync(beatmapSetOverlay = new BeatmapSetOverlay { Depth = -2 }, mainContent.Add);
LoadComponentAsync(userProfile = new UserProfileOverlay { Depth = -3 }, mainContent.Add);
LoadComponentAsync(musicController = new MusicController LoadComponentAsync(musicController = new MusicController
{ {
Depth = -3, Depth = -4,
Position = new Vector2(0, Toolbar.HEIGHT), Position = new Vector2(0, Toolbar.HEIGHT),
Anchor = Anchor.TopRight, Anchor = Anchor.TopRight,
Origin = Anchor.TopRight, Origin = Anchor.TopRight,
@ -197,7 +200,7 @@ namespace osu.Game
LoadComponentAsync(notificationOverlay = new NotificationOverlay LoadComponentAsync(notificationOverlay = new NotificationOverlay
{ {
Depth = -3, Depth = -4,
Anchor = Anchor.TopRight, Anchor = Anchor.TopRight,
Origin = Anchor.TopRight, Origin = Anchor.TopRight,
}, overlayContent.Add); }, overlayContent.Add);
@ -223,6 +226,7 @@ namespace osu.Game
dependencies.Cache(chat); dependencies.Cache(chat);
dependencies.Cache(userProfile); dependencies.Cache(userProfile);
dependencies.Cache(musicController); dependencies.Cache(musicController);
dependencies.Cache(beatmapSetOverlay);
dependencies.Cache(notificationOverlay); dependencies.Cache(notificationOverlay);
dependencies.Cache(dialogOverlay); dependencies.Cache(dialogOverlay);

View File

@ -15,7 +15,6 @@ using osu.Game.Beatmaps;
using osu.Game.Configuration; using osu.Game.Configuration;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Graphics.Cursor; using osu.Game.Graphics.Cursor;
using osu.Game.Graphics.Processing;
using osu.Game.Online.API; using osu.Game.Online.API;
using SQLite.Net; using SQLite.Net;
using osu.Framework.Graphics.Performance; using osu.Framework.Graphics.Performance;
@ -82,6 +81,13 @@ namespace osu.Game
protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent) => protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent) =>
dependencies = new DependencyContainer(base.CreateLocalDependencies(parent)); dependencies = new DependencyContainer(base.CreateLocalDependencies(parent));
private SQLiteConnection createConnection()
{
var conn = Host.Storage.GetDatabase(@"client");
conn.BusyTimeout = new TimeSpan(TimeSpan.TicksPerSecond * 10);
return conn;
}
private SQLiteConnection connection; private SQLiteConnection connection;
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
@ -90,8 +96,7 @@ namespace osu.Game
dependencies.Cache(this); dependencies.Cache(this);
dependencies.Cache(LocalConfig); dependencies.Cache(LocalConfig);
connection = Host.Storage.GetDatabase(@"client"); connection = createConnection();
connection.CreateTable<StoreVersion>(); connection.CreateTable<StoreVersion>();
dependencies.Cache(API = new APIAccess dependencies.Cache(API = new APIAccess
@ -180,7 +185,7 @@ namespace osu.Game
GlobalKeyBindingInputManager globalBinding; GlobalKeyBindingInputManager globalBinding;
base.Content.Add(new RatioAdjust base.Content.Add(new DrawSizePreservingFillContainer
{ {
Children = new Drawable[] Children = new Drawable[]
{ {

View File

@ -0,0 +1,131 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Beatmaps;
using osu.Game.Graphics.Sprites;
using osu.Game.Users;
using OpenTK;
using OpenTK.Graphics;
using osu.Framework.Allocation;
using osu.Game.Graphics.Containers;
using osu.Framework.Graphics.Cursor;
namespace osu.Game.Overlays.BeatmapSet
{
public class AuthorInfo : Container
{
private const float height = 50;
private readonly UpdateableAvatar avatar;
private readonly ClickableArea clickableArea;
private readonly FillFlowContainer fields;
private UserProfileOverlay profile;
private BeatmapSetInfo beatmapSet;
public BeatmapSetInfo BeatmapSet
{
get { return beatmapSet; }
set
{
if (value == beatmapSet) return;
beatmapSet = value;
var i = BeatmapSet.OnlineInfo;
avatar.User = i.Author;
clickableArea.Action = () => profile?.ShowUser(avatar.User);
fields.Children = new Drawable[]
{
new Field("made by", i.Author.Username, @"Exo2.0-RegularItalic"),
new Field("submitted on", i.Submitted.ToString(@"MMM d, yyyy"), @"Exo2.0-Bold")
{
Margin = new MarginPadding { Top = 5 },
},
};
if (i.Ranked.HasValue)
{
fields.Add(new Field("ranked on ", i.Ranked.Value.ToString(@"MMM d, yyyy"), @"Exo2.0-Bold"));
}
else if (i.LastUpdated.HasValue)
{
fields.Add(new Field("last updated on ", i.LastUpdated.Value.ToString(@"MMM d, yyyy"), @"Exo2.0-Bold"));
}
}
}
public AuthorInfo()
{
RelativeSizeAxes = Axes.X;
Height = height;
Children = new Drawable[]
{
clickableArea = new ClickableArea
{
AutoSizeAxes = Axes.Both,
CornerRadius = 3,
Masking = true,
Child = avatar = new UpdateableAvatar
{
Size = new Vector2(height),
},
EdgeEffect = new EdgeEffectParameters
{
Colour = Color4.Black.Opacity(0.25f),
Type = EdgeEffectType.Shadow,
Radius = 3,
Offset = new Vector2(0f, 1f),
},
},
fields = new FillFlowContainer
{
RelativeSizeAxes = Axes.Both,
Direction = FillDirection.Vertical,
Padding = new MarginPadding { Left = height + 5 },
},
};
}
[BackgroundDependencyLoader(true)]
private void load(UserProfileOverlay profile)
{
this.profile = profile;
clickableArea.Action = () => profile?.ShowUser(avatar.User);
}
private class Field : FillFlowContainer
{
public Field(string first, string second, string secondFont)
{
AutoSizeAxes = Axes.Both;
Direction = FillDirection.Horizontal;
Children = new[]
{
new OsuSpriteText
{
Text = $"{first} ",
TextSize = 13,
},
new OsuSpriteText
{
Text = second,
TextSize = 13,
Font = secondFont,
},
};
}
}
private class ClickableArea : OsuClickableContainer, IHasTooltip
{
public string TooltipText => @"View Profile";
}
}
}

View File

@ -0,0 +1,130 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Cursor;
using osu.Game.Beatmaps;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using OpenTK;
namespace osu.Game.Overlays.BeatmapSet
{
public class BasicStats : Container
{
private readonly Statistic length, bpm, circleCount, sliderCount;
private BeatmapSetInfo beatmapSet;
public BeatmapSetInfo BeatmapSet
{
get { return beatmapSet; }
set
{
if (value == beatmapSet) return;
beatmapSet = value;
bpm.Value = BeatmapSet.OnlineInfo.BPM.ToString(@"0.##");
}
}
private BeatmapInfo beatmap;
public BeatmapInfo Beatmap
{
get { return beatmap; }
set
{
if (value == beatmap) return;
beatmap = value;
length.Value = TimeSpan.FromMilliseconds(beatmap.OnlineInfo.Length).ToString(@"m\:ss");
circleCount.Value = beatmap.OnlineInfo.CircleCount.ToString("N0");
sliderCount.Value = beatmap.OnlineInfo.SliderCount.ToString("N0");
}
}
public BasicStats()
{
Child = new FillFlowContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Direction = FillDirection.Horizontal,
Children = new[]
{
length = new Statistic(FontAwesome.fa_clock_o, "Length") { Width = 0.25f },
bpm = new Statistic(FontAwesome.fa_circle, "BPM") { Width = 0.25f },
circleCount = new Statistic(FontAwesome.fa_circle_o, "Circle Count") { Width = 0.25f },
sliderCount = new Statistic(FontAwesome.fa_circle, "Slider Count") { Width = 0.25f },
},
};
}
private class Statistic : Container, IHasTooltip
{
private readonly string name;
private readonly OsuSpriteText value;
public string TooltipText => name;
public string Value
{
get { return value.Text; }
set { this.value.Text = value; }
}
public Statistic(FontAwesome icon, string name)
{
this.name = name;
RelativeSizeAxes = Axes.X;
AutoSizeAxes = Axes.Y;
Children = new Drawable[]
{
new Container
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
AutoSizeAxes = Axes.Both,
Children = new Drawable[]
{
new SpriteIcon
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.Centre,
Icon = FontAwesome.fa_square,
Size = new Vector2(13),
Rotation = 45,
Colour = OsuColour.FromHex(@"441288"),
},
new SpriteIcon
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.Centre,
Icon = icon,
Size = new Vector2(13),
Colour = OsuColour.FromHex(@"f7dd55"),
Scale = new Vector2(0.8f),
},
value = new OsuSpriteText
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
TextSize = 13,
Font = @"Exo2.0-Bold",
Margin = new MarginPadding { Left = 10 },
},
},
},
};
}
[BackgroundDependencyLoader]
private void load(OsuColour colour)
{
value.Colour = colour.Yellow;
}
}
}
}

View File

@ -0,0 +1,312 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using System.Linq;
using osu.Framework;
using osu.Framework.Allocation;
using osu.Framework.Configuration;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Input;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Drawables;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites;
using OpenTK;
using OpenTK.Graphics;
namespace osu.Game.Overlays.BeatmapSet
{
public class BeatmapPicker : Container
{
private const float tile_icon_padding = 7;
private const float tile_spacing = 2;
private readonly DifficultiesContainer difficulties;
private readonly OsuSpriteText version, starRating;
private readonly Statistic plays, favourites;
public readonly Bindable<BeatmapInfo> Beatmap = new Bindable<BeatmapInfo>();
private BeatmapSetInfo beatmapSet;
public BeatmapSetInfo BeatmapSet
{
get { return beatmapSet; }
set
{
if (value == beatmapSet) return;
beatmapSet = value;
Beatmap.Value = BeatmapSet.Beatmaps.First();
plays.Value = BeatmapSet.OnlineInfo.PlayCount;
favourites.Value = BeatmapSet.OnlineInfo.FavouriteCount;
difficulties.ChildrenEnumerable = BeatmapSet.Beatmaps.Select(b => new DifficultySelectorButton(b)
{
State = DifficultySelectorState.NotSelected,
OnHovered = beatmap =>
{
showBeatmap(beatmap);
starRating.Text = beatmap.StarDifficulty.ToString("Star Difficulty 0.##");
starRating.FadeIn(100);
},
OnClicked = beatmap =>
{
Beatmap.Value = beatmap;
},
});
updateDifficultyButtons();
}
}
public BeatmapPicker()
{
RelativeSizeAxes = Axes.X;
AutoSizeAxes = Axes.Y;
Children = new Drawable[]
{
new FillFlowContainer
{
AutoSizeAxes = Axes.Y,
RelativeSizeAxes = Axes.X,
Direction = FillDirection.Vertical,
Children = new Drawable[]
{
difficulties = new DifficultiesContainer
{
AutoSizeAxes = Axes.Both,
Margin = new MarginPadding { Left = -(tile_icon_padding + tile_spacing / 2) },
OnLostHover = () =>
{
showBeatmap(Beatmap.Value);
starRating.FadeOut(100);
},
},
new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Margin = new MarginPadding { Top = 10 },
Spacing = new Vector2(5f),
Children = new[]
{
version = new OsuSpriteText
{
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
TextSize = 20,
Font = @"Exo2.0-Bold",
},
starRating = new OsuSpriteText
{
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
TextSize = 13,
Font = @"Exo2.0-Bold",
Text = "Star Difficulty",
Alpha = 0,
Margin = new MarginPadding { Bottom = 1 },
},
},
},
new FillFlowContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Spacing = new Vector2(10f),
Margin = new MarginPadding { Top = 5 },
Children = new[]
{
plays = new Statistic(FontAwesome.fa_play_circle),
favourites = new Statistic(FontAwesome.fa_heart),
},
},
},
},
};
Beatmap.ValueChanged += b =>
{
showBeatmap(b);
updateDifficultyButtons();
};
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
starRating.Colour = colours.Yellow;
}
protected override void LoadComplete()
{
base.LoadComplete();
// done here so everything can bind in intialization and get the first trigger
Beatmap.TriggerChange();
}
private void showBeatmap(BeatmapInfo beatmap) => version.Text = beatmap.Version;
private void updateDifficultyButtons()
{
difficulties.Children.ToList().ForEach(diff => diff.State = diff.Beatmap == Beatmap.Value ? DifficultySelectorState.Selected : DifficultySelectorState.NotSelected);
}
private class DifficultiesContainer : FillFlowContainer<DifficultySelectorButton>
{
public Action OnLostHover;
protected override void OnHoverLost(InputState state)
{
base.OnHoverLost(state);
OnLostHover?.Invoke();
}
}
private class DifficultySelectorButton : OsuClickableContainer, IStateful<DifficultySelectorState>
{
private const float transition_duration = 100;
private const float size = 52;
private readonly Container bg;
private readonly DifficultyIcon icon;
public readonly BeatmapInfo Beatmap;
public Action<BeatmapInfo> OnHovered;
public Action<BeatmapInfo> OnClicked;
public event Action<DifficultySelectorState> StateChanged;
private DifficultySelectorState state;
public DifficultySelectorState State
{
get { return state; }
set
{
if (value == state) return;
state = value;
StateChanged?.Invoke(State);
if (value == DifficultySelectorState.Selected)
fadeIn();
else
fadeOut();
}
}
public DifficultySelectorButton(BeatmapInfo beatmap)
{
Beatmap = beatmap;
Size = new Vector2(size);
Margin = new MarginPadding { Horizontal = tile_spacing / 2 };
Children = new Drawable[]
{
bg = new Container
{
RelativeSizeAxes = Axes.Both,
Masking = true,
CornerRadius = 4,
Child = new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Color4.Black.Opacity(0.5f),
},
},
icon = new DifficultyIcon(beatmap)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = new Vector2(size - tile_icon_padding * 2),
Margin = new MarginPadding { Bottom = 1 },
},
};
}
protected override bool OnHover(InputState state)
{
fadeIn();
OnHovered?.Invoke(Beatmap);
return base.OnHover(state);
}
protected override void OnHoverLost(InputState state)
{
if (State == DifficultySelectorState.NotSelected)
fadeOut();
base.OnHoverLost(state);
}
protected override bool OnClick(InputState state)
{
OnClicked?.Invoke(Beatmap);
return base.OnClick(state);
}
private void fadeIn()
{
bg.FadeIn(transition_duration);
icon.FadeIn(transition_duration);
}
private void fadeOut()
{
bg.FadeOut();
icon.FadeTo(0.7f, transition_duration);
}
}
private class Statistic : FillFlowContainer
{
private readonly OsuSpriteText text;
private int value;
public int Value
{
get { return value; }
set
{
this.value = value;
text.Text = Value.ToString(@"N0");
}
}
public Statistic(FontAwesome icon)
{
AutoSizeAxes = Axes.Both;
Direction = FillDirection.Horizontal;
Spacing = new Vector2(2f);
Children = new Drawable[]
{
new SpriteIcon
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
Icon = icon,
Shadow = true,
Size = new Vector2(13),
},
text = new OsuSpriteText
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
Font = @"Exo2.0-SemiBoldItalic",
TextSize = 14,
},
};
}
}
private enum DifficultySelectorState
{
Selected,
NotSelected,
}
}
}

View File

@ -0,0 +1,118 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Game.Beatmaps;
using osu.Game.Screens.Select.Details;
using OpenTK;
using OpenTK.Graphics;
namespace osu.Game.Overlays.BeatmapSet
{
public class Details : FillFlowContainer
{
private readonly PreviewButton preview;
private readonly BasicStats basic;
private readonly AdvancedStats advanced;
private readonly UserRatings ratings;
private BeatmapSetInfo beatmapSet;
public BeatmapSetInfo BeatmapSet
{
get { return beatmapSet; }
set
{
if (value == beatmapSet) return;
beatmapSet = value;
basic.BeatmapSet = preview.BeatmapSet = BeatmapSet;
}
}
private BeatmapInfo beatmap;
public BeatmapInfo Beatmap
{
get { return beatmap; }
set
{
if (value == beatmap) return;
beatmap = value;
basic.Beatmap = advanced.Beatmap = Beatmap;
ratings.Metrics = Beatmap.Metrics;
}
}
public Details()
{
Width = BeatmapSetOverlay.RIGHT_WIDTH;
AutoSizeAxes = Axes.Y;
Spacing = new Vector2(1f);
Children = new Drawable[]
{
preview = new PreviewButton
{
RelativeSizeAxes = Axes.X,
},
new DetailBox
{
Child = basic = new BasicStats
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Margin = new MarginPadding { Vertical = 10 },
},
},
new DetailBox
{
Child = advanced = new AdvancedStats
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Margin = new MarginPadding { Vertical = 7.5f },
},
},
new DetailBox
{
Child = ratings = new UserRatings
{
RelativeSizeAxes = Axes.X,
Height = 95,
Margin = new MarginPadding { Top = 10 },
},
},
};
}
private class DetailBox : Container
{
private readonly Container content;
protected override Container<Drawable> Content => content;
public DetailBox()
{
RelativeSizeAxes = Axes.X;
AutoSizeAxes = Axes.Y;
InternalChildren = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Color4.Black.Opacity(0.5f),
},
content = new Container
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Padding = new MarginPadding { Horizontal = 15 },
},
};
}
}
}
}

View File

@ -0,0 +1,59 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using OpenTK;
namespace osu.Game.Overlays.BeatmapSet
{
public class DownloadButton : HeaderButton
{
public DownloadButton(string title, string subtitle)
{
Width = 120;
RelativeSizeAxes = Axes.Y;
Child = new Container
{
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding { Horizontal = 10 },
Children = new Drawable[]
{
new FillFlowContainer
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Vertical,
Children = new[]
{
new OsuSpriteText
{
Text = title,
TextSize = 13,
Font = @"Exo2.0-Bold",
},
new OsuSpriteText
{
Text = subtitle,
TextSize = 11,
Font = @"Exo2.0-Bold",
},
},
},
new SpriteIcon
{
Anchor = Anchor.CentreRight,
Origin = Anchor.CentreRight,
Icon = FontAwesome.fa_download,
Size = new Vector2(16),
Margin = new MarginPadding { Right = 5 },
},
},
};
}
}
}

View File

@ -0,0 +1,80 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.Configuration;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Game.Graphics;
using osu.Game.Graphics.Backgrounds;
using OpenTK;
namespace osu.Game.Overlays.BeatmapSet
{
public class FavouriteButton : HeaderButton
{
public readonly Bindable<bool> Favourited = new Bindable<bool>();
public FavouriteButton()
{
RelativeSizeAxes = Axes.Y;
Container pink;
SpriteIcon icon;
Children = new Drawable[]
{
pink = new Container
{
RelativeSizeAxes = Axes.Both,
Alpha = 0f,
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = OsuColour.FromHex(@"9f015f"),
},
new Triangles
{
RelativeSizeAxes = Axes.Both,
ColourLight = OsuColour.FromHex(@"cb2187"),
ColourDark = OsuColour.FromHex(@"9f015f"),
TriangleScale = 1.5f,
},
},
},
icon = new SpriteIcon
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Icon = FontAwesome.fa_heart_o,
Size = new Vector2(18),
Shadow = false,
},
};
Favourited.ValueChanged += value =>
{
if (value)
{
pink.FadeIn(200);
icon.Icon = FontAwesome.fa_heart;
}
else
{
pink.FadeOut(200);
icon.Icon = FontAwesome.fa_heart_o;
}
};
Action = () => Favourited.Value = !Favourited.Value;
}
protected override void UpdateAfterChildren()
{
base.UpdateAfterChildren();
Width = DrawHeight;
}
}
}

View File

@ -0,0 +1,228 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.Allocation;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Colour;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Drawables;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using OpenTK;
using OpenTK.Graphics;
namespace osu.Game.Overlays.BeatmapSet
{
public class Header : Container
{
private const float transition_duration = 250;
private const float tabs_height = 50;
private const float buttons_height = 45;
private const float buttons_spacing = 5;
private readonly Box tabsBg;
private readonly Container coverContainer;
private readonly OsuSpriteText title, artist;
private readonly AuthorInfo author;
private readonly Details details;
private DelayedLoadWrapper cover;
public readonly BeatmapPicker Picker;
private BeatmapSetInfo beatmapSet;
public BeatmapSetInfo BeatmapSet
{
get { return beatmapSet; }
set
{
if (value == beatmapSet) return;
beatmapSet = value;
Picker.BeatmapSet = author.BeatmapSet = details.BeatmapSet = BeatmapSet;
title.Text = BeatmapSet.Metadata.Title;
artist.Text = BeatmapSet.Metadata.Artist;
cover?.FadeOut(400, Easing.Out);
coverContainer.Add(cover = new DelayedLoadWrapper(new BeatmapSetCover(BeatmapSet)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both,
FillMode = FillMode.Fill,
OnLoadComplete = d =>
{
d.FadeInFromZero(400, Easing.Out);
},
})
{
RelativeSizeAxes = Axes.Both,
TimeBeforeLoad = 300
});
}
}
public Header()
{
RelativeSizeAxes = Axes.X;
Height = 400;
Masking = true;
EdgeEffect = new EdgeEffectParameters
{
Colour = Color4.Black.Opacity(0.25f),
Type = EdgeEffectType.Shadow,
Radius = 3,
Offset = new Vector2(0f, 1f),
};
Container noVideoButtons;
FillFlowContainer videoButtons;
Children = new Drawable[]
{
new Container
{
RelativeSizeAxes = Axes.X,
Height = tabs_height,
Children = new[]
{
tabsBg = new Box
{
RelativeSizeAxes = Axes.Both,
},
},
},
new Container
{
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding { Top = tabs_height },
Children = new Drawable[]
{
new Container
{
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Color4.Black,
},
coverContainer = new Container
{
RelativeSizeAxes = Axes.Both,
},
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = ColourInfo.GradientVertical(Color4.Black.Opacity(0.3f), Color4.Black.Opacity(0.8f)),
},
},
},
new Container
{
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding { Top = 20, Bottom = 30, Horizontal = BeatmapSetOverlay.X_PADDING },
Child = new FillFlowContainer
{
RelativeSizeAxes = Axes.Both,
Direction = FillDirection.Vertical,
Children = new Drawable[]
{
new Container
{
RelativeSizeAxes = Axes.X,
Height = 113,
Child = Picker = new BeatmapPicker(),
},
title = new OsuSpriteText
{
Font = @"Exo2.0-BoldItalic",
TextSize = 37,
},
artist = new OsuSpriteText
{
Font = @"Exo2.0-SemiBoldItalic",
TextSize = 25,
},
new Container
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Margin = new MarginPadding { Top = 20 },
Child = author = new AuthorInfo(),
},
new Container
{
RelativeSizeAxes = Axes.X,
Height = buttons_height,
Margin = new MarginPadding { Top = 10 },
Children = new Drawable[]
{
new FavouriteButton(),
new Container
{
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding { Left = buttons_height + buttons_spacing },
Children = new Drawable[]
{
noVideoButtons = new Container
{
RelativeSizeAxes = Axes.Both,
Alpha = 0f,
Child = new DownloadButton("Download", @""),
},
videoButtons = new FillFlowContainer
{
RelativeSizeAxes = Axes.Both,
Spacing = new Vector2(buttons_spacing),
Alpha = 0f,
Children = new[]
{
new DownloadButton("Download", "with Video"),
new DownloadButton("Download", "without Video"),
},
},
},
},
},
},
},
},
},
details = new Details
{
Anchor = Anchor.BottomRight,
Origin = Anchor.BottomRight,
Margin = new MarginPadding { Right = BeatmapSetOverlay.X_PADDING },
},
},
},
};
Picker.Beatmap.ValueChanged += b =>
{
details.Beatmap = b;
if (b.OnlineInfo.HasVideo)
{
noVideoButtons.FadeOut(transition_duration);
videoButtons.FadeIn(transition_duration);
}
else
{
noVideoButtons.FadeIn(transition_duration);
videoButtons.FadeOut(transition_duration);
}
};
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
tabsBg.Colour = colours.Gray3;
}
}
}

View File

@ -0,0 +1,45 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Game.Graphics;
using osu.Game.Graphics.Backgrounds;
using osu.Game.Graphics.Containers;
namespace osu.Game.Overlays.BeatmapSet
{
public class HeaderButton : OsuClickableContainer
{
private readonly Container content;
protected override Container<Drawable> Content => content;
public HeaderButton()
{
CornerRadius = 3;
Masking = true;
InternalChildren = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = OsuColour.FromHex(@"094c5f"),
},
new Triangles
{
RelativeSizeAxes = Axes.Both,
ColourLight = OsuColour.FromHex(@"0f7c9b"),
ColourDark = OsuColour.FromHex(@"094c5f"),
TriangleScale = 1.5f,
},
content = new Container
{
RelativeSizeAxes = Axes.Both,
},
};
}
}
}

View File

@ -0,0 +1,196 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.Allocation;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Game.Beatmaps;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using OpenTK;
using OpenTK.Graphics;
namespace osu.Game.Overlays.BeatmapSet
{
public class Info : Container
{
private const float transition_duration = 250;
private const float metadata_width = 225;
private const float spacing = 20;
private readonly MetadataSection description, source, tags;
private readonly Box successRateBackground;
private readonly SuccessRate successRate;
private BeatmapSetInfo beatmapSet;
public BeatmapSetInfo BeatmapSet
{
get { return beatmapSet; }
set
{
if (value == beatmapSet) return;
beatmapSet = value;
source.Text = BeatmapSet.Metadata.Source;
tags.Text = BeatmapSet.Metadata.Tags;
}
}
public BeatmapInfo Beatmap
{
get { return successRate.Beatmap; }
set { successRate.Beatmap = value; }
}
public Info()
{
RelativeSizeAxes = Axes.X;
Height = 220;
Masking = true;
EdgeEffect = new EdgeEffectParameters
{
Colour = Color4.Black.Opacity(0.25f),
Type = EdgeEffectType.Shadow,
Radius = 3,
Offset = new Vector2(0f, 1f),
};
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Color4.White,
},
new Container
{
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding { Top = 15, Horizontal = BeatmapSetOverlay.X_PADDING },
Children = new Drawable[]
{
new Container
{
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding { Right = metadata_width + BeatmapSetOverlay.RIGHT_WIDTH + spacing * 2 },
Child = new ScrollContainer
{
RelativeSizeAxes = Axes.Both,
ScrollbarVisible = false,
Child = description = new MetadataSection("Description"),
},
},
new ScrollContainer
{
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
RelativeSizeAxes = Axes.Y,
Width = metadata_width,
ScrollbarVisible = false,
Padding = new MarginPadding { Horizontal = 10 },
Margin = new MarginPadding { Right = BeatmapSetOverlay.RIGHT_WIDTH + spacing },
Child = new FillFlowContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Direction = FillDirection.Vertical,
LayoutDuration = transition_duration,
Children = new[]
{
source = new MetadataSection("Source"),
tags = new MetadataSection("Tags"),
},
},
},
new Container
{
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
RelativeSizeAxes = Axes.Y,
Width = BeatmapSetOverlay.RIGHT_WIDTH,
Children = new Drawable[]
{
successRateBackground = new Box
{
RelativeSizeAxes = Axes.Both,
},
successRate = new SuccessRate
{
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding { Top = 20, Horizontal = 15 },
},
},
},
},
},
};
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
successRateBackground.Colour = colours.GrayE;
source.TextColour = description.TextColour = colours.Gray5;
tags.TextColour = colours.BlueDark;
}
private class MetadataSection : FillFlowContainer
{
private readonly OsuSpriteText header;
private readonly TextFlowContainer textFlow;
public string Text
{
set
{
if (string.IsNullOrEmpty(value))
{
this.FadeOut(transition_duration);
return;
}
this.FadeIn(transition_duration);
textFlow.Clear();
textFlow.AddText(value, s => s.TextSize = 14);
}
}
public Color4 TextColour
{
get { return textFlow.Colour; }
set { textFlow.Colour = value; }
}
public MetadataSection(string title)
{
RelativeSizeAxes = Axes.X;
AutoSizeAxes = Axes.Y;
Spacing = new Vector2(5f);
InternalChildren = new Drawable[]
{
header = new OsuSpriteText
{
Text = title,
Font = @"Exo2.0-Bold",
TextSize = 14,
Shadow = false,
Margin = new MarginPadding { Top = 20 },
},
textFlow = new TextFlowContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
},
};
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
header.Colour = colours.Gray5;
}
}
}
}

View File

@ -0,0 +1,107 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.Allocation;
using osu.Framework.Audio.Track;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Input;
using osu.Game.Beatmaps;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using OpenTK;
using OpenTK.Graphics;
using osu.Game.Overlays.Direct;
using osu.Framework.Configuration;
namespace osu.Game.Overlays.BeatmapSet
{
public class PreviewButton : OsuClickableContainer
{
private const float transition_duration = 500;
private readonly Box bg, progress;
private readonly PlayButton playButton;
private Track preview => playButton.Preview;
private Bindable<bool> playing => playButton.Playing;
public BeatmapSetInfo BeatmapSet
{
get { return playButton.BeatmapSet; }
set { playButton.BeatmapSet = value; }
}
public PreviewButton()
{
Height = 42;
Children = new Drawable[]
{
bg = new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Color4.Black.Opacity(0.25f),
},
new Container
{
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
RelativeSizeAxes = Axes.X,
Height = 3,
Child = progress = new Box
{
RelativeSizeAxes = Axes.Both,
Width = 0f,
Alpha = 0f,
},
},
playButton = new PlayButton
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = new Vector2(18),
},
};
Action = () => playing.Value = !playing.Value;
playing.ValueChanged += newValue => progress.FadeTo(newValue ? 1 : 0, 100);
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
progress.Colour = colours.Yellow;
}
protected override void Update()
{
base.Update();
if (playing.Value && preview != null)
{
progress.Width = (float)(preview.CurrentTime / preview.Length);
}
}
protected override void Dispose(bool isDisposing)
{
playing.Value = false;
base.Dispose(isDisposing);
}
protected override bool OnHover(InputState state)
{
bg.FadeColour(Color4.Black.Opacity(0.5f), 100);
return base.OnHover(state);
}
protected override void OnHoverLost(InputState state)
{
bg.FadeColour(Color4.Black.Opacity(0.25f), 100);
base.OnHoverLost(state);
}
}
}

View File

@ -0,0 +1,113 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Beatmaps;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
using osu.Game.Screens.Select.Details;
namespace osu.Game.Overlays.BeatmapSet
{
public class SuccessRate : Container
{
private readonly FillFlowContainer header;
private readonly OsuSpriteText successRateLabel, successPercent, graphLabel;
private readonly Bar successRate;
private readonly Container percentContainer;
private readonly FailRetryGraph graph;
private BeatmapInfo beatmap;
public BeatmapInfo Beatmap
{
get { return beatmap; }
set
{
if (value == beatmap) return;
beatmap = value;
var rate = (float)beatmap.OnlineInfo.PassCount / beatmap.OnlineInfo.PlayCount;
successPercent.Text = $"{Math.Round(rate * 100)}%";
successRate.Length = rate;
percentContainer.ResizeWidthTo(successRate.Length, 250, Easing.InOutCubic);
graph.Metrics = Beatmap.Metrics;
}
}
public SuccessRate()
{
Children = new Drawable[]
{
header = new FillFlowContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Direction = FillDirection.Vertical,
Children = new Drawable[]
{
successRateLabel = new OsuSpriteText
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
Text = "Success Rate",
TextSize = 13,
},
successRate = new Bar
{
RelativeSizeAxes = Axes.X,
Height = 5,
Margin = new MarginPadding { Top = 5 },
},
percentContainer = new Container
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Width = 0f,
Child = successPercent = new OsuSpriteText
{
Anchor = Anchor.TopRight,
Origin = Anchor.TopCentre,
Text = @"0%",
TextSize = 13,
},
},
graphLabel = new OsuSpriteText
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
Text = "Points of Failure",
TextSize = 13,
Margin = new MarginPadding { Vertical = 20 },
},
},
},
graph = new FailRetryGraph
{
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
RelativeSizeAxes = Axes.Both,
},
};
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
successRateLabel.Colour = successPercent.Colour = graphLabel.Colour = colours.Gray5;
successRate.AccentColour = colours.Green;
successRate.BackgroundColour = colours.GrayD;
}
protected override void UpdateAfterChildren()
{
base.UpdateAfterChildren();
graph.Padding = new MarginPadding { Top = header.DrawHeight };
}
}
}

View File

@ -0,0 +1,102 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using OpenTK;
using OpenTK.Graphics;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Input;
using osu.Game.Beatmaps;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Overlays.BeatmapSet;
namespace osu.Game.Overlays
{
public class BeatmapSetOverlay : WaveOverlayContainer
{
public const float X_PADDING = 40;
public const float RIGHT_WIDTH = 275;
private readonly Header header;
private readonly Info info;
// receive input outside our bounds so we can trigger a close event on ourselves.
public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => true;
public BeatmapSetOverlay()
{
FirstWaveColour = OsuColour.Gray(0.4f);
SecondWaveColour = OsuColour.Gray(0.3f);
ThirdWaveColour = OsuColour.Gray(0.2f);
FourthWaveColour = OsuColour.Gray(0.1f);
Anchor = Anchor.TopCentre;
Origin = Anchor.TopCentre;
RelativeSizeAxes = Axes.Both;
Width = 0.85f;
Masking = true;
EdgeEffect = new EdgeEffectParameters
{
Colour = Color4.Black.Opacity(0),
Type = EdgeEffectType.Shadow,
Radius = 3,
Offset = new Vector2(0f, 1f),
};
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = OsuColour.Gray(0.2f)
},
new ScrollContainer
{
RelativeSizeAxes = Axes.Both,
ScrollbarVisible = false,
Child = new ReverseChildIDFillFlowContainer<Drawable>
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Direction = FillDirection.Vertical,
Children = new Drawable[]
{
header = new Header(),
info = new Info(),
},
},
},
};
header.Picker.Beatmap.ValueChanged += b => info.Beatmap = b;
}
protected override void PopIn()
{
base.PopIn();
FadeEdgeEffectTo(0.25f, APPEAR_DURATION, Easing.In);
}
protected override void PopOut()
{
base.PopOut();
FadeEdgeEffectTo(0, DISAPPEAR_DURATION, Easing.Out);
}
protected override bool OnClick(InputState state)
{
State = Visibility.Hidden;
return true;
}
public void ShowBeatmapSet(BeatmapSetInfo set)
{
header.BeatmapSet = info.BeatmapSet = set;
Show();
}
}
}

View File

@ -63,6 +63,7 @@ namespace osu.Game.Overlays.Chat
private const float padding = 15; private const float padding = 15;
private const float message_padding = 200; private const float message_padding = 200;
private const float action_padding = 3;
private const float text_size = 20; private const float text_size = 20;
private Color4 customUsernameColour; private Color4 customUsernameColour;
@ -194,6 +195,8 @@ namespace osu.Game.Overlays.Chat
} }
} }
}; };
if (message.IsAction && senderHasBackground)
contentFlow.Colour = OsuColour.FromHex(message.Sender.Colour);
updateMessageContent(); updateMessageContent();
FinishTransforms(true); FinishTransforms(true);
@ -206,7 +209,17 @@ namespace osu.Game.Overlays.Chat
timestamp.Text = $@"{message.Timestamp.LocalDateTime:HH:mm:ss}"; timestamp.Text = $@"{message.Timestamp.LocalDateTime:HH:mm:ss}";
username.Text = $@"{message.Sender.Username}" + (senderHasBackground ? "" : ":"); username.Text = $@"{message.Sender.Username}" + (senderHasBackground ? "" : ":");
contentFlow.Text = message.Content;
if (message.IsAction)
{
contentFlow.Clear();
contentFlow.AddText("[", sprite => sprite.Padding = new MarginPadding { Right = action_padding });
contentFlow.AddText(message.Content, sprite => sprite.Font = @"Exo2.0-MediumItalic");
contentFlow.AddText("]", sprite => sprite.Padding = new MarginPadding { Left = action_padding });
}
else
contentFlow.Text = message.Content;
} }
private class MessageSender : ClickableContainer, IHasContextMenu private class MessageSender : ClickableContainer, IHasContextMenu

View File

@ -465,7 +465,7 @@ namespace osu.Game.Overlays
textbox.Text = string.Empty; textbox.Text = string.Empty;
if (string.IsNullOrEmpty(postText)) if (string.IsNullOrWhiteSpace(postText))
return; return;
var target = currentChannel; var target = currentChannel;
@ -478,11 +478,36 @@ namespace osu.Game.Overlays
return; return;
} }
bool isAction = false;
if (postText[0] == '/') if (postText[0] == '/')
{ {
// TODO: handle commands string[] parameters = postText.Substring(1).Split(new[] { ' ' }, 2);
target.AddNewMessages(new ErrorMessage("Chat commands are not supported yet!")); string command = parameters[0];
return; string content = parameters.Length == 2 ? parameters[1] : string.Empty;
switch (command)
{
case "me":
if (string.IsNullOrWhiteSpace(content))
{
currentChannel.AddNewMessages(new ErrorMessage("Usage: /me [action]"));
return;
}
isAction = true;
postText = content;
break;
case "help":
currentChannel.AddNewMessages(new InfoMessage("Supported commands: /help, /me [action]"));
return;
default:
currentChannel.AddNewMessages(new ErrorMessage($@"""/{command}"" is not supported! For a list of supported commands see /help"));
return;
}
} }
var message = new LocalEchoMessage var message = new LocalEchoMessage
@ -491,6 +516,7 @@ namespace osu.Game.Overlays
Timestamp = DateTimeOffset.Now, Timestamp = DateTimeOffset.Now,
TargetType = TargetType.Channel, //TODO: read this from channel TargetType = TargetType.Channel, //TODO: read this from channel
TargetId = target.Id, TargetId = target.Id,
IsAction = isAction,
Content = postText Content = postText
}; };

View File

@ -12,7 +12,6 @@ using osu.Game.Graphics;
using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Sprites;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Framework.Input;
namespace osu.Game.Overlays.Direct namespace osu.Game.Overlays.Direct
{ {
@ -22,6 +21,11 @@ namespace osu.Game.Overlays.Direct
private const float vertical_padding = 5; private const float vertical_padding = 5;
private FillFlowContainer bottomPanel; private FillFlowContainer bottomPanel;
private PlayButton playButton;
private Box progressBar;
protected override PlayButton PlayButton => playButton;
protected override Box PreviewBar => progressBar;
public DirectGridPanel(BeatmapSetInfo beatmap) : base(beatmap) public DirectGridPanel(BeatmapSetInfo beatmap) : base(beatmap)
{ {
@ -88,6 +92,15 @@ namespace osu.Game.Overlays.Direct
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
}, },
progressBar = new Box
{
Origin = Anchor.BottomLeft,
RelativeSizeAxes = Axes.X,
BypassAutoSizeAxes = Axes.Both,
Size = new Vector2(0, 3),
Alpha = 0,
Colour = colours.Yellow,
},
new FillFlowContainer new FillFlowContainer
{ {
RelativeSizeAxes = Axes.X, RelativeSizeAxes = Axes.X,
@ -150,6 +163,15 @@ namespace osu.Game.Overlays.Direct
}, },
}, },
}, },
new DownloadButton
{
Size = new Vector2(30),
Margin = new MarginPadding(horizontal_padding),
Anchor = Anchor.CentreRight,
Origin = Anchor.CentreRight,
Colour = colours.Gray5,
Action = StartDownload
},
}, },
}, },
}, },
@ -170,13 +192,13 @@ namespace osu.Game.Overlays.Direct
new Statistic(FontAwesome.fa_heart, SetInfo.OnlineInfo?.FavouriteCount ?? 0), new Statistic(FontAwesome.fa_heart, SetInfo.OnlineInfo?.FavouriteCount ?? 0),
}, },
}, },
playButton = new PlayButton(SetInfo)
{
Margin = new MarginPadding { Top = 5, Left = 10 },
Size = new Vector2(30),
Alpha = 0,
},
}); });
} }
protected override bool OnClick(InputState state)
{
StartDownload();
return true;
}
} }
} }

View File

@ -11,10 +11,8 @@ using osu.Game.Graphics;
using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Sprites;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Localisation; using osu.Framework.Localisation;
using osu.Framework.Input;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Graphics.Containers;
namespace osu.Game.Overlays.Direct namespace osu.Game.Overlays.Direct
{ {
@ -30,8 +28,14 @@ namespace osu.Game.Overlays.Direct
Height = height; Height = height;
} }
private PlayButton playButton;
private Box progressBar;
protected override PlayButton PlayButton => playButton;
protected override Box PreviewBar => progressBar;
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(LocalisationEngine localisation) private void load(LocalisationEngine localisation, OsuColour colours)
{ {
Content.CornerRadius = 5; Content.CornerRadius = 5;
@ -50,29 +54,50 @@ namespace osu.Game.Overlays.Direct
{ {
new FillFlowContainer new FillFlowContainer
{ {
Origin = Anchor.CentreLeft,
Anchor = Anchor.CentreLeft,
AutoSizeAxes = Axes.Both, AutoSizeAxes = Axes.Both,
Direction = FillDirection.Vertical, Direction = FillDirection.Horizontal,
LayoutEasing = Easing.OutQuint,
LayoutDuration = 120,
Spacing = new Vector2(10, 0),
Children = new Drawable[] Children = new Drawable[]
{ {
new OsuSpriteText playButton = new PlayButton(SetInfo)
{ {
Current = localisation.GetUnicodePreference(SetInfo.Metadata.TitleUnicode, SetInfo.Metadata.Title), Origin = Anchor.CentreLeft,
TextSize = 18, Anchor = Anchor.CentreLeft,
Font = @"Exo2.0-BoldItalic", Size = new Vector2(height / 2),
}, FillMode = FillMode.Fit,
new OsuSpriteText Alpha = 0,
{
Current = localisation.GetUnicodePreference(SetInfo.Metadata.ArtistUnicode, SetInfo.Metadata.Artist),
Font = @"Exo2.0-BoldItalic",
}, },
new FillFlowContainer new FillFlowContainer
{ {
AutoSizeAxes = Axes.X, AutoSizeAxes = Axes.Both,
Height = 20, Direction = FillDirection.Vertical,
Margin = new MarginPadding { Top = vertical_padding, Bottom = vertical_padding }, Children = new Drawable[]
Children = GetDifficultyIcons(), {
new OsuSpriteText
{
Current = localisation.GetUnicodePreference(SetInfo.Metadata.TitleUnicode, SetInfo.Metadata.Title),
TextSize = 18,
Font = @"Exo2.0-BoldItalic",
},
new OsuSpriteText
{
Current = localisation.GetUnicodePreference(SetInfo.Metadata.ArtistUnicode, SetInfo.Metadata.Artist),
Font = @"Exo2.0-BoldItalic",
},
new FillFlowContainer
{
AutoSizeAxes = Axes.X,
Height = 20,
Margin = new MarginPadding { Top = vertical_padding, Bottom = vertical_padding },
Children = GetDifficultyIcons(),
},
},
}, },
}, }
}, },
new FillFlowContainer new FillFlowContainer
{ {
@ -128,49 +153,17 @@ namespace osu.Game.Overlays.Direct
}, },
}, },
}, },
}); progressBar = new Box
}
private class DownloadButton : OsuClickableContainer
{
private readonly SpriteIcon icon;
public DownloadButton()
{
Children = new Drawable[]
{ {
icon = new SpriteIcon Anchor = Anchor.BottomLeft,
{ Origin = Anchor.BottomLeft,
Anchor = Anchor.Centre, RelativeSizeAxes = Axes.X,
Origin = Anchor.Centre, BypassAutoSizeAxes = Axes.Y,
Size = new Vector2(30), Size = new Vector2(0, 3),
Icon = FontAwesome.fa_osu_chevron_down_o, Alpha = 0,
}, Colour = colours.Yellow,
}; },
} });
protected override bool OnMouseDown(InputState state, MouseDownEventArgs args)
{
icon.ScaleTo(0.9f, 1000, Easing.Out);
return base.OnMouseDown(state, args);
}
protected override bool OnMouseUp(InputState state, MouseUpEventArgs args)
{
icon.ScaleTo(1f, 500, Easing.OutElastic);
return base.OnMouseUp(state, args);
}
protected override bool OnHover(InputState state)
{
icon.ScaleTo(1.1f, 500, Easing.OutElastic);
return base.OnHover(state);
}
protected override void OnHoverLost(InputState state)
{
icon.ScaleTo(1f, 500, Easing.OutElastic);
}
} }
} }
} }

View File

@ -20,6 +20,8 @@ using osu.Game.Online.API;
using osu.Framework.Logging; using osu.Framework.Logging;
using osu.Game.Overlays.Notifications; using osu.Game.Overlays.Notifications;
using osu.Game.Online.API.Requests; using osu.Game.Online.API.Requests;
using osu.Framework.Configuration;
using osu.Framework.Audio.Track;
namespace osu.Game.Overlays.Direct namespace osu.Game.Overlays.Direct
{ {
@ -37,6 +39,12 @@ namespace osu.Game.Overlays.Direct
private ProgressBar progressBar; private ProgressBar progressBar;
private BeatmapManager beatmaps; private BeatmapManager beatmaps;
private NotificationOverlay notifications; private NotificationOverlay notifications;
private BeatmapSetOverlay beatmapSetOverlay;
public Track Preview => PlayButton.Preview;
public Bindable<bool> PreviewPlaying => PlayButton.Playing;
protected abstract PlayButton PlayButton { get; }
protected abstract Box PreviewBar { get; }
protected override Container<Drawable> Content => content; protected override Container<Drawable> Content => content;
@ -63,11 +71,12 @@ namespace osu.Game.Overlays.Direct
[BackgroundDependencyLoader(permitNulls: true)] [BackgroundDependencyLoader(permitNulls: true)]
private void load(APIAccess api, BeatmapManager beatmaps, OsuColour colours, NotificationOverlay notifications) private void load(APIAccess api, BeatmapManager beatmaps, OsuColour colours, NotificationOverlay notifications, BeatmapSetOverlay beatmapSetOverlay)
{ {
this.api = api; this.api = api;
this.beatmaps = beatmaps; this.beatmaps = beatmaps;
this.notifications = notifications; this.notifications = notifications;
this.beatmapSetOverlay = beatmapSetOverlay;
AddInternal(content = new Container AddInternal(content = new Container
{ {
@ -102,10 +111,21 @@ namespace osu.Game.Overlays.Direct
attachDownload(downloadRequest); attachDownload(downloadRequest);
} }
protected override void Update()
{
base.Update();
if (PreviewPlaying && Preview != null)
{
PreviewBar.Width = (float)(Preview.CurrentTime / Preview.Length);
}
}
protected override bool OnHover(InputState state) protected override bool OnHover(InputState state)
{ {
content.TweenEdgeEffectTo(edgeEffectHovered, hover_transition_time, Easing.OutQuint); content.TweenEdgeEffectTo(edgeEffectHovered, hover_transition_time, Easing.OutQuint);
content.MoveToY(-4, hover_transition_time, Easing.OutQuint); content.MoveToY(-4, hover_transition_time, Easing.OutQuint);
PlayButton.FadeIn(120, Easing.InOutQuint);
return base.OnHover(state); return base.OnHover(state);
} }
@ -114,10 +134,21 @@ namespace osu.Game.Overlays.Direct
{ {
content.TweenEdgeEffectTo(edgeEffectNormal, hover_transition_time, Easing.OutQuint); content.TweenEdgeEffectTo(edgeEffectNormal, hover_transition_time, Easing.OutQuint);
content.MoveToY(0, hover_transition_time, Easing.OutQuint); content.MoveToY(0, hover_transition_time, Easing.OutQuint);
if (!PreviewPlaying)
PlayButton.FadeOut(120, Easing.InOutQuint);
base.OnHoverLost(state); base.OnHoverLost(state);
} }
protected override bool OnClick(InputState state)
{
ShowInformation();
PreviewPlaying.Value = false;
return true;
}
protected void ShowInformation() => beatmapSetOverlay?.ShowBeatmapSet(SetInfo);
protected void StartDownload() protected void StartDownload()
{ {
if (!api.LocalUser.Value.IsSupporter) if (!api.LocalUser.Value.IsSupporter)
@ -173,6 +204,9 @@ namespace osu.Game.Overlays.Direct
{ {
base.LoadComplete(); base.LoadComplete();
this.FadeInFromZero(200, Easing.Out); this.FadeInFromZero(200, Easing.Out);
PreviewPlaying.ValueChanged += newValue => PlayButton.FadeTo(newValue || IsHovered ? 1 : 0, 120, Easing.InOutQuint);
PreviewPlaying.ValueChanged += newValue => PreviewBar.FadeTo(newValue ? 1 : 0, 120, Easing.InOutQuint);
} }
protected List<DifficultyIcon> GetDifficultyIcons() protected List<DifficultyIcon> GetDifficultyIcons()

View File

@ -0,0 +1,53 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.Graphics;
using osu.Framework.Input;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using OpenTK;
namespace osu.Game.Overlays.Direct
{
public class DownloadButton : OsuClickableContainer
{
private readonly SpriteIcon icon;
public DownloadButton()
{
Children = new Drawable[]
{
icon = new SpriteIcon
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = new Vector2(30),
Icon = FontAwesome.fa_osu_chevron_down_o,
},
};
}
protected override bool OnMouseDown(InputState state, MouseDownEventArgs args)
{
icon.ScaleTo(0.9f, 1000, Easing.Out);
return base.OnMouseDown(state, args);
}
protected override bool OnMouseUp(InputState state, MouseUpEventArgs args)
{
icon.ScaleTo(1f, 500, Easing.OutElastic);
return base.OnMouseUp(state, args);
}
protected override bool OnHover(InputState state)
{
icon.ScaleTo(1.1f, 500, Easing.OutElastic);
return base.OnHover(state);
}
protected override void OnHoverLost(InputState state)
{
icon.ScaleTo(1f, 500, Easing.OutElastic);
}
}
}

View File

@ -21,7 +21,7 @@ namespace osu.Game.Overlays.Direct
public Header() public Header()
{ {
Tabs.Current.Value = DirectTab.Search; Tabs.Current.Value = DirectTab.NewestMaps;
Tabs.Current.TriggerChange(); Tabs.Current.TriggerChange();
} }
} }

View File

@ -0,0 +1,181 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using OpenTK.Graphics;
using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Audio.Track;
using osu.Framework.Configuration;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Input;
using osu.Game.Beatmaps;
using osu.Game.Graphics;
using osu.Game.Graphics.UserInterface;
namespace osu.Game.Overlays.Direct
{
public class PlayButton : Container
{
public readonly Bindable<bool> Playing = new Bindable<bool>();
public Track Preview { get; private set; }
private BeatmapSetInfo beatmapSet;
public BeatmapSetInfo BeatmapSet
{
get { return beatmapSet; }
set
{
if (value == beatmapSet) return;
beatmapSet = value;
Playing.Value = false;
trackLoader = null;
Preview = null;
}
}
private Color4 hoverColour;
private readonly SpriteIcon icon;
private readonly LoadingAnimation loadingAnimation;
private const float transition_duration = 500;
private bool loading
{
set
{
if (value)
{
loadingAnimation.Show();
icon.FadeOut(transition_duration * 5, Easing.OutQuint);
}
else
{
loadingAnimation.Hide();
icon.FadeIn(transition_duration, Easing.OutQuint);
}
}
}
public PlayButton(BeatmapSetInfo setInfo = null)
{
BeatmapSet = setInfo;
AddRange(new Drawable[]
{
icon = new SpriteIcon
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
FillMode = FillMode.Fit,
RelativeSizeAxes = Axes.Both,
Icon = FontAwesome.fa_play,
},
loadingAnimation = new LoadingAnimation(),
});
Playing.ValueChanged += playing =>
{
icon.Icon = playing ? FontAwesome.fa_pause : FontAwesome.fa_play;
icon.FadeColour(playing || IsHovered ? hoverColour : Color4.White, 120, Easing.InOutQuint);
updatePreviewTrack(playing);
};
}
[BackgroundDependencyLoader]
private void load(OsuColour colour)
{
hoverColour = colour.Yellow;
}
protected override bool OnClick(InputState state)
{
Playing.Value = !Playing.Value;
return true;
}
protected override bool OnHover(InputState state)
{
icon.FadeColour(hoverColour, 120, Easing.InOutQuint);
return base.OnHover(state);
}
protected override void OnHoverLost(InputState state)
{
if (!Playing.Value)
icon.FadeColour(Color4.White, 120, Easing.InOutQuint);
base.OnHoverLost(state);
}
protected override void Update()
{
base.Update();
if (Preview?.HasCompleted ?? false)
{
Playing.Value = false;
Preview = null;
}
}
private void updatePreviewTrack(bool playing)
{
if (playing)
{
if (Preview == null)
{
beginAudioLoad();
return;
}
Preview.Seek(0);
Preview.Start();
}
else
{
Preview?.Stop();
}
}
private TrackLoader trackLoader;
private void beginAudioLoad()
{
if (trackLoader != null) return;
Add(new AsyncLoadWrapper(trackLoader = new TrackLoader($"https://b.ppy.sh/preview/{BeatmapSet.OnlineBeatmapSetID}.mp3")
{
OnLoadComplete = d =>
{
// we may have been replaced by another loader
if (trackLoader != d) return;
Preview = (d as TrackLoader)?.Preview;
Playing.TriggerChange();
},
}));
}
private class TrackLoader : Drawable
{
private readonly string preview;
public Track Preview;
public TrackLoader(string preview)
{
this.preview = preview;
}
[BackgroundDependencyLoader]
private void load(AudioManager audio)
{
if (!string.IsNullOrEmpty(preview))
{
Preview = audio.Track.Get(preview);
Preview.Volume.Value = 0.5;
}
}
}
}
}

View File

@ -32,6 +32,7 @@ namespace osu.Game.Overlays
private readonly FillFlowContainer resultCountsContainer; private readonly FillFlowContainer resultCountsContainer;
private readonly OsuSpriteText resultCountsText; private readonly OsuSpriteText resultCountsText;
private FillFlowContainer<DirectPanel> panels; private FillFlowContainer<DirectPanel> panels;
private DirectPanel playing;
protected override Color4 BackgroundColour => OsuColour.FromHex(@"485e74"); protected override Color4 BackgroundColour => OsuColour.FromHex(@"485e74");
protected override Color4 TrianglesColourLight => OsuColour.FromHex(@"465b71"); protected override Color4 TrianglesColourLight => OsuColour.FromHex(@"465b71");
@ -201,6 +202,12 @@ namespace osu.Game.Overlays
panels.FadeOut(200); panels.FadeOut(200);
panels.Expire(); panels.Expire();
panels = null; panels = null;
if (playing != null)
{
playing.PreviewPlaying.Value = false;
playing = null;
}
} }
if (BeatmapSets == null) return; if (BeatmapSets == null) return;
@ -227,6 +234,17 @@ namespace osu.Game.Overlays
{ {
if (panels != null) ScrollFlow.Remove(panels); if (panels != null) ScrollFlow.Remove(panels);
ScrollFlow.Add(panels = newPanels); ScrollFlow.Add(panels = newPanels);
foreach (DirectPanel panel in p.Children)
panel.PreviewPlaying.ValueChanged += newValue =>
{
if (newValue)
{
if (playing != null && playing != panel)
playing.PreviewPlaying.Value = false;
playing = panel;
}
};
}); });
} }
@ -251,7 +269,7 @@ namespace osu.Game.Overlays
if (Header.Tabs.Current.Value == DirectTab.Search && (Filter.Search.Text == string.Empty || currentQuery == string.Empty)) return; if (Header.Tabs.Current.Value == DirectTab.Search && (Filter.Search.Text == string.Empty || currentQuery == string.Empty)) return;
getSetsRequest = new GetBeatmapSetsRequest(currentQuery, getSetsRequest = new GetBeatmapSetsRequest(currentQuery.Value ?? string.Empty,
((FilterControl)Filter).Ruleset.Value, ((FilterControl)Filter).Ruleset.Value,
Filter.DisplayStyleControl.Dropdown.Current.Value, Filter.DisplayStyleControl.Dropdown.Current.Value,
Filter.Tabs.Current.Value); //todo: sort direction (?) Filter.Tabs.Current.Value); //todo: sort direction (?)

View File

@ -3,6 +3,7 @@
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Overlays.Settings.Sections.General; using osu.Game.Overlays.Settings.Sections.General;
using OpenTK.Graphics; using OpenTK.Graphics;
@ -28,35 +29,43 @@ namespace osu.Game.Overlays
{ {
Children = new Drawable[] Children = new Drawable[]
{ {
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Color4.Black,
Alpha = 0.6f,
},
new OsuContextMenuContainer new OsuContextMenuContainer
{ {
Width = 360, Width = 360,
AutoSizeAxes = Axes.Y, AutoSizeAxes = Axes.Y,
Masking = true,
AutoSizeDuration = transition_time,
AutoSizeEasing = Easing.OutQuint,
Children = new Drawable[] Children = new Drawable[]
{ {
settingsSection = new LoginSettings
{
Padding = new MarginPadding(10),
RequestHide = Hide,
},
new Box new Box
{ {
RelativeSizeAxes = Axes.X, RelativeSizeAxes = Axes.Both,
Anchor = Anchor.BottomLeft, Colour = Color4.Black,
Origin = Anchor.BottomLeft, Alpha = 0.6f,
Height = 3,
Colour = colours.Yellow,
Alpha = 1,
}, },
new Container
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Masking = true,
AutoSizeDuration = transition_time,
AutoSizeEasing = Easing.OutQuint,
Children = new Drawable[]
{
settingsSection = new LoginSettings
{
Padding = new MarginPadding(10),
RequestHide = Hide,
},
new Box
{
RelativeSizeAxes = Axes.X,
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
Height = 3,
Colour = colours.Yellow,
Alpha = 1,
},
}
}
} }
} }
}; };

View File

@ -159,7 +159,7 @@ namespace osu.Game.Overlays
{ {
Anchor = Anchor.TopCentre, Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre, Origin = Anchor.TopCentre,
RelativeSizeAxes = Axes.X, RelativeSizeAxes = Axes.Both,
}); });
} }

View File

@ -90,6 +90,7 @@ namespace osu.Game.Overlays.MedalSplash
}, },
description = new TextFlowContainer description = new TextFlowContainer
{ {
TextAnchor = Anchor.TopCentre,
Anchor = Anchor.TopCentre, Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre, Origin = Anchor.TopCentre,
RelativeSizeAxes = Axes.X, RelativeSizeAxes = Axes.X,
@ -115,15 +116,16 @@ namespace osu.Game.Overlays.MedalSplash
medalSprite.Texture = textures.Get(medal.ImageUrl); medalSprite.Texture = textures.Get(medal.ImageUrl);
medalGlow.Texture = textures.Get(@"MedalSplash/medal-glow"); medalGlow.Texture = textures.Get(@"MedalSplash/medal-glow");
description.Colour = colours.BlueLight; description.Colour = colours.BlueLight;
unlocked.Position = new Vector2(0f, medalContainer.Size.Y / 2 + 10);
infoFlow.Position = new Vector2(0f, unlocked.Position.Y + 90);
} }
protected override void LoadComplete() protected override void LoadComplete()
{ {
base.LoadComplete(); base.LoadComplete();
updateState(); updateState();
unlocked.Position = new Vector2(0f, medalContainer.DrawSize.Y / 2 + 10);
infoFlow.Position = new Vector2(0f, unlocked.Position.Y + 90);
} }
public DisplayState State public DisplayState State
@ -172,6 +174,7 @@ namespace osu.Game.Overlays.MedalSplash
this.ScaleTo(scale_when_full, duration, Easing.OutExpo); this.ScaleTo(scale_when_full, duration, Easing.OutExpo);
this.MoveToY(MedalOverlay.DISC_SIZE / 2 - 60, duration, Easing.OutExpo); this.MoveToY(MedalOverlay.DISC_SIZE / 2 - 60, duration, Easing.OutExpo);
unlocked.Show();
name.FadeInFromZero(duration + 100); name.FadeInFromZero(duration + 100);
description.FadeInFromZero(duration * 2); description.FadeInFromZero(duration * 2);
break; break;

View File

@ -127,7 +127,7 @@ namespace osu.Game.Overlays.Mods
if (mod == null) if (mod == null)
{ {
Mods = new Mod[0]; Mods = Array.Empty<Mod>();
Alpha = 0; Alpha = 0;
} }
else else

View File

@ -161,11 +161,15 @@ namespace osu.Game.Overlays
{ {
prevButton = new IconButton prevButton = new IconButton
{ {
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Action = prev, Action = prev,
Icon = FontAwesome.fa_step_backward, Icon = FontAwesome.fa_step_backward,
}, },
playButton = new IconButton playButton = new IconButton
{ {
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Scale = new Vector2(1.4f), Scale = new Vector2(1.4f),
IconScale = new Vector2(1.4f), IconScale = new Vector2(1.4f),
Action = play, Action = play,
@ -173,6 +177,8 @@ namespace osu.Game.Overlays
}, },
nextButton = new IconButton nextButton = new IconButton
{ {
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Action = next, Action = next,
Icon = FontAwesome.fa_step_forward, Icon = FontAwesome.fa_step_forward,
}, },

View File

@ -19,6 +19,7 @@ using osu.Game.Users;
using System.Diagnostics; using System.Diagnostics;
using System.Globalization; using System.Globalization;
using System.Collections.Generic; using System.Collections.Generic;
using osu.Framework.Graphics.Cursor;
namespace osu.Game.Overlays.Profile namespace osu.Game.Overlays.Profile
{ {
@ -119,15 +120,11 @@ namespace osu.Game.Overlays.Profile
} }
} }
}, },
new LinkFlowContainer.LinkText new LinkFlowContainer.ProfileLink(user)
{ {
Text = user.Username,
Url = $@"https://osu.ppy.sh/users/{user.Id}",
TextSize = 30,
Font = @"Exo2.0-RegularItalic",
Anchor = Anchor.BottomLeft, Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft, Origin = Anchor.BottomLeft,
Y = -48 Y = -48,
}, },
countryFlag = new DrawableFlag(user.Country?.FlagName) countryFlag = new DrawableFlag(user.Country?.FlagName)
{ {
@ -534,6 +531,19 @@ namespace osu.Game.Overlays.Profile
}); });
} }
} }
public class ProfileLink : LinkText, IHasTooltip
{
public string TooltipText => "View Profile in Browser";
public ProfileLink(User user)
{
Text = user.Username;
Url = $@"https://osu.ppy.sh/users/{user.Id}";
Font = @"Exo2.0-RegularItalic";
TextSize = 30;
}
}
} }
} }
} }

View File

@ -1,10 +1,30 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // 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.Graphics namespace osu.Game.Overlays.Settings.Sections.Graphics
{ {
public class DetailSettings : SettingsSubsection public class DetailSettings : SettingsSubsection
{ {
protected override string Header => "Detail Settings"; protected override string Header => "Detail Settings";
[BackgroundDependencyLoader]
private void load(OsuConfigManager config)
{
Children = new[]
{
new SettingsCheckbox
{
LabelText = "Storyboards",
Bindable = config.GetBindable<bool>(OsuSetting.ShowStoryboard)
},
new SettingsCheckbox
{
LabelText = "Rotate cursor when dragging",
Bindable = config.GetBindable<bool>(OsuSetting.CursorRotation)
},
};
}
} }
} }

View File

@ -34,6 +34,7 @@ namespace osu.Game.Overlays
public const float CONTENT_X_MARGIN = 50; public const float CONTENT_X_MARGIN = 50;
// receive input outside our bounds so we can trigger a close event on ourselves.
public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => true; public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => true;
protected override bool OnClick(InputState state) protected override bool OnClick(InputState state)

View File

@ -1,7 +1,6 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework;
using OpenTK.Graphics; using OpenTK.Graphics;
using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
@ -165,10 +164,8 @@ namespace osu.Game.Overlays
wavesContainer.Height = Math.Max(0, DrawHeight - (contentContainer.DrawHeight - contentContainer.Y)); wavesContainer.Height = Math.Max(0, DrawHeight - (contentContainer.DrawHeight - contentContainer.Y));
} }
private class Wave : Container, IStateful<Visibility> private class Wave : VisibilityContainer
{ {
public event Action<Visibility> StateChanged;
public float FinalPosition; public float FinalPosition;
public Wave() public Wave()
@ -183,13 +180,7 @@ namespace osu.Game.Overlays
Radius = 20f, Radius = 20f,
}; };
Children = new Drawable[] Child = new Box { RelativeSizeAxes = Axes.Both };
{
new Box
{
RelativeSizeAxes = Axes.Both,
},
};
} }
protected override void Update() protected override void Update()
@ -201,28 +192,8 @@ namespace osu.Game.Overlays
Height = Parent.Parent.DrawSize.Y * 1.5f; Height = Parent.Parent.DrawSize.Y * 1.5f;
} }
private Visibility state; protected override void PopIn() => this.MoveToY(FinalPosition, APPEAR_DURATION, easing_show);
protected override void PopOut() => this.MoveToY(Parent.Parent.DrawSize.Y, DISAPPEAR_DURATION, easing_hide);
public Visibility State
{
get { return state; }
set
{
state = value;
switch (value)
{
case Visibility.Hidden:
this.MoveToY(Parent.Parent.DrawSize.Y, DISAPPEAR_DURATION, easing_hide);
break;
case Visibility.Visible:
this.MoveToY(FinalPosition, APPEAR_DURATION, easing_show);
break;
}
StateChanged?.Invoke(State);
}
}
} }
} }
} }

View File

@ -25,6 +25,11 @@ namespace osu.Game.Rulesets.Objects.Drawables
/// </summary> /// </summary>
public virtual Color4 AccentColour { get; set; } = Color4.Gray; public virtual Color4 AccentColour { get; set; } = Color4.Gray;
/// <summary>
/// Whether a visible judgement should be displayed when this representation is hit.
/// </summary>
public virtual bool DisplayJudgement => true;
protected DrawableHitObject(HitObject hitObject) protected DrawableHitObject(HitObject hitObject)
{ {
HitObject = hitObject; HitObject = hitObject;

Some files were not shown because too many files have changed in this diff Show More